From 86b33ca505a9925e6139c961e305cc785ff59cd0 Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Wed, 13 May 2026 19:55:42 +0400 Subject: [PATCH 01/29] Enhance supported-chains command to list tenant-specific chains and mock forwarder addresses (#428) * Enhance supported-chains command to list tenant-specific chains and mock forwarder addresses - Introduced ChainForwarderRow struct for JSON output. - Updated command description and examples for clarity. - Integrated runtime context to fetch tenant-specific forwarders. - Added sorting for output rows by chain selector and address. - Improved error handling for missing user context and forwarders. - Updated tests to validate new forwarder functionality. * generate docs * fix lint issues --- .../supported_chains/supported_chains.go | 77 ++++++++++-- .../supported_chains/supported_chains_test.go | 115 ++++++++++++++++++ cmd/workflow/workflow.go | 2 +- docs/cre_workflow.md | 2 +- docs/cre_workflow_supported-chains.md | 6 +- internal/tenantctx/tenantctx.go | 52 ++++++++ internal/tenantctx/tenantctx_test.go | 47 +++++++ .../workflow_private_registry.go | 4 + 8 files changed, 291 insertions(+), 14 deletions(-) create mode 100644 cmd/workflow/supported_chains/supported_chains_test.go diff --git a/cmd/workflow/supported_chains/supported_chains.go b/cmd/workflow/supported_chains/supported_chains.go index 45d8737f..808f94d4 100644 --- a/cmd/workflow/supported_chains/supported_chains.go +++ b/cmd/workflow/supported_chains/supported_chains.go @@ -3,43 +3,98 @@ package supported_chains import ( "encoding/json" "fmt" + "sort" + "strings" + "text/tabwriter" "github.com/spf13/cobra" - "github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain/evm" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/ui" ) const outputFormatJSON = "json" -func New() *cobra.Command { +// ChainForwarderRow is one tenant chain with mock forwarder address for JSON output. +type ChainForwarderRow struct { + ChainName string `json:"chainName"` + ChainSelector uint64 `json:"chainSelector"` + Address string `json:"address"` +} + +func New(runtimeContext *runtime.Context) *cobra.Command { var outputFormat string cmd := &cobra.Command{ Use: "supported-chains", - Short: "List all supported chain names", + Short: "List chains and mock forwarder addresses for your tenant", + Long: "Lists chain selectors and mock Keystone forwarder contract addresses returned by the platform " + + "for the current tenant (from cre login / CRE_API_KEY). Chains are those enabled for your tenant.", Example: "cre workflow supported-chains\n" + " cre workflow supported-chains --output json", Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { if outputFormat != "" && outputFormat != outputFormatJSON { return fmt.Errorf("--output %q is not supported; only %q is accepted", outputFormat, outputFormatJSON) } - names := evm.SupportedChainNames() + if runtimeContext == nil || runtimeContext.TenantContext == nil { + return fmt.Errorf("user context not available — run `cre login` and retry") + } + + fwd := runtimeContext.TenantContext.Forwarders + if len(fwd) == 0 { + ui.Print("No forwarders returned for this tenant.") + ui.Dim("If you recently upgraded the CLI, run cre login again (or set CRE_API_KEY) to refresh context.") + return nil + } + + rows := make([]ChainForwarderRow, 0, len(fwd)) + for _, f := range fwd { + name := "-" + if n, err := settings.GetChainNameByChainSelector(f.ChainSelector); err == nil { + name = n + } + rows = append(rows, ChainForwarderRow{ + ChainName: name, + ChainSelector: f.ChainSelector, + Address: f.Address, + }) + } + sort.Slice(rows, func(i, j int) bool { + if rows[i].ChainSelector != rows[j].ChainSelector { + return rows[i].ChainSelector < rows[j].ChainSelector + } + return rows[i].Address < rows[j].Address + }) if outputFormat == outputFormatJSON { - out, err := json.MarshalIndent(names, "", " ") + out, err := json.MarshalIndent(rows, "", " ") if err != nil { - return fmt.Errorf("failed to serialize chain names as JSON: %w", err) + return fmt.Errorf("failed to serialize chains as JSON: %w", err) } - fmt.Println(string(out)) + ui.Print(string(out)) return nil } - fmt.Println("Supported chain names:") - for _, name := range names { - fmt.Printf(" %s\n", name) + ui.Print("Chains and mock forwarders (tenant-scoped):") + ui.Line() + + var buf strings.Builder + w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0) + if _, err := fmt.Fprintln(w, "CHAIN\tSELECTOR\tMOCK FORWARDER"); err != nil { + return err + } + for _, r := range rows { + if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n", r.ChainName, r.ChainSelector, r.Address); err != nil { + return err + } + } + if err := w.Flush(); err != nil { + return err } + ui.Print(strings.TrimSuffix(buf.String(), "\n")) return nil }, } diff --git a/cmd/workflow/supported_chains/supported_chains_test.go b/cmd/workflow/supported_chains/supported_chains_test.go new file mode 100644 index 00000000..f4c1adf1 --- /dev/null +++ b/cmd/workflow/supported_chains/supported_chains_test.go @@ -0,0 +1,115 @@ +package supported_chains_test + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + "sync" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + supportedchains "github.com/smartcontractkit/cre-cli/cmd/workflow/supported_chains" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" +) + +// captureStdout runs fn while redirecting os.Stdout to a buffer (ui package prints to stdout). +func captureStdout(t *testing.T, fn func()) string { + t.Helper() + old := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + var buf bytes.Buffer + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _, _ = io.Copy(&buf, r) + }() + + fn() + require.NoError(t, w.Close()) + os.Stdout = old + wg.Wait() + _ = r.Close() + return buf.String() +} + +func TestSupportedChains_MissingTenantContext(t *testing.T) { + t.Parallel() + logger := zerolog.New(io.Discard) + cmd := supportedchains.New(&runtime.Context{Logger: &logger}) + cmd.SetArgs([]string{}) + var stderr bytes.Buffer + cmd.SetErr(&stderr) + err := cmd.Execute() + require.Error(t, err) + require.Contains(t, err.Error(), "user context not available") +} + +func TestSupportedChains_NilRuntimeContext(t *testing.T) { + t.Parallel() + cmd := supportedchains.New(nil) + cmd.SetArgs([]string{}) + err := cmd.Execute() + require.Error(t, err) +} + +func TestSupportedChains_EmptyForwarders(t *testing.T) { + logger := zerolog.New(io.Discard) + cmd := supportedchains.New(&runtime.Context{ + Logger: &logger, + TenantContext: &tenantctx.EnvironmentContext{ + TenantID: "t1", + Forwarders: []tenantctx.Forwarder{}, + }, + }) + cmd.SetArgs([]string{}) + + out := captureStdout(t, func() { + require.NoError(t, cmd.Execute()) + }) + require.Contains(t, out, "No forwarders returned") +} + +func TestSupportedChains_JSON(t *testing.T) { + logger := zerolog.New(io.Discard) + const sepoliaSel = uint64(16015286601757825753) + cmd := supportedchains.New(&runtime.Context{ + Logger: &logger, + TenantContext: &tenantctx.EnvironmentContext{ + Forwarders: []tenantctx.Forwarder{ + {ChainSelector: sepoliaSel, Address: "0x15fC6ae953E024d975e77382eEeC56A9101f9F88"}, + }, + }, + }) + cmd.SetArgs([]string{"--output", "json"}) + + out := captureStdout(t, func() { + require.NoError(t, cmd.Execute()) + }) + var rows []supportedchains.ChainForwarderRow + require.NoError(t, json.Unmarshal([]byte(strings.TrimSpace(out)), &rows)) + require.Len(t, rows, 1) + require.Equal(t, sepoliaSel, rows[0].ChainSelector) + require.Equal(t, "0x15fC6ae953E024d975e77382eEeC56A9101f9F88", rows[0].Address) + require.NotEmpty(t, rows[0].ChainName) + require.NotEqual(t, "-", rows[0].ChainName) +} + +func TestSupportedChains_InvalidOutputFormat(t *testing.T) { + t.Parallel() + logger := zerolog.New(io.Discard) + cmd := supportedchains.New(&runtime.Context{ + Logger: &logger, + TenantContext: &tenantctx.EnvironmentContext{Forwarders: []tenantctx.Forwarder{{ChainSelector: 1, Address: "0x"}}}, + }) + cmd.SetArgs([]string{"--output", "yaml"}) + require.Error(t, cmd.Execute()) +} diff --git a/cmd/workflow/workflow.go b/cmd/workflow/workflow.go index 44d4e5f1..c301b83a 100644 --- a/cmd/workflow/workflow.go +++ b/cmd/workflow/workflow.go @@ -26,7 +26,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { Long: `The workflow command allows you to register and manage existing workflows.`, } - workflowCmd.AddCommand(supported_chains.New()) + workflowCmd.AddCommand(supported_chains.New(runtimeContext)) workflowCmd.AddCommand(activate.New(runtimeContext)) workflowCmd.AddCommand(build.New(runtimeContext)) workflowCmd.AddCommand(convert.New(runtimeContext)) diff --git a/docs/cre_workflow.md b/docs/cre_workflow.md index 2043c5b0..a7d7b49d 100644 --- a/docs/cre_workflow.md +++ b/docs/cre_workflow.md @@ -41,5 +41,5 @@ cre workflow [optional flags] * [cre workflow list](cre_workflow_list.md) - Lists workflows deployed for your organization * [cre workflow pause](cre_workflow_pause.md) - Pauses workflow on the Workflow Registry contract * [cre workflow simulate](cre_workflow_simulate.md) - Simulates a workflow -* [cre workflow supported-chains](cre_workflow_supported-chains.md) - List all supported chain names +* [cre workflow supported-chains](cre_workflow_supported-chains.md) - List chains and mock forwarder addresses for your tenant diff --git a/docs/cre_workflow_supported-chains.md b/docs/cre_workflow_supported-chains.md index a3300433..b3c80536 100644 --- a/docs/cre_workflow_supported-chains.md +++ b/docs/cre_workflow_supported-chains.md @@ -1,6 +1,10 @@ ## cre workflow supported-chains -List all supported chain names +List chains and mock forwarder addresses for your tenant + +### Synopsis + +Lists chain selectors and mock Keystone forwarder contract addresses returned by the platform for the current tenant (from cre login / CRE_API_KEY). Chains are those enabled for your tenant. ``` cre workflow supported-chains [optional flags] diff --git a/internal/tenantctx/tenantctx.go b/internal/tenantctx/tenantctx.go index c94a62d0..ea20230e 100644 --- a/internal/tenantctx/tenantctx.go +++ b/internal/tenantctx/tenantctx.go @@ -2,9 +2,11 @@ package tenantctx import ( "context" + "encoding/json" "fmt" "os" "path/filepath" + "strconv" "strings" "github.com/machinebox/graphql" @@ -29,12 +31,24 @@ type Registry struct { SecretsAuthFlows []string `yaml:"secrets_auth_flows" json:"secretsAuthFlows"` } +// Forwarder is a chain selector and mock forwarder contract address for the tenant. +type Forwarder struct { + ChainSelector uint64 `yaml:"chain_selector" json:"chainSelector"` + Address string `yaml:"address" json:"address"` +} + // EnvironmentContext holds user context for a single CLI environment. type EnvironmentContext struct { TenantID string `yaml:"tenant_id"` DefaultDonFamily string `yaml:"default_don_family"` VaultGatewayURL string `yaml:"vault_gateway_url"` Registries []*Registry `yaml:"registries"` + Forwarders []Forwarder `yaml:"forwarders,omitempty"` +} + +type gqlForwarder struct { + ChainSelector json.RawMessage `json:"chainSelector"` + Address string `json:"address"` } type getTenantConfigResponse struct { @@ -50,6 +64,7 @@ type getTenantConfigResponse struct { Address *string `json:"address"` SecretsAuthFlows []string `json:"secretsAuthFlows"` } `json:"registries"` + Forwarders []gqlForwarder `json:"forwarders"` } `json:"getTenantConfig"` } @@ -66,6 +81,10 @@ const getTenantConfigQuery = `query GetTenantConfig { address secretsAuthFlows } + forwarders { + chainSelector + address + } } }` @@ -104,11 +123,27 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client, }) } + forwarders := make([]Forwarder, 0, len(tc.Forwarders)) + for _, f := range tc.Forwarders { + sel, err := parseChainSelectorJSON(f.ChainSelector) + if err != nil { + log.Warn().Err(err).Str("address", f.Address).Msg("skipping forwarder with invalid chainSelector") + continue + } + addr := strings.TrimSpace(f.Address) + if addr == "" { + log.Warn().Uint64("chainSelector", sel).Msg("skipping forwarder with empty address") + continue + } + forwarders = append(forwarders, Forwarder{ChainSelector: sel, Address: addr}) + } + envCtx := &EnvironmentContext{ TenantID: tc.TenantID, DefaultDonFamily: tc.DefaultDonFamily, VaultGatewayURL: tc.VaultGatewayURL, Registries: registries, + Forwarders: forwarders, } contextMap := map[string]*EnvironmentContext{ @@ -151,6 +186,23 @@ func abbreviateAddress(addr string) string { return addr[:6] + "..." + addr[len(addr)-4:] } +// parseChainSelectorJSON decodes chainSelector from GraphQL JSON (string or number). +// Prefer string values in the API response to avoid loss of precision for large selectors. +func parseChainSelectorJSON(raw []byte) (uint64, error) { + if len(raw) == 0 || string(raw) == "null" { + return 0, fmt.Errorf("empty chain selector") + } + var s string + if err := json.Unmarshal(raw, &s); err == nil { + return strconv.ParseUint(strings.TrimSpace(s), 10, 64) + } + var n json.Number + if err := json.Unmarshal(raw, &n); err == nil { + return strconv.ParseUint(string(n), 10, 64) + } + return 0, fmt.Errorf("chain selector must be a decimal string or integer JSON value: %s", string(raw)) +} + // LoadContext reads the registry manifest from ~/.cre/ // and returns the EnvironmentContext for the given environment name. func LoadContext(envName string) (*EnvironmentContext, error) { diff --git a/internal/tenantctx/tenantctx_test.go b/internal/tenantctx/tenantctx_test.go index 225ac59c..63ddbb65 100644 --- a/internal/tenantctx/tenantctx_test.go +++ b/internal/tenantctx/tenantctx_test.go @@ -59,6 +59,12 @@ func gqlResponseOnChainAndPrivate() map[string]any { "secretsAuthFlows": []any{"BROWSER"}, }, }, + "forwarders": []any{ + map[string]any{ + "chainSelector": "16015286601757825753", + "address": "0x15fC6ae953E024d975e77382eEeC56A9101f9F88", + }, + }, }, }, } @@ -79,6 +85,7 @@ func gqlResponsePrivateOnly() map[string]any { "secretsAuthFlows": []any{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, } @@ -159,6 +166,17 @@ func TestFetchAndWriteContext_OnChainAndPrivate(t *testing.T) { if len(private.SecretsAuthFlows) != 1 || private.SecretsAuthFlows[0] != "browser" { t.Errorf("private SecretsAuthFlows = %v, want [browser]", private.SecretsAuthFlows) } + + if len(envCtx.Forwarders) != 1 { + t.Fatalf("expected 1 forwarder, got %d", len(envCtx.Forwarders)) + } + f := envCtx.Forwarders[0] + if f.ChainSelector != 16015286601757825753 { + t.Errorf("forwarder chain selector = %d, want %d", f.ChainSelector, uint64(16015286601757825753)) + } + if f.Address != "0x15fC6ae953E024d975e77382eEeC56A9101f9F88" { + t.Errorf("forwarder address = %q, want Sepolia mock forwarder", f.Address) + } } func TestFetchAndWriteContext_PrivateOnly(t *testing.T) { @@ -184,6 +202,35 @@ func TestFetchAndWriteContext_PrivateOnly(t *testing.T) { if envCtx.Registries[0].ID != "private" { t.Errorf("ID = %q, want %q", envCtx.Registries[0].ID, "private") } + if len(envCtx.Forwarders) != 0 { + t.Errorf("expected 0 forwarders, got %d", len(envCtx.Forwarders)) + } +} + +func TestParseChainSelectorJSON(t *testing.T) { + t.Parallel() + cases := []struct { + raw string + want uint64 + wantErr bool + }{ + {`"16015286601757825753"`, 16015286601757825753, false}, + {`16015286601757825753`, 16015286601757825753, false}, + {`null`, 0, true}, + {`"not-a-number"`, 0, true}, + } + for _, tc := range cases { + got, err := parseChainSelectorJSON([]byte(tc.raw)) + if tc.wantErr { + if err == nil { + t.Errorf("raw %q: wanted error", tc.raw) + } + continue + } + if err != nil || got != tc.want { + t.Errorf("raw %q: got (%d, %v), want %d", tc.raw, got, err, tc.want) + } + } } func TestFetchAndWriteContext_GQLError(t *testing.T) { diff --git a/test/multi_command_flows/workflow_private_registry.go b/test/multi_command_flows/workflow_private_registry.go index 5ab8c3df..92720350 100644 --- a/test/multi_command_flows/workflow_private_registry.go +++ b/test/multi_command_flows/workflow_private_registry.go @@ -120,6 +120,7 @@ func workflowDeployPrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -334,6 +335,7 @@ func workflowPausePrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -503,6 +505,7 @@ func workflowActivatePrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -672,6 +675,7 @@ func workflowDeletePrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) From 0f81beb4cda87bd870882472f9e8aa70e2a50413 Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Wed, 13 May 2026 22:41:34 -0500 Subject: [PATCH 02/29] Add ADI testnet and Celo sepolia support (#431) * Updated sdk versions * Added ADI testnet and Celo sepolia mock forwarders --- cmd/common/compile_test.go | 2 +- cmd/workflow/convert/convert_test.go | 2 +- cmd/workflow/simulate/chain/evm/supported_chains.go | 5 ++++- go.mod | 6 +++--- go.sum | 12 ++++++------ internal/constants/constants.go | 4 ++-- .../builtin/hello-world-ts/workflow/package.json | 4 ++-- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/cmd/common/compile_test.go b/cmd/common/compile_test.go index 340465e7..4813681a 100644 --- a/cmd/common/compile_test.go +++ b/cmd/common/compile_test.go @@ -118,7 +118,7 @@ func TestCompileWorkflowToWasm_TS_Success(t *testing.T) { mainPath := filepath.Join(dir, "main.ts") require.NoError(t, os.WriteFile(mainPath, []byte(`export async function main() { return "ok"; } `), 0600)) - require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.6.0"}} + require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.7.0"}} `), 0600)) install := exec.Command("bun", "install") install.Dir = dir diff --git a/cmd/workflow/convert/convert_test.go b/cmd/workflow/convert/convert_test.go index 2bd0bb53..277dbac5 100644 --- a/cmd/workflow/convert/convert_test.go +++ b/cmd/workflow/convert/convert_test.go @@ -303,7 +303,7 @@ production-settings: ` require.NoError(t, os.WriteFile(workflowYAML, []byte(yamlContent), 0600)) require.NoError(t, os.WriteFile(mainTS, []byte("export default function run() { return Promise.resolve({ result: \"ok\" }); }\n"), 0600)) - require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.6.0"}}`), 0600)) + require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.7.0"}}`), 0600)) h := newHandler(nil) err := h.Execute(Inputs{WorkflowFolder: dir, Force: true}) diff --git a/cmd/workflow/simulate/chain/evm/supported_chains.go b/cmd/workflow/simulate/chain/evm/supported_chains.go index 96bef7ff..e2f92e67 100644 --- a/cmd/workflow/simulate/chain/evm/supported_chains.go +++ b/cmd/workflow/simulate/chain/evm/supported_chains.go @@ -104,7 +104,7 @@ var SupportedChains = []chain.ChainConfig{ {Selector: chainselectors.MEGAETH_MAINNET.Selector, Forwarder: "0x6E9EE680ef59ef64Aa8C7371279c27E496b5eDc1"}, // Celo - // {Selector: chainselectors.CELO_SEPOLIA.Selector, Forwarder: "0x6E9EE680ef59ef64Aa8C7371279c27E496b5eDc1"}, + {Selector: chainselectors.CELO_SEPOLIA.Selector, Forwarder: "0x6E9EE680ef59ef64Aa8C7371279c27E496b5eDc1"}, {Selector: chainselectors.CELO_MAINNET.Selector, Forwarder: "0x6E9EE680ef59ef64Aa8C7371279c27E496b5eDc1"}, // Gnosis @@ -134,4 +134,7 @@ var SupportedChains = []chain.ChainConfig{ // DTCC {Selector: chainselectors.DTCC_TESTNET_ANDESITE.Selector, Forwarder: "0x6E9EE680ef59ef64Aa8C7371279c27E496b5eDc1"}, + + // ADI + {Selector: chainselectors.ADI_TESTNET.Selector, Forwarder: "0x9eF6468C5f37b976E57d52054c693269479A784d"}, } diff --git a/go.mod b/go.mod index ffb6c346..4158f7ee 100644 --- a/go.mod +++ b/go.mod @@ -27,13 +27,13 @@ require ( github.com/smartcontractkit/chainlink-common v0.11.2-0.20260421191147-d10b9943ac71 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260119171452-39c98c3b33cd - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260420204255-a3f3bdd56877 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260512230622-65f10f4cd305 github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5 github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 - github.com/smartcontractkit/cre-sdk-go v1.7.0 - github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.9 + github.com/smartcontractkit/cre-sdk-go v1.9.0 + github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.10 github.com/smartcontractkit/mcms v0.41.1 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 6fa0e20d..76db702d 100644 --- a/go.sum +++ b/go.sum @@ -1361,8 +1361,8 @@ github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0. github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:ATjAPIVJibHRcIfiG47rEQkUIOoYa6KDvWj3zwCAw6g= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d h1:AJy55QJ/pBhXkZjc7N+ATnWfxrcjq9BI9DmdtdjwDUQ= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:5JdppgngCOUS76p61zCinSCgOhPeYQ+OcDUuome5THQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260420204255-a3f3bdd56877 h1:6UueUIbck1Ogarm9rm/9TS6b09mKgMmx+YE8XFg63AQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260420204255-a3f3bdd56877/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260512230622-65f10f4cd305 h1:SH3vSzOujXLVEzh8sBL8tDQFI3sOvDOWyne33fMm94k= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= @@ -1401,10 +1401,10 @@ github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5/go.mod h1:pMBypeRoT8Nnb5gbkW6kIs+fBVsc/OzUx2eR9MtyFaw= github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 h1:G2TvBxwvzLMIU5hMPsQlVAY027mT1P/VTiWA6yegmpM= github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5/go.mod h1:8/a/Nlo4ZndgBEumXoE+gvBA5LH40BfDM+6SZa4Q89M= -github.com/smartcontractkit/cre-sdk-go v1.7.0 h1:MtaJ4jXS/5RcRCrjoza52/g3c0qrGXGB3V5yO9l6tUA= -github.com/smartcontractkit/cre-sdk-go v1.7.0/go.mod h1:yYrQFz1UH7hhRbPO0q4fgo1tfsJNd4yXnI3oCZE0RzM= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.9 h1:UORlnFd/BNjSX9MjUDjSg7/awWwgXqS+BdWOnyEIqWk= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.9/go.mod h1:M4EQIX5V66V7wKyDBa/8L3JLFf/m0FNmGDvjIqKPqSw= +github.com/smartcontractkit/cre-sdk-go v1.9.0 h1:E7Eum5XH3wZ+lNpr2j5biJRiP3lcK4NJuQbMRNXMmSE= +github.com/smartcontractkit/cre-sdk-go v1.9.0/go.mod h1:cnSxheEt2r9LhDswNv+EDyUr7GbOlKlTwHsCTa+FOtQ= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.10 h1:Z3xh3dN4NIIlGwOsBgrKi5WLBYfex8TyuW8tSDQd0BQ= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.10/go.mod h1:b1ZyaTde7PfEWweMe+rKwPe+auf0w9Dk48nge/JiIf0= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgHxTHuzJIF3Vj6LSMOnjhqKgRqYW+0MV2SExtCYL1Q= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 29cb7e92..bcfa16c1 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -58,8 +58,8 @@ const ( WorkflowLanguageWasm = "wasm" // SDK dependency versions (used by generate-bindings and go module init) - SdkVersion = "v1.7.0" - EVMCapabilitiesVersion = "v1.0.0-beta.9" + SdkVersion = "v1.9.0" + EVMCapabilitiesVersion = "v1.0.0-beta.10" HTTPCapabilitiesVersion = "v1.3.0" CronCapabilitiesVersion = "v1.3.0" diff --git a/internal/templaterepo/builtin/hello-world-ts/workflow/package.json b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json index 65487e3b..a703a03b 100644 --- a/internal/templaterepo/builtin/hello-world-ts/workflow/package.json +++ b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json @@ -8,9 +8,9 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.6.0" + "@chainlink/cre-sdk": "^1.7.0" }, "devDependencies": { "typescript": "5.9.3" } -} +} \ No newline at end of file From 7add59de9ebe8bdbc818f6e4623f8dea7fa66b08 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Thu, 14 May 2026 09:49:07 +0100 Subject: [PATCH 03/29] Add don family support for offchain registry (#426) * Add don family support for ofchain registry * missed * Simplify * update tests * fix tests * revert * revert 2 * review feedback --- internal/settings/registry_resolution.go | 23 ++- internal/settings/registry_resolution_test.go | 180 ++++++++---------- 2 files changed, 99 insertions(+), 104 deletions(-) diff --git a/internal/settings/registry_resolution.go b/internal/settings/registry_resolution.go index 797c4f7d..d5939385 100644 --- a/internal/settings/registry_resolution.go +++ b/internal/settings/registry_resolution.go @@ -68,6 +68,19 @@ func (r *OffChainRegistry) ID() string { return r.id } func (r *OffChainRegistry) Type() RegistryType { return RegistryTypeOffChain } func (r *OffChainRegistry) DonFamily() string { return r.donFamily } +// EffectiveDonFamily prefers envSet.DonFamily (CRE_CLI_DON_FAMILY at load); otherwise tenantCtx.DefaultDonFamily. +func EffectiveDonFamily(envSet *environments.EnvironmentSet, tenantCtx *tenantctx.EnvironmentContext) string { + if envSet != nil { + if v := strings.TrimSpace(envSet.DonFamily); v != "" { + return v + } + } + if tenantCtx != nil { + return strings.TrimSpace(tenantCtx.DefaultDonFamily) + } + return "" +} + // ResolveRegistry maps an optional deployment-registry value to a concrete // ResolvedRegistry. When deploymentRegistry is empty the static EnvironmentSet // values are used (backwards-compatible default). When set, it is looked up in @@ -78,7 +91,7 @@ func ResolveRegistry( envSet *environments.EnvironmentSet, ) (ResolvedRegistry, error) { if deploymentRegistry == "" { - return defaultFromEnvironmentSet(envSet), nil + return defaultFromEnvironmentSet(envSet, tenantCtx), nil } if tenantCtx == nil { @@ -92,7 +105,7 @@ func ResolveRegistry( } if ParseRegistryType(reg.Type) == RegistryTypeOffChain { - return NewOffChainRegistry(reg.ID, tenantCtx.DefaultDonFamily), nil + return NewOffChainRegistry(reg.ID, EffectiveDonFamily(envSet, tenantCtx)), nil } if reg.Address == nil || *reg.Address == "" { @@ -111,7 +124,7 @@ func ResolveRegistry( reg.ID, *reg.Address, chainName, - tenantCtx.DefaultDonFamily, + EffectiveDonFamily(envSet, tenantCtx), envSet.WorkflowRegistryChainExplorerURL, ), nil } @@ -125,12 +138,12 @@ func ParseRegistryType(raw string) RegistryType { return RegistryTypeOnChain } -func defaultFromEnvironmentSet(envSet *environments.EnvironmentSet) *OnChainRegistry { +func defaultFromEnvironmentSet(envSet *environments.EnvironmentSet, tenantCtx *tenantctx.EnvironmentContext) *OnChainRegistry { return NewOnChainRegistry( fmt.Sprintf("onchain:%s", envSet.WorkflowRegistryChainName), envSet.WorkflowRegistryAddress, envSet.WorkflowRegistryChainName, - envSet.DonFamily, + EffectiveDonFamily(envSet, tenantCtx), envSet.WorkflowRegistryChainExplorerURL, ) } diff --git a/internal/settings/registry_resolution_test.go b/internal/settings/registry_resolution_test.go index 282dfdc8..15010598 100644 --- a/internal/settings/registry_resolution_test.go +++ b/internal/settings/registry_resolution_test.go @@ -1,9 +1,10 @@ package settings import ( - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/tenantctx" ) @@ -16,7 +17,6 @@ func stagingEnvSet() *environments.EnvironmentSet { WorkflowRegistryAddress: "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135", WorkflowRegistryChainName: "ethereum-testnet-sepolia", WorkflowRegistryChainExplorerURL: "https://sepolia.etherscan.io", - DonFamily: "zone-a", } } @@ -40,94 +40,96 @@ func sampleTenantCtx() *tenantctx.EnvironmentContext { } } -func TestResolveRegistry_EmptyFallsBackToEnvSet(t *testing.T) { +func TestResolveRegistry_Empty_AddressAndChainFromEnvSet_NoDonWithoutTenantOrEnv(t *testing.T) { envSet := stagingEnvSet() resolved, err := ResolveRegistry("", nil, envSet) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + assert.NoError(t, err) onchain, ok := resolved.(*OnChainRegistry) - if !ok { - t.Fatalf("expected *OnChainRegistry, got %T", resolved) - } - if onchain.Address() != envSet.WorkflowRegistryAddress { - t.Errorf("expected address %s, got %s", envSet.WorkflowRegistryAddress, onchain.Address()) - } - if onchain.ChainName() != envSet.WorkflowRegistryChainName { - t.Errorf("expected chain %s, got %s", envSet.WorkflowRegistryChainName, onchain.ChainName()) - } - if onchain.DonFamily() != envSet.DonFamily { - t.Errorf("expected don %s, got %s", envSet.DonFamily, onchain.DonFamily()) - } - if onchain.ExplorerURL() != envSet.WorkflowRegistryChainExplorerURL { - t.Errorf("expected explorer %s, got %s", envSet.WorkflowRegistryChainExplorerURL, onchain.ExplorerURL()) - } + assert.True(t, ok, "expected *OnChainRegistry, got %T", resolved) + assert.Equal(t, envSet.WorkflowRegistryAddress, onchain.Address()) + assert.Equal(t, envSet.WorkflowRegistryChainName, onchain.ChainName()) + assert.Equal(t, "", onchain.DonFamily()) + assert.Equal(t, envSet.WorkflowRegistryChainExplorerURL, onchain.ExplorerURL()) +} + +func TestResolveRegistry_DefaultRegistry_UsesTenantDonFamily(t *testing.T) { + envSet := stagingEnvSet() + tenantCtx := &tenantctx.EnvironmentContext{DefaultDonFamily: "tenant-zone"} + resolved, err := ResolveRegistry("", tenantCtx, envSet) + assert.NoError(t, err) + onchain, ok := resolved.(*OnChainRegistry) + assert.True(t, ok, "expected *OnChainRegistry, got %T", resolved) + assert.Equal(t, "tenant-zone", onchain.DonFamily()) } func TestResolveRegistry_OnChainFromContext(t *testing.T) { resolved, err := ResolveRegistry("onchain:ethereum-testnet-sepolia", sampleTenantCtx(), stagingEnvSet()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + assert.NoError(t, err) onchain, ok := resolved.(*OnChainRegistry) - if !ok { - t.Fatalf("expected *OnChainRegistry, got %T", resolved) - } - if onchain.Address() != "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135" { - t.Errorf("unexpected address: %s", onchain.Address()) - } - if onchain.ChainName() != "ethereum-testnet-sepolia" { - t.Errorf("unexpected chain name: %s", onchain.ChainName()) - } - if onchain.DonFamily() != "zone-a" { - t.Errorf("unexpected don family: %s", onchain.DonFamily()) - } + assert.True(t, ok, "expected *OnChainRegistry, got %T", resolved) + assert.Equal(t, "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135", onchain.Address()) + assert.Equal(t, "ethereum-testnet-sepolia", onchain.ChainName()) + assert.Equal(t, "zone-a", onchain.DonFamily()) +} + +func TestResolveRegistry_NamedRegistry_EnvOverridesTenantDonFamily(t *testing.T) { + envSet := stagingEnvSet() + envSet.DonFamily = "from-env-var" + tenantCtx := sampleTenantCtx() + tenantCtx.DefaultDonFamily = "from-tenant" + + t.Run("on-chain named", func(t *testing.T) { + resolved, err := ResolveRegistry("onchain:ethereum-testnet-sepolia", tenantCtx, envSet) + assert.NoError(t, err) + onchain, ok := resolved.(*OnChainRegistry) + assert.True(t, ok, "expected *OnChainRegistry, got %T", resolved) + assert.Equal(t, "from-env-var", onchain.DonFamily()) + }) + + t.Run("private", func(t *testing.T) { + resolved, err := ResolveRegistry("private", tenantCtx, envSet) + assert.NoError(t, err) + offchain, ok := resolved.(*OffChainRegistry) + assert.True(t, ok, "expected *OffChainRegistry, got %T", resolved) + assert.Equal(t, "from-env-var", offchain.DonFamily()) + }) +} + +func TestResolveRegistry_DefaultRegistry_EnvOverridesTenantDonFamily(t *testing.T) { + envSet := stagingEnvSet() + envSet.DonFamily = "from-env-var" + tenantCtx := &tenantctx.EnvironmentContext{DefaultDonFamily: "tenant-zone"} + resolved, err := ResolveRegistry("", tenantCtx, envSet) + assert.NoError(t, err) + onchain, ok := resolved.(*OnChainRegistry) + assert.True(t, ok, "expected *OnChainRegistry, got %T", resolved) + assert.Equal(t, "from-env-var", onchain.DonFamily()) } func TestResolveRegistry_OffChainFromContext(t *testing.T) { resolved, err := ResolveRegistry("private", sampleTenantCtx(), stagingEnvSet()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + assert.NoError(t, err) offchain, ok := resolved.(*OffChainRegistry) - if !ok { - t.Fatalf("expected *OffChainRegistry, got %T", resolved) - } - if offchain.ID() != "private" { - t.Errorf("expected ID %q, got %q", "private", offchain.ID()) - } - if offchain.DonFamily() != "zone-a" { - t.Errorf("unexpected don family: %s", offchain.DonFamily()) - } - if resolved.Type() != RegistryTypeOffChain { - t.Errorf("expected type %s, got %s", RegistryTypeOffChain, resolved.Type()) - } + assert.True(t, ok, "expected *OffChainRegistry, got %T", resolved) + assert.Equal(t, "private", offchain.ID()) + assert.Equal(t, "zone-a", offchain.DonFamily()) + assert.Equal(t, RegistryTypeOffChain, resolved.Type()) } func TestResolveRegistry_UnknownID(t *testing.T) { _, err := ResolveRegistry("does-not-exist", sampleTenantCtx(), stagingEnvSet()) - if err == nil { - t.Fatal("expected error for unknown registry ID") - } - if !strings.Contains(err.Error(), "not found in user context") { - t.Errorf("unexpected error: %v", err) - } - if !strings.Contains(err.Error(), "onchain:ethereum-testnet-sepolia") { - t.Errorf("error should list available IDs: %v", err) - } + assert.Error(t, err) + assert.Contains(t, err.Error(), "not found in user context") + assert.Contains(t, err.Error(), "onchain:ethereum-testnet-sepolia") } func TestResolveRegistry_NilTenantContextWithID(t *testing.T) { _, err := ResolveRegistry("private", nil, stagingEnvSet()) - if err == nil { - t.Fatal("expected error when TenantContext is nil with a registry ID set") - } - if !strings.Contains(err.Error(), "user context is not available") { - t.Errorf("unexpected error: %v", err) - } + assert.Error(t, err) + assert.Contains(t, err.Error(), "user context is not available") } func TestResolveRegistry_OnChainMissingAddress(t *testing.T) { @@ -142,12 +144,8 @@ func TestResolveRegistry_OnChainMissingAddress(t *testing.T) { }, } _, err := ResolveRegistry("onchain:no-addr", ctx, stagingEnvSet()) - if err == nil { - t.Fatal("expected error for on-chain registry without address") - } - if !strings.Contains(err.Error(), "has no address") { - t.Errorf("unexpected error: %v", err) - } + assert.Error(t, err) + assert.Contains(t, err.Error(), "has no address") } func TestResolveRegistry_OnChainMissingChainSelector(t *testing.T) { @@ -162,12 +160,8 @@ func TestResolveRegistry_OnChainMissingChainSelector(t *testing.T) { }, } _, err := ResolveRegistry("onchain:no-chain", ctx, stagingEnvSet()) - if err == nil { - t.Fatal("expected error for on-chain registry without chain selector") - } - if !strings.Contains(err.Error(), "has no chain_selector") { - t.Errorf("unexpected error: %v", err) - } + assert.Error(t, err) + assert.Contains(t, err.Error(), "has no chain_selector") } func TestParseRegistryType(t *testing.T) { @@ -183,32 +177,20 @@ func TestParseRegistryType(t *testing.T) { {"unknown", RegistryTypeOnChain}, } for _, tt := range tests { - if got := ParseRegistryType(tt.input); got != tt.want { - t.Errorf("ParseRegistryType(%q) = %q, want %q", tt.input, got, tt.want) - } + t.Run(tt.input, func(t *testing.T) { + assert.Equal(t, tt.want, ParseRegistryType(tt.input)) + }) } } func TestInterfaceMethods(t *testing.T) { onchain := NewOnChainRegistry("oc-1", "0x1234", "sepolia", "zone-a", "https://etherscan.io") - if onchain.Type() != RegistryTypeOnChain { - t.Errorf("expected on-chain type") - } - if onchain.ID() != "oc-1" { - t.Errorf("expected ID oc-1, got %s", onchain.ID()) - } - if onchain.DonFamily() != "zone-a" { - t.Errorf("expected don zone-a, got %s", onchain.DonFamily()) - } + assert.Equal(t, RegistryTypeOnChain, onchain.Type()) + assert.Equal(t, "oc-1", onchain.ID()) + assert.Equal(t, "zone-a", onchain.DonFamily()) offchain := NewOffChainRegistry("private", "zone-b") - if offchain.Type() != RegistryTypeOffChain { - t.Errorf("expected off-chain type") - } - if offchain.ID() != "private" { - t.Errorf("expected ID private, got %s", offchain.ID()) - } - if offchain.DonFamily() != "zone-b" { - t.Errorf("expected don zone-b, got %s", offchain.DonFamily()) - } + assert.Equal(t, RegistryTypeOffChain, offchain.Type()) + assert.Equal(t, "private", offchain.ID()) + assert.Equal(t, "zone-b", offchain.DonFamily()) } From 907b32c80f7fd1cbac64bda0eeabe7f4eafbf89b Mon Sep 17 00:00:00 2001 From: Jakub Nowak Date: Thu, 14 May 2026 11:46:11 +0200 Subject: [PATCH 04/29] Update workflow hash owner resolution (#427) * Update workflow hash owner resolution. Ensure hash uses registry-aware owner selection so off-chain registries require --public_key while on-chain retains existing owner derivation. Co-authored-by: Cursor * docs * fetch logged context if possible * docs * fix messages * ui improvment --------- Co-authored-by: Cursor --- cmd/root.go | 36 ++++++++++- cmd/workflow/hash/hash.go | 69 ++++++++++++++++++++- cmd/workflow/hash/hash_test.go | 110 ++++++++++++++++++++++++++++++++- docs/cre_workflow_hash.md | 2 +- 4 files changed, 208 insertions(+), 9 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 8c24d6e5..7288fc00 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -236,7 +236,7 @@ func newRootCommand() *cobra.Command { spinner.Update("Loading user context...") } if err := runtimeContext.AttachTenantContext(cmd.Context()); err != nil { - runtimeContext.Logger.Warn().Err(err).Msg("failed to load user context") + ui.Warning("Failed to load user context") } // Check if organization is ungated for commands that require it @@ -280,8 +280,38 @@ func newRootCommand() *cobra.Command { return fmt.Errorf("%w", err) } - if err := runtimeContext.AttachResolvedRegistry(); err != nil { - return err + if cmd.CommandPath() == "cre workflow hash" && + runtimeContext.Settings != nil && + strings.EqualFold(runtimeContext.Settings.Workflow.UserWorkflowSettings.DeploymentRegistry, "private") && + runtimeContext.TenantContext == nil { + if showSpinner { + spinner.Update("Loading credentials...") + } + err := runtimeContext.AttachCredentials(cmd.Context(), shouldSkipValidation(cmd)) + if err != nil { + ui.Warning("Failed to load credentials for workflow hash") + } else { + if showSpinner { + spinner.Update("Loading user context...") + } + if err := runtimeContext.AttachTenantContext(cmd.Context()); err != nil { + ui.Warning("Failed to load user context") + } + } + } + + shouldResolveRegistry := true + if cmd.Name() == "hash" && + runtimeContext.TenantContext == nil && + runtimeContext.Settings != nil && + runtimeContext.Settings.Workflow.UserWorkflowSettings.DeploymentRegistry != "" { + shouldResolveRegistry = false + } + + if shouldResolveRegistry { + if err := runtimeContext.AttachResolvedRegistry(); err != nil { + return err + } } if isRegistryRPCCommand(cmd) { diff --git a/cmd/workflow/hash/hash.go b/cmd/workflow/hash/hash.go index 99cc311c..49533870 100644 --- a/cmd/workflow/hash/hash.go +++ b/cmd/workflow/hash/hash.go @@ -3,6 +3,7 @@ package hash import ( "fmt" "os" + "strings" "github.com/spf13/cobra" @@ -24,6 +25,8 @@ type Inputs struct { OwnerFromSettings string PrivateKey string SkipTypeChecks bool + RegistryType settings.RegistryType + DerivedOwner string } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -41,6 +44,10 @@ func New(runtimeContext *runtime.Context) *cobra.Command { v := runtimeContext.Viper rawPrivKey := v.GetString(settings.EthPrivateKeyEnvVar) + registryType, err := resolveRegistryType(runtimeContext) + if err != nil { + return err + } inputs := Inputs{ ForUser: forUser, @@ -51,6 +58,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) @@ -59,8 +68,9 @@ func New(runtimeContext *runtime.Context) *cobra.Command { hashCmd.Flags().String("public_key", "", "Owner address to use for computing the workflow hash. "+ - "Required when CRE_ETH_PRIVATE_KEY is not set and no workflow-owner-address is configured. "+ - "Defaults to the address derived from CRE_ETH_PRIVATE_KEY or the workflow-owner-address in project settings.") + "Required when the owner cannot be automatically derived. "+ + "Auto-derivation uses workflow-owner-address/CRE_ETH_PRIVATE_KEY for on-chain or login-derived owner for off-chain. "+ + "If provided, overrides the owner derived from credentials or settings.") hashCmd.Flags().String("wasm", "", "Path or URL to a pre-built WASM binary (skips compilation)") hashCmd.Flags().String("config", "", "Override the config file path from workflow.yaml") hashCmd.Flags().Bool("no-config", false, "Hash without a config file") @@ -87,7 +97,13 @@ func Execute(inputs Inputs) error { return err } - ownerAddress, err := ResolveOwner(inputs.ForUser, inputs.OwnerFromSettings, inputs.PrivateKey) + ownerAddress, err := ResolveOwnerForRegistry( + inputs.RegistryType, + inputs.ForUser, + inputs.OwnerFromSettings, + inputs.PrivateKey, + inputs.DerivedOwner, + ) if err != nil { return err } @@ -127,6 +143,53 @@ func ResolveOwner(forUser, ownerFromSettings, privateKey string) (string, error) return "", fmt.Errorf("cannot determine workflow owner: provide --public_key or ensure CRE_ETH_PRIVATE_KEY is set") } +func ResolveOwnerForRegistry(registryType settings.RegistryType, forUser, ownerFromSettings, privateKey, derivedOwner string) (string, error) { + if registryType == settings.RegistryTypeOffChain { + if forUser != "" { + return forUser, nil + } + if derivedOwner == "" { + return "", fmt.Errorf("cannot determine workflow owner for off-chain registry: provide --public_key") + } + return derivedOwner, nil + } + + return ResolveOwner(forUser, ownerFromSettings, privateKey) +} + +func resolveRegistryType(runtimeContext *runtime.Context) (settings.RegistryType, error) { + if runtimeContext.ResolvedRegistry != nil { + return runtimeContext.ResolvedRegistry.Type(), nil + } + + deploymentRegistry := runtimeContext.Settings.Workflow.UserWorkflowSettings.DeploymentRegistry + if deploymentRegistry == "" { + return settings.RegistryTypeOnChain, nil + } + + if runtimeContext.TenantContext != nil { + resolved, err := settings.ResolveRegistry( + deploymentRegistry, + runtimeContext.TenantContext, + runtimeContext.EnvironmentSet, + ) + if err != nil { + return "", err + } + return resolved.Type(), nil + } + + if isPrivateRegistryID(deploymentRegistry) { + return settings.RegistryTypeOffChain, nil + } + + return settings.RegistryTypeOnChain, nil +} + +func isPrivateRegistryID(deploymentRegistry string) bool { + return strings.EqualFold(deploymentRegistry, "private") +} + func loadBinary(wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) ([]byte, error) { if wasmFlag != "" { if cmdcommon.IsURL(wasmFlag) { diff --git a/cmd/workflow/hash/hash_test.go b/cmd/workflow/hash/hash_test.go index 6587ba2e..0ed08a82 100644 --- a/cmd/workflow/hash/hash_test.go +++ b/cmd/workflow/hash/hash_test.go @@ -15,6 +15,8 @@ import ( workflowUtils "github.com/smartcontractkit/chainlink-common/pkg/workflows" cmdcommon "github.com/smartcontractkit/cre-cli/cmd/common" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/settings" ) // Well-known test private key (never use on a real network). @@ -23,6 +25,8 @@ const testPrivateKey = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7b // Address derived from testPrivateKey. const testDerivedAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +const testDerivedOwner = "0x1111111111111111111111111111111111111111" + func TestResolveOwner_WithForUser(t *testing.T) { t.Parallel() addr, err := ResolveOwner("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "", "") @@ -108,6 +112,34 @@ func TestExecute_WithoutForUser_NoKey_Errors(t *testing.T) { assert.Contains(t, err.Error(), "--public_key") } +func TestResolveOwnerForRegistry_OffChainRequiresPublicKey(t *testing.T) { + t.Parallel() + _, err := ResolveOwnerForRegistry(settings.RegistryTypeOffChain, "", "0xSettingsOwner", testPrivateKey, "") + require.Error(t, err) + assert.Contains(t, err.Error(), "--public_key") +} + +func TestResolveOwnerForRegistry_OffChainUsesPublicKey(t *testing.T) { + t.Parallel() + addr, err := ResolveOwnerForRegistry(settings.RegistryTypeOffChain, "0xOwner", "0xSettingsOwner", testPrivateKey, testDerivedOwner) + require.NoError(t, err) + assert.Equal(t, "0xOwner", addr) +} + +func TestResolveOwnerForRegistry_OffChainUsesDerivedOwner(t *testing.T) { + t.Parallel() + addr, err := ResolveOwnerForRegistry(settings.RegistryTypeOffChain, "", "0xSettingsOwner", testPrivateKey, testDerivedOwner) + require.NoError(t, err) + assert.Equal(t, testDerivedOwner, addr) +} + +func TestResolveOwnerForRegistry_OnChainUsesDefaults(t *testing.T) { + t.Parallel() + addr, err := ResolveOwnerForRegistry(settings.RegistryTypeOnChain, "", "0xSettingsOwner", "", testDerivedOwner) + require.NoError(t, err) + assert.Equal(t, "0xSettingsOwner", addr) +} + func TestExecute_HashesAreDeterministic(t *testing.T) { wasmFile, configFile := setupTestArtifacts(t) @@ -159,6 +191,52 @@ func TestExecute_EmptyConfig(t *testing.T) { require.NoError(t, err) } +func TestExecute_OffChainRequiresPublicKey(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + RegistryType: settings.RegistryTypeOffChain, + } + + err := Execute(inputs) + require.Error(t, err) + assert.Contains(t, err.Error(), "--public_key") +} + +func TestExecute_OffChainUsesPublicKey(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + ForUser: "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + RegistryType: settings.RegistryTypeOffChain, + DerivedOwner: testDerivedOwner, + } + + err := Execute(inputs) + require.NoError(t, err) +} + +func TestExecute_OffChainUsesDerivedOwner(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + RegistryType: settings.RegistryTypeOffChain, + DerivedOwner: testDerivedOwner, + } + + err := Execute(inputs) + require.NoError(t, err) +} + func TestExecute_DifferentOwnersProduceDifferentWorkflowHashes(t *testing.T) { wasmFile, configFile := setupTestArtifacts(t) @@ -217,8 +295,8 @@ func TestHashCommandFlags(t *testing.T) { f := cmd.Flags().Lookup("public_key") require.NotNil(t, f, "public_key flag should exist") assert.Equal(t, "", f.DefValue) - assert.Contains(t, f.Usage, "Required when CRE_ETH_PRIVATE_KEY is not set") - assert.Contains(t, f.Usage, "Defaults to") + assert.Contains(t, f.Usage, "Required when the owner cannot be automatically derived") + assert.Contains(t, f.Usage, "overrides the owner derived") f = cmd.Flags().Lookup("wasm") require.NotNil(t, f, "wasm flag should exist") @@ -230,6 +308,34 @@ func TestHashCommandFlags(t *testing.T) { require.NotNil(t, f, "no-config flag should exist") } +func TestResolveRegistryType(t *testing.T) { + t.Parallel() + + runtimeContext := &runtime.Context{ + Settings: &settings.Settings{ + Workflow: settings.WorkflowSettings{ + UserWorkflowSettings: struct { + WorkflowOwnerAddress string `mapstructure:"workflow-owner-address" yaml:"workflow-owner-address"` + WorkflowOwnerType string `mapstructure:"workflow-owner-type" yaml:"workflow-owner-type"` + WorkflowName string `mapstructure:"workflow-name" yaml:"workflow-name"` + DeploymentRegistry string `mapstructure:"deployment-registry" yaml:"deployment-registry"` + }{ + DeploymentRegistry: "private", + }, + }, + }, + } + + registryType, err := resolveRegistryType(runtimeContext) + require.NoError(t, err) + assert.Equal(t, settings.RegistryTypeOffChain, registryType) + + runtimeContext.Settings.Workflow.UserWorkflowSettings.DeploymentRegistry = "mainnet" + registryType, err = resolveRegistryType(runtimeContext) + require.NoError(t, err) + assert.Equal(t, settings.RegistryTypeOnChain, registryType) +} + // setupTestArtifacts creates a minimal WASM file and config file in a temp directory. func setupTestArtifacts(t *testing.T) (wasmPath, configPath string) { t.Helper() diff --git a/docs/cre_workflow_hash.md b/docs/cre_workflow_hash.md index 44b2fb13..8acc754f 100644 --- a/docs/cre_workflow_hash.md +++ b/docs/cre_workflow_hash.md @@ -24,7 +24,7 @@ cre workflow hash [optional flags] --default-config Use the config path from workflow.yaml settings (default behavior) -h, --help help for hash --no-config Hash without a config file - --public_key string Owner address to use for computing the workflow hash. Required when CRE_ETH_PRIVATE_KEY is not set and no workflow-owner-address is configured. Defaults to the address derived from CRE_ETH_PRIVATE_KEY or the workflow-owner-address in project settings. + --public_key string Owner address to use for computing the workflow hash. Required when the owner cannot be automatically derived. Auto-derivation uses workflow-owner-address/CRE_ETH_PRIVATE_KEY for on-chain or login-derived owner for off-chain. If provided, overrides the owner derived from credentials or settings. --skip-type-checks Skip TypeScript project typecheck during compilation (passes --skip-type-checks to cre-compile) --wasm string Path or URL to a pre-built WASM binary (skips compilation) ``` From 70504890b88c26c1e89cd45117d04d7257e767c9 Mon Sep 17 00:00:00 2001 From: Emmanuel Jacquier Date: Thu, 14 May 2026 10:12:30 -0400 Subject: [PATCH 05/29] Non-evms supported-chains list & --allow-unknown-chains flag (#419) * Also list non-evm in supported-chains cmd, added a --allow-unknown-chains to skip validation for simulation/ deploy * gendoc --- cmd/root.go | 10 ++++ docs/cre.md | 15 +++--- docs/cre_account.md | 13 ++--- docs/cre_account_access.md | 13 ++--- docs/cre_account_link-key.md | 13 ++--- docs/cre_account_list-key.md | 13 ++--- docs/cre_account_unlink-key.md | 13 ++--- docs/cre_generate-bindings.md | 11 +++-- docs/cre_init.md | 13 ++--- docs/cre_login.md | 13 ++--- docs/cre_logout.md | 13 ++--- docs/cre_registry.md | 13 ++--- docs/cre_registry_list.md | 13 ++--- docs/cre_secrets.md | 13 ++--- docs/cre_secrets_create.md | 15 +++--- docs/cre_secrets_delete.md | 15 +++--- docs/cre_secrets_execute.md | 15 +++--- docs/cre_secrets_list.md | 15 +++--- docs/cre_secrets_update.md | 15 +++--- docs/cre_templates.md | 13 ++--- docs/cre_templates_add.md | 13 ++--- docs/cre_templates_list.md | 13 ++--- docs/cre_templates_remove.md | 13 ++--- docs/cre_update.md | 13 ++--- docs/cre_version.md | 13 ++--- docs/cre_whoami.md | 13 ++--- docs/cre_workflow.md | 13 ++--- docs/cre_workflow_activate.md | 13 ++--- docs/cre_workflow_build.md | 13 ++--- docs/cre_workflow_custom-build.md | 13 ++--- docs/cre_workflow_delete.md | 13 ++--- docs/cre_workflow_deploy.md | 13 ++--- docs/cre_workflow_get.md | 13 ++--- docs/cre_workflow_hash.md | 13 ++--- docs/cre_workflow_limits.md | 13 ++--- docs/cre_workflow_limits_export.md | 13 ++--- docs/cre_workflow_list.md | 13 ++--- docs/cre_workflow_pause.md | 13 ++--- docs/cre_workflow_simulate.md | 13 ++--- docs/cre_workflow_supported-chains.md | 13 ++--- internal/settings/settings_get.go | 67 +++++++++++++++++++++++--- internal/settings/settings_load.go | 2 + internal/settings/workflow_settings.go | 7 ++- 43 files changed, 355 insertions(+), 248 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 7288fc00..a7306216 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -445,6 +445,16 @@ func newRootCommand() *cobra.Command { false, "Fail instead of prompting; requires all inputs via flags", ) + // allow-unknown-chains skips chain-name validation against the chain-selectors + // registry so experimental chains can be configured and tested before they are + // added upstream. The chain name is still passed through verbatim to RPC and + // selector lookups, which will surface their own errors if the chain is truly + // unknown to downstream callers. + rootCmd.PersistentFlags().Bool( + settings.Flags.AllowUnknownChains.Name, + false, + "Skip chain-name validation against the chain-selectors registry (for experimental chains)", + ) rootCmd.CompletionOptions.HiddenDefaultCmd = true secretsCmd := secrets.New(runtimeContext) diff --git a/docs/cre.md b/docs/cre.md index 6e495250..3f77ed01 100644 --- a/docs/cre.md +++ b/docs/cre.md @@ -13,13 +13,14 @@ cre [optional flags] ### Options ``` - -e, --env string Path to .env file which contains sensitive info - -h, --help help for cre - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + -h, --help help for cre + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_account.md b/docs/cre_account.md index bda41812..908033fc 100644 --- a/docs/cre_account.md +++ b/docs/cre_account.md @@ -19,12 +19,13 @@ cre account [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_account_access.md b/docs/cre_account_access.md index 12a3e5af..a477d1af 100644 --- a/docs/cre_account_access.md +++ b/docs/cre_account_access.md @@ -19,12 +19,13 @@ cre account access [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_account_link-key.md b/docs/cre_account_link-key.md index 21baa9ac..4f04ca28 100644 --- a/docs/cre_account_link-key.md +++ b/docs/cre_account_link-key.md @@ -22,12 +22,13 @@ cre account link-key [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_account_list-key.md b/docs/cre_account_list-key.md index e5a14229..771b08f9 100644 --- a/docs/cre_account_list-key.md +++ b/docs/cre_account_list-key.md @@ -19,12 +19,13 @@ cre account list-key [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_account_unlink-key.md b/docs/cre_account_unlink-key.md index b7dd2c96..37925798 100644 --- a/docs/cre_account_unlink-key.md +++ b/docs/cre_account_unlink-key.md @@ -21,12 +21,13 @@ cre account unlink-key [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_generate-bindings.md b/docs/cre_generate-bindings.md index e955955d..9b6bacf9 100644 --- a/docs/cre_generate-bindings.md +++ b/docs/cre_generate-bindings.md @@ -37,11 +37,12 @@ cre generate-bindings [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_init.md b/docs/cre_init.md index 962da29a..6db85847 100644 --- a/docs/cre_init.md +++ b/docs/cre_init.md @@ -29,12 +29,13 @@ cre init [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_login.md b/docs/cre_login.md index 8ce4fb04..44a0b2ae 100644 --- a/docs/cre_login.md +++ b/docs/cre_login.md @@ -28,12 +28,13 @@ cre login [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_logout.md b/docs/cre_logout.md index e70e3b89..55a89250 100644 --- a/docs/cre_logout.md +++ b/docs/cre_logout.md @@ -19,12 +19,13 @@ cre logout [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_registry.md b/docs/cre_registry.md index 149d0fdb..0ef2a741 100644 --- a/docs/cre_registry.md +++ b/docs/cre_registry.md @@ -19,12 +19,13 @@ cre registry [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_registry_list.md b/docs/cre_registry_list.md index 7872dca8..bbeb20bb 100644 --- a/docs/cre_registry_list.md +++ b/docs/cre_registry_list.md @@ -33,12 +33,13 @@ cre registry list ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_secrets.md b/docs/cre_secrets.md index 6526d819..a2b48c07 100644 --- a/docs/cre_secrets.md +++ b/docs/cre_secrets.md @@ -20,12 +20,13 @@ cre secrets [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_secrets_create.md b/docs/cre_secrets_create.md index 15c34497..a3dd12f9 100644 --- a/docs/cre_secrets_create.md +++ b/docs/cre_secrets_create.md @@ -23,13 +23,14 @@ cre secrets create my-secrets.yaml ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_secrets_delete.md b/docs/cre_secrets_delete.md index 31f135ac..c61bcd9a 100644 --- a/docs/cre_secrets_delete.md +++ b/docs/cre_secrets_delete.md @@ -23,13 +23,14 @@ cre secrets delete my-secrets.yaml ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_secrets_execute.md b/docs/cre_secrets_execute.md index e4b3a10d..464bd617 100644 --- a/docs/cre_secrets_execute.md +++ b/docs/cre_secrets_execute.md @@ -22,13 +22,14 @@ cre secrets execute 157364...af4d5.json ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_secrets_list.md b/docs/cre_secrets_list.md index 295fa4ec..2214c55a 100644 --- a/docs/cre_secrets_list.md +++ b/docs/cre_secrets_list.md @@ -18,13 +18,14 @@ cre secrets list [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_secrets_update.md b/docs/cre_secrets_update.md index 4639c07c..526a2c12 100644 --- a/docs/cre_secrets_update.md +++ b/docs/cre_secrets_update.md @@ -23,13 +23,14 @@ cre secrets update my-secrets.yaml ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + --timeout duration Timeout for secrets operations (e.g. 30m, 2h, 48h). (default 48h0m0s) + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_templates.md b/docs/cre_templates.md index 3acf6fbe..dc4f9df9 100644 --- a/docs/cre_templates.md +++ b/docs/cre_templates.md @@ -24,12 +24,13 @@ cre templates [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_templates_add.md b/docs/cre_templates_add.md index 2f3206ce..adaa6303 100644 --- a/docs/cre_templates_add.md +++ b/docs/cre_templates_add.md @@ -25,12 +25,13 @@ cre templates add smartcontractkit/cre-templates@main myorg/my-templates ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_templates_list.md b/docs/cre_templates_list.md index 63ca94da..4994db23 100644 --- a/docs/cre_templates_list.md +++ b/docs/cre_templates_list.md @@ -21,12 +21,13 @@ cre templates list [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_templates_remove.md b/docs/cre_templates_remove.md index 3e47f961..23730215 100644 --- a/docs/cre_templates_remove.md +++ b/docs/cre_templates_remove.md @@ -25,12 +25,13 @@ cre templates remove smartcontractkit/cre-templates myorg/my-templates ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_update.md b/docs/cre_update.md index 559cd3e1..5dbc7438 100644 --- a/docs/cre_update.md +++ b/docs/cre_update.md @@ -15,12 +15,13 @@ cre update [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_version.md b/docs/cre_version.md index def3782b..3409f9f7 100644 --- a/docs/cre_version.md +++ b/docs/cre_version.md @@ -19,12 +19,13 @@ cre version [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_whoami.md b/docs/cre_whoami.md index 28131a27..1b8b9fd4 100644 --- a/docs/cre_whoami.md +++ b/docs/cre_whoami.md @@ -19,12 +19,13 @@ cre whoami [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow.md b/docs/cre_workflow.md index a7d7b49d..25797b8a 100644 --- a/docs/cre_workflow.md +++ b/docs/cre_workflow.md @@ -19,12 +19,13 @@ cre workflow [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_activate.md b/docs/cre_workflow_activate.md index 6fff282b..778789b4 100644 --- a/docs/cre_workflow_activate.md +++ b/docs/cre_workflow_activate.md @@ -27,12 +27,13 @@ cre workflow activate ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_build.md b/docs/cre_workflow_build.md index 0a5d5e33..845ca732 100644 --- a/docs/cre_workflow_build.md +++ b/docs/cre_workflow_build.md @@ -27,12 +27,13 @@ cre workflow build ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_custom-build.md b/docs/cre_workflow_custom-build.md index 88264e8b..d97a154e 100644 --- a/docs/cre_workflow_custom-build.md +++ b/docs/cre_workflow_custom-build.md @@ -26,12 +26,13 @@ cre workflow custom-build ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_delete.md b/docs/cre_workflow_delete.md index d4ce8496..4268dc7d 100644 --- a/docs/cre_workflow_delete.md +++ b/docs/cre_workflow_delete.md @@ -27,12 +27,13 @@ cre workflow delete ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_deploy.md b/docs/cre_workflow_deploy.md index 8254aa9a..c89a3d4b 100644 --- a/docs/cre_workflow_deploy.md +++ b/docs/cre_workflow_deploy.md @@ -34,12 +34,13 @@ cre workflow deploy ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_get.md b/docs/cre_workflow_get.md index c60cc49a..b93eeb69 100644 --- a/docs/cre_workflow_get.md +++ b/docs/cre_workflow_get.md @@ -27,12 +27,13 @@ cre workflow get ./my-workflow --target staging ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_hash.md b/docs/cre_workflow_hash.md index 8acc754f..09d60256 100644 --- a/docs/cre_workflow_hash.md +++ b/docs/cre_workflow_hash.md @@ -32,12 +32,13 @@ cre workflow hash [optional flags] ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_limits.md b/docs/cre_workflow_limits.md index 634f2402..886c6e8a 100644 --- a/docs/cre_workflow_limits.md +++ b/docs/cre_workflow_limits.md @@ -15,12 +15,13 @@ The limits command provides tools for managing workflow simulation limits. ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_limits_export.md b/docs/cre_workflow_limits_export.md index b778ae7c..fa0ddbe6 100644 --- a/docs/cre_workflow_limits_export.md +++ b/docs/cre_workflow_limits_export.md @@ -26,12 +26,13 @@ cre workflow limits export > my-limits.json ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_list.md b/docs/cre_workflow_list.md index 261c7eed..88d78e6e 100644 --- a/docs/cre_workflow_list.md +++ b/docs/cre_workflow_list.md @@ -32,12 +32,13 @@ cre workflow list ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_pause.md b/docs/cre_workflow_pause.md index 2513d39a..1c3ea36c 100644 --- a/docs/cre_workflow_pause.md +++ b/docs/cre_workflow_pause.md @@ -27,12 +27,13 @@ cre workflow pause ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_simulate.md b/docs/cre_workflow_simulate.md index 8e95a535..af5bd1fa 100644 --- a/docs/cre_workflow_simulate.md +++ b/docs/cre_workflow_simulate.md @@ -37,12 +37,13 @@ cre workflow simulate ./my-workflow ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/docs/cre_workflow_supported-chains.md b/docs/cre_workflow_supported-chains.md index b3c80536..c813d844 100644 --- a/docs/cre_workflow_supported-chains.md +++ b/docs/cre_workflow_supported-chains.md @@ -27,12 +27,13 @@ cre workflow supported-chains ### Options inherited from parent commands ``` - -e, --env string Path to .env file which contains sensitive info - --non-interactive Fail instead of prompting; requires all inputs via flags - -R, --project-root string Path to the project root - -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config - -T, --target string Use target settings from YAML config - -v, --verbose Run command in VERBOSE mode + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode ``` ### SEE ALSO diff --git a/internal/settings/settings_get.go b/internal/settings/settings_get.go index 9ddb2d28..f83fb79b 100644 --- a/internal/settings/settings_get.go +++ b/internal/settings/settings_get.go @@ -262,16 +262,69 @@ func ChainNameFromSelectorString(raw string) (string, error) { return GetChainNameByChainSelector(sel) } +// GetChainSelectorByChainName resolves a chain name to its chain selector across +// all chain families supported by chain-selectors (EVM, Aptos, Solana, Sui, +// Tron, Ton, Starknet, Stellar, Canton). Returns an error if the name is not +// found in any family. func GetChainSelectorByChainName(name string) (uint64, error) { - chainID, err := chainSelectors.ChainIdFromName(name) - if err != nil { - return 0, fmt.Errorf("failed to get chain ID from name %q: %w\n Run 'cre workflow supported-chains' to see all valid chain names", name, err) + if chainID, err := chainSelectors.ChainIdFromName(name); err == nil { + selector, selErr := chainSelectors.SelectorFromChainId(chainID) + if selErr != nil { + return 0, fmt.Errorf("failed to get selector from chain ID %d: %w", chainID, selErr) + } + return selector, nil } - selector, err := chainSelectors.SelectorFromChainId(chainID) - if err != nil { - return 0, fmt.Errorf("failed to get selector from chain ID %d: %w", chainID, err) + if selector, ok := findNonEVMSelectorByName(name); ok { + return selector, nil } - return selector, nil + return 0, fmt.Errorf("chain not found for name %q\n Run 'cre workflow supported-chains' to see all valid chain names", name) +} + +// findNonEVMSelectorByName looks up a chain name in every non-EVM family +// registered with chain-selectors. The EVM family is intentionally excluded +// because ChainIdFromName already covers it. +func findNonEVMSelectorByName(name string) (uint64, bool) { + for _, c := range chainSelectors.AptosALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.SolanaALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.SuiALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.TronALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.TonALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.StarknetALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.StellarALL { + if c.Name == name { + return c.Selector, true + } + } + for _, c := range chainSelectors.CantonALL { + if c.Name == name { + return c.Selector, true + } + } + return 0, false } diff --git a/internal/settings/settings_load.go b/internal/settings/settings_load.go index 6a9e394c..58bd6790 100644 --- a/internal/settings/settings_load.go +++ b/internal/settings/settings_load.go @@ -47,6 +47,7 @@ type flagNames struct { NonInteractive Flag SkipConfirmation Flag ChangesetFile Flag + AllowUnknownChains Flag } var Flags = flagNames{ @@ -64,6 +65,7 @@ var Flags = flagNames{ NonInteractive: Flag{"non-interactive", ""}, SkipConfirmation: Flag{"yes", "y"}, ChangesetFile: Flag{"changeset-file", ""}, + AllowUnknownChains: Flag{"allow-unknown-chains", ""}, } func AddTxnTypeFlags(cmd *cobra.Command) { diff --git a/internal/settings/workflow_settings.go b/internal/settings/workflow_settings.go index e31b708d..992a9f3b 100644 --- a/internal/settings/workflow_settings.go +++ b/internal/settings/workflow_settings.go @@ -163,7 +163,7 @@ func loadWorkflowSettings(logger *zerolog.Logger, v *viper.Viper, cmd *cobra.Com return WorkflowSettings{}, errors.Wrap(err, "for target "+target) } - if err := validateSettings(&workflowSettings); err != nil { + if err := validateSettings(&workflowSettings, v.GetBool(Flags.AllowUnknownChains.Name)); err != nil { return WorkflowSettings{}, errors.Wrap(err, "for target "+target) } @@ -260,12 +260,15 @@ func flattenWorkflowSettingsToViper(v *viper.Viper, target string, effectiveWork return nil } -func validateSettings(config *WorkflowSettings) error { +func validateSettings(config *WorkflowSettings, allowUnknownChains bool) error { // TODO validate that all chain names mentioned for the contracts above have a matching URL specified for _, rpc := range config.RPCs { if err := isValidRpcUrl(rpc.Url); err != nil { return errors.Wrap(err, "invalid rpc url for "+rpc.ChainName) } + if allowUnknownChains { + continue + } if err := IsValidChainName(rpc.ChainName); err != nil { return err } From 6aa5160374116f4bee5ccd75f17f9fb22fb224bc Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Wed, 20 May 2026 07:00:45 +0400 Subject: [PATCH 06/29] Implement SecureRemove function for safer file deletion and update logout handler to use it. (#440) --- cmd/logout/logout.go | 2 +- internal/credentials/credentials.go | 35 ++++++++++++++++++++++++ internal/credentials/credentials_test.go | 25 +++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/cmd/logout/logout.go b/cmd/logout/logout.go index ee0f86f1..5411c8bc 100644 --- a/cmd/logout/logout.go +++ b/cmd/logout/logout.go @@ -88,7 +88,7 @@ func (h *handler) execute() error { } } - if err := os.Remove(credPath); err != nil && !os.IsNotExist(err) { + if err := credentials.SecureRemove(credPath); err != nil { spinner.Stop() return fmt.Errorf("failed to delete credentials file: %w", err) } diff --git a/internal/credentials/credentials.go b/internal/credentials/credentials.go index 3e7f3533..f0b3ca5d 100644 --- a/internal/credentials/credentials.go +++ b/internal/credentials/credentials.go @@ -106,6 +106,41 @@ func SaveCredentials(tokenSet *CreLoginTokenSet) error { return nil } +// SecureRemove overwrites a file with zeroes before deleting it. +func SecureRemove(path string) error { + f, err := os.OpenFile(path, os.O_RDWR, 0) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + info, err := f.Stat() + if err != nil { + _ = f.Close() + return err + } + + size := info.Size() + if size > 0 { + zeros := make([]byte, size) + if _, err := f.WriteAt(zeros, 0); err != nil { + _ = f.Close() + return err + } + if err := f.Sync(); err != nil { + _ = f.Close() + return err + } + } + + if err := f.Close(); err != nil { + return err + } + return os.Remove(path) +} + // decodeJWTClaims extracts the claims map from the access token JWT payload. func (c *Credentials) decodeJWTClaims() (map[string]interface{}, error) { if c.Tokens == nil || c.Tokens.AccessToken == "" { diff --git a/internal/credentials/credentials_test.go b/internal/credentials/credentials_test.go index fd5ef8f5..af594c24 100644 --- a/internal/credentials/credentials_test.go +++ b/internal/credentials/credentials_test.go @@ -411,3 +411,28 @@ func TestCheckIsUngatedOrganization_InvalidJWTFormat(t *testing.T) { }) } } + +func TestSecureRemove(t *testing.T) { + t.Run("missing file is no-op", func(t *testing.T) { + path := filepath.Join(t.TempDir(), "missing.yaml") + if err := SecureRemove(path); err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("overwrites with zeroes before delete", func(t *testing.T) { + path := filepath.Join(t.TempDir(), ConfigFile) + secret := []byte("AccessToken: super-secret-token\n") + if err := os.WriteFile(path, secret, 0o600); err != nil { + t.Fatalf("write file: %v", err) + } + + if err := SecureRemove(path); err != nil { + t.Fatalf("SecureRemove: %v", err) + } + + if _, err := os.Stat(path); !os.IsNotExist(err) { + t.Fatal("expected file to be removed") + } + }) +} From 4635576d52144d6492ac8ba7db062bdf1f6ad2bc Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Wed, 20 May 2026 11:13:40 +0100 Subject: [PATCH 07/29] Remove org owned feature flag (#441) --- cmd/secrets/common/handler.go | 25 ++------- cmd/secrets/common/handler_test.go | 74 +++++-------------------- internal/environments/environments.go | 9 --- internal/environments/environments.yaml | 3 - 4 files changed, 19 insertions(+), 92 deletions(-) diff --git a/cmd/secrets/common/handler.go b/cmd/secrets/common/handler.go index d0ba25f2..cbf057e6 100644 --- a/cmd/secrets/common/handler.go +++ b/cmd/secrets/common/handler.go @@ -277,16 +277,8 @@ func (h *Handler) fetchVaultMasterPublicKeyHex() (string, error) { return rpcResp.Result.PublicKey, nil } -// ResolveEffectiveOwner returns the owner string to use for vault secret identifiers. -// When SecretsOrgOwned is enabled, the org ID (from auth validation) is used; -// otherwise, the workflow owner address is used and must be a valid hex address. +// ResolveEffectiveOwner returns the checksummed workflow owner address for owner-key vault operations. func (h *Handler) ResolveEffectiveOwner() (string, error) { - if h.EnvironmentSet != nil && h.EnvironmentSet.SecretsOrgOwned { - if h.Credentials == nil || h.Credentials.OrgID == "" { - return "", fmt.Errorf("org ID required when CRE_CLI_SECRETS_ORG_OWNED is enabled; ensure auth validation succeeds") - } - return h.Credentials.OrgID, nil - } if !common.IsHexAddress(h.OwnerAddress) { return "", fmt.Errorf("owner address %q is not a valid hex address", h.OwnerAddress) } @@ -296,7 +288,7 @@ func (h *Handler) ResolveEffectiveOwner() (string, error) { // ResolveVaultIdentifierOwnerForAuth returns the owner string used in vault JSON-RPC payloads // (SecretIdentifier.Owner and list request Owner). Browser auth always uses the signed-in // organization ID so digests and identifiers align with JWT AuthorizedOwner() on the gateway; -// owner-key auth uses ResolveEffectiveOwner() (workflow address unless CRE_CLI_SECRETS_ORG_OWNED). +// onchain auth uses ResolveEffectiveOwner() (linked workflow owner address). func (h *Handler) ResolveVaultIdentifierOwnerForAuth(secretsAuth string) (string, error) { if IsBrowserFlow(secretsAuth) { if h.Credentials == nil { @@ -313,18 +305,9 @@ func (h *Handler) ResolveVaultIdentifierOwnerForAuth(secretsAuth string) (string return h.ResolveEffectiveOwner() } -// EncryptSecrets takes the raw secrets and encrypts them, returning pointers. -// When SecretsOrgOwned is enabled, uses SHA256(orgID) as the TDH2 label and orgID as the owner. -// Otherwise, uses the workflow owner address left-padded to 32 bytes as the TDH2 label. +// EncryptSecrets takes the raw secrets and encrypts them for the owner-key (onchain) flow. +// TDH2 label is the workflow owner address left-padded to 32 bytes; SecretIdentifier.Owner is the same hex address string. func (h *Handler) EncryptSecrets(rawSecrets UpsertSecretsInputs) ([]*vault.EncryptedSecret, error) { - if h.EnvironmentSet != nil && h.EnvironmentSet.SecretsOrgOwned { - owner, err := h.ResolveEffectiveOwner() - if err != nil { - return nil, err - } - return h.EncryptSecretsForBrowserOrg(rawSecrets, owner) - } - pubKeyHex, err := h.fetchVaultMasterPublicKeyHex() if err != nil { return nil, err diff --git a/cmd/secrets/common/handler_test.go b/cmd/secrets/common/handler_test.go index fb547b62..21cc51fc 100644 --- a/cmd/secrets/common/handler_test.go +++ b/cmd/secrets/common/handler_test.go @@ -134,64 +134,38 @@ func TestEncryptSecrets(t *testing.T) { } func TestResolveEffectiveOwner(t *testing.T) { - t.Run("returns canonicalized address when SecretsOrgOwned is false", func(t *testing.T) { + t.Run("returns canonicalized workflow owner address", func(t *testing.T) { h, _, _ := newMockHandler(t) h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - h.EnvironmentSet.SecretsOrgOwned = false owner, err := h.ResolveEffectiveOwner() require.NoError(t, err) require.Equal(t, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", owner) }) - t.Run("errors when SecretsOrgOwned is false and owner address is empty", func(t *testing.T) { + t.Run("errors when owner address is empty", func(t *testing.T) { h, _, _ := newMockHandler(t) h.OwnerAddress = "" - h.EnvironmentSet.SecretsOrgOwned = false _, err := h.ResolveEffectiveOwner() require.Error(t, err) require.Contains(t, err.Error(), "not a valid hex address") }) - t.Run("errors when SecretsOrgOwned is false and owner address is malformed", func(t *testing.T) { + t.Run("errors when owner address is malformed", func(t *testing.T) { h, _, _ := newMockHandler(t) h.OwnerAddress = "not-an-address" - h.EnvironmentSet.SecretsOrgOwned = false _, err := h.ResolveEffectiveOwner() require.Error(t, err) require.Contains(t, err.Error(), "not a valid hex address") }) - - t.Run("returns org ID when SecretsOrgOwned is true and org ID is set", func(t *testing.T) { - h, _, _ := newMockHandler(t) - h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - h.EnvironmentSet.SecretsOrgOwned = true - h.Credentials.OrgID = "org-123" - - owner, err := h.ResolveEffectiveOwner() - require.NoError(t, err) - require.Equal(t, "org-123", owner) - }) - - t.Run("errors when SecretsOrgOwned is true but org ID is empty", func(t *testing.T) { - h, _, _ := newMockHandler(t) - h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - h.EnvironmentSet.SecretsOrgOwned = true - h.Credentials.OrgID = "" - - _, err := h.ResolveEffectiveOwner() - require.Error(t, err) - require.Contains(t, err.Error(), "org ID required") - }) } func TestResolveVaultIdentifierOwnerForAuth(t *testing.T) { - t.Run("browser returns org ID when SecretsOrgOwned is false", func(t *testing.T) { + t.Run("browser returns org ID", func(t *testing.T) { h, _, _ := newMockHandler(t) h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - h.EnvironmentSet.SecretsOrgOwned = false h.Credentials.AuthType = credentials.AuthTypeBearer h.Credentials.OrgID = "org-browser" @@ -220,10 +194,9 @@ func TestResolveVaultIdentifierOwnerForAuth(t *testing.T) { require.Contains(t, err.Error(), "organization information is missing") }) - t.Run("owner-key delegates to ResolveEffectiveOwner", func(t *testing.T) { + t.Run("onchain delegates to ResolveEffectiveOwner", func(t *testing.T) { h, _, _ := newMockHandler(t) h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - h.EnvironmentSet.SecretsOrgOwned = false owner, err := h.ResolveVaultIdentifierOwnerForAuth(SecretsAuthOnchain) require.NoError(t, err) @@ -231,7 +204,7 @@ func TestResolveVaultIdentifierOwnerForAuth(t *testing.T) { }) } -func TestEncryptSecrets_OrgOwned(t *testing.T) { +func TestEncryptSecrets_UsesWorkflowOwnerAddress(t *testing.T) { mockGw := &mockGatewayClient{ post: func(body []byte) ([]byte, int, error) { var req jsonrpc2.Request[vaultcommon.GetPublicKeyRequest] @@ -247,34 +220,17 @@ func TestEncryptSecrets_OrgOwned(t *testing.T) { }, } - raw := UpsertSecretsInputs{ - {ID: "secret-1", Value: "val1", Namespace: "main"}, - } - - t.Run("uses orgID as owner when SecretsOrgOwned is true", func(t *testing.T) { - h, _, _ := newMockHandler(t) - h.Gw = mockGw - h.EnvironmentSet.SecretsOrgOwned = true - h.Credentials.OrgID = "org-456" - - enc, err := h.EncryptSecrets(raw) - require.NoError(t, err) - require.Len(t, enc, 1) - require.Equal(t, "org-456", enc[0].Id.Owner) - require.Equal(t, "secret-1", enc[0].Id.Key) - }) - - t.Run("uses address as owner when SecretsOrgOwned is false", func(t *testing.T) { - h, _, _ := newMockHandler(t) - h.Gw = mockGw - h.OwnerAddress = "0xabc" - h.EnvironmentSet.SecretsOrgOwned = false + h, _, _ := newMockHandler(t) + h.Gw = mockGw + h.OwnerAddress = "0xabc" - enc, err := h.EncryptSecrets(raw) - require.NoError(t, err) - require.Len(t, enc, 1) - require.Equal(t, "0xabc", enc[0].Id.Owner) + enc, err := h.EncryptSecrets(UpsertSecretsInputs{ + {ID: "secret-1", Value: "val1", Namespace: "main"}, }) + require.NoError(t, err) + require.Len(t, enc, 1) + require.Equal(t, "0xabc", enc[0].Id.Owner) + require.Equal(t, "secret-1", enc[0].Id.Key) } func TestPackAllowlistRequestTxData_Success_With0x(t *testing.T) { diff --git a/internal/environments/environments.go b/internal/environments/environments.go index c851fd74..5cf980bd 100644 --- a/internal/environments/environments.go +++ b/internal/environments/environments.go @@ -25,7 +25,6 @@ const ( EnvVarWorkflowRegistryChainName = "CRE_CLI_WORKFLOW_REGISTRY_CHAIN_NAME" EnvVarWorkflowRegistryChainExplorerURL = "CRE_CLI_WORKFLOW_REGISTRY_CHAIN_EXPLORER_URL" EnvVarDonFamily = "CRE_CLI_DON_FAMILY" - EnvVarSecretsOrgOwned = "CRE_CLI_SECRETS_ORG_OWNED" DefaultEnv = "PRODUCTION" StagingEnv = "STAGING" @@ -48,7 +47,6 @@ type EnvironmentSet struct { WorkflowRegistryChainName string `yaml:"CRE_CLI_WORKFLOW_REGISTRY_CHAIN_NAME"` WorkflowRegistryChainExplorerURL string `yaml:"CRE_CLI_WORKFLOW_REGISTRY_CHAIN_EXPLORER_URL"` DonFamily string `yaml:"CRE_CLI_DON_FAMILY"` - SecretsOrgOwned bool `yaml:"CRE_CLI_SECRETS_ORG_OWNED"` } // RequiresVPN returns true if the GraphQL endpoint is on a private network @@ -101,7 +99,6 @@ func NewEnvironmentSet(ff *fileFormat, envName string) *EnvironmentSet { wrAddress := os.Getenv(EnvVarWorkflowRegistryAddress) wrChainName := os.Getenv(EnvVarWorkflowRegistryChainName) donFamily := os.Getenv(EnvVarDonFamily) - secretsOrgOwned := os.Getenv(EnvVarSecretsOrgOwned) set.EnvName = envName if authBase != "" { @@ -131,9 +128,6 @@ func NewEnvironmentSet(ff *fileFormat, envName string) *EnvironmentSet { if donFamily != "" { set.DonFamily = donFamily } - if secretsOrgOwned != "" { - set.SecretsOrgOwned = strings.EqualFold(secretsOrgOwned, "true") - } newEnvironmentSetWarningsOnce.Do(func() { switch envName { @@ -170,9 +164,6 @@ func NewEnvironmentSet(ff *fileFormat, envName string) *EnvironmentSet { if donFamily != "" { ui.Warning(fmt.Sprintf("%s set, using %s", EnvVarDonFamily, donFamily)) } - if secretsOrgOwned != "" { - ui.Warning(fmt.Sprintf("%s set, using %s", EnvVarSecretsOrgOwned, secretsOrgOwned)) - } }) return &set diff --git a/internal/environments/environments.yaml b/internal/environments/environments.yaml index c88c0103..d789bf29 100644 --- a/internal/environments/environments.yaml +++ b/internal/environments/environments.yaml @@ -6,7 +6,6 @@ ENVIRONMENTS: CRE_CLI_GRAPHQL_URL: https://graphql-cre-dev.tailf8f749.ts.net/graphql CRE_VAULT_DON_GATEWAY_URL: https://cre-gateway-one-zone-a.main.stage.cldev.sh/ CRE_CLI_DON_FAMILY: "zone-a" - CRE_CLI_SECRETS_ORG_OWNED: false CRE_CLI_WORKFLOW_REGISTRY_ADDRESS: "0x7e69E853D9Ce50C2562a69823c80E01360019Cef" CRE_CLI_WORKFLOW_REGISTRY_CHAIN_NAME: "ethereum-testnet-sepolia" # eth-sepolia @@ -19,7 +18,6 @@ ENVIRONMENTS: CRE_CLI_GRAPHQL_URL: https://graphql-cre-stage.tailf8f749.ts.net/graphql CRE_VAULT_DON_GATEWAY_URL: https://cre-gateway-one-zone-a.main.stage.cldev.sh/ CRE_CLI_DON_FAMILY: "zone-a" - CRE_CLI_SECRETS_ORG_OWNED: false CRE_CLI_WORKFLOW_REGISTRY_ADDRESS: "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135" CRE_CLI_WORKFLOW_REGISTRY_CHAIN_NAME: "ethereum-testnet-sepolia" # eth-sepolia @@ -32,7 +30,6 @@ ENVIRONMENTS: CRE_CLI_GRAPHQL_URL: https://api.cre.chain.link/graphql CRE_VAULT_DON_GATEWAY_URL: https://01.gateway.zone-a.cre.chain.link CRE_CLI_DON_FAMILY: "zone-a" - CRE_CLI_SECRETS_ORG_OWNED: false CRE_CLI_WORKFLOW_REGISTRY_ADDRESS: "0x4Ac54353FA4Fa961AfcC5ec4B118596d3305E7e5" CRE_CLI_WORKFLOW_REGISTRY_CHAIN_NAME: "ethereum-mainnet" From 64b33c7d8b3f63c862703132a7bcc05c0f8bc029 Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Thu, 21 May 2026 06:03:40 -0500 Subject: [PATCH 08/29] Add adi-mainnet support (#445) Added adi-mainnet support --- cmd/common/compile_test.go | 2 +- cmd/workflow/convert/convert_test.go | 2 +- cmd/workflow/simulate/chain/evm/supported_chains.go | 1 + go.mod | 6 +++--- go.sum | 12 ++++++------ internal/constants/constants.go | 4 ++-- .../builtin/hello-world-ts/workflow/package.json | 2 +- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cmd/common/compile_test.go b/cmd/common/compile_test.go index 4813681a..4e811aec 100644 --- a/cmd/common/compile_test.go +++ b/cmd/common/compile_test.go @@ -118,7 +118,7 @@ func TestCompileWorkflowToWasm_TS_Success(t *testing.T) { mainPath := filepath.Join(dir, "main.ts") require.NoError(t, os.WriteFile(mainPath, []byte(`export async function main() { return "ok"; } `), 0600)) - require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.7.0"}} + require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.8.0"}} `), 0600)) install := exec.Command("bun", "install") install.Dir = dir diff --git a/cmd/workflow/convert/convert_test.go b/cmd/workflow/convert/convert_test.go index 277dbac5..6be7c933 100644 --- a/cmd/workflow/convert/convert_test.go +++ b/cmd/workflow/convert/convert_test.go @@ -303,7 +303,7 @@ production-settings: ` require.NoError(t, os.WriteFile(workflowYAML, []byte(yamlContent), 0600)) require.NoError(t, os.WriteFile(mainTS, []byte("export default function run() { return Promise.resolve({ result: \"ok\" }); }\n"), 0600)) - require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.7.0"}}`), 0600)) + require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.8.0"}}`), 0600)) h := newHandler(nil) err := h.Execute(Inputs{WorkflowFolder: dir, Force: true}) diff --git a/cmd/workflow/simulate/chain/evm/supported_chains.go b/cmd/workflow/simulate/chain/evm/supported_chains.go index e2f92e67..25db7edb 100644 --- a/cmd/workflow/simulate/chain/evm/supported_chains.go +++ b/cmd/workflow/simulate/chain/evm/supported_chains.go @@ -137,4 +137,5 @@ var SupportedChains = []chain.ChainConfig{ // ADI {Selector: chainselectors.ADI_TESTNET.Selector, Forwarder: "0x9eF6468C5f37b976E57d52054c693269479A784d"}, + {Selector: chainselectors.ADI_MAINNET.Selector, Forwarder: "0x6Aa382fb8762E1232936478DD9DbC04F637028f1"}, } diff --git a/go.mod b/go.mod index 4158f7ee..677bbc68 100644 --- a/go.mod +++ b/go.mod @@ -27,13 +27,13 @@ require ( github.com/smartcontractkit/chainlink-common v0.11.2-0.20260421191147-d10b9943ac71 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260119171452-39c98c3b33cd - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260512230622-65f10f4cd305 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520185909-e3aaa299df11 github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5 github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 - github.com/smartcontractkit/cre-sdk-go v1.9.0 - github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.10 + github.com/smartcontractkit/cre-sdk-go v1.10.0 + github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.11 github.com/smartcontractkit/mcms v0.41.1 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 76db702d..e969dcc5 100644 --- a/go.sum +++ b/go.sum @@ -1361,8 +1361,8 @@ github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0. github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:ATjAPIVJibHRcIfiG47rEQkUIOoYa6KDvWj3zwCAw6g= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d h1:AJy55QJ/pBhXkZjc7N+ATnWfxrcjq9BI9DmdtdjwDUQ= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:5JdppgngCOUS76p61zCinSCgOhPeYQ+OcDUuome5THQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260512230622-65f10f4cd305 h1:SH3vSzOujXLVEzh8sBL8tDQFI3sOvDOWyne33fMm94k= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520185909-e3aaa299df11 h1:soTVlFw+eVEHLtAL/ZeSR8HcFd6z3w4c4F9K+esr7vs= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520185909-e3aaa299df11/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= @@ -1401,10 +1401,10 @@ github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5/go.mod h1:pMBypeRoT8Nnb5gbkW6kIs+fBVsc/OzUx2eR9MtyFaw= github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 h1:G2TvBxwvzLMIU5hMPsQlVAY027mT1P/VTiWA6yegmpM= github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5/go.mod h1:8/a/Nlo4ZndgBEumXoE+gvBA5LH40BfDM+6SZa4Q89M= -github.com/smartcontractkit/cre-sdk-go v1.9.0 h1:E7Eum5XH3wZ+lNpr2j5biJRiP3lcK4NJuQbMRNXMmSE= -github.com/smartcontractkit/cre-sdk-go v1.9.0/go.mod h1:cnSxheEt2r9LhDswNv+EDyUr7GbOlKlTwHsCTa+FOtQ= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.10 h1:Z3xh3dN4NIIlGwOsBgrKi5WLBYfex8TyuW8tSDQd0BQ= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.10/go.mod h1:b1ZyaTde7PfEWweMe+rKwPe+auf0w9Dk48nge/JiIf0= +github.com/smartcontractkit/cre-sdk-go v1.10.0 h1:M8aLM+4450nMb0A3vQjzBIgqJtQJdLCSEgGfJPD1kjY= +github.com/smartcontractkit/cre-sdk-go v1.10.0/go.mod h1:vo7PQluFp5IWXz7p4vUxdfSLy5L8D45EMi5b3s4kBxY= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.11 h1:JO4fuKL6inYovJBgogF4SzAwTqC7VlK3OOv1gbr46Po= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.11/go.mod h1:ciOILa3cmdATsOyshnySWMMwU6M4z/FQYfx1fwm2e2s= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgHxTHuzJIF3Vj6LSMOnjhqKgRqYW+0MV2SExtCYL1Q= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index bcfa16c1..0dc5f5d4 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -58,8 +58,8 @@ const ( WorkflowLanguageWasm = "wasm" // SDK dependency versions (used by generate-bindings and go module init) - SdkVersion = "v1.9.0" - EVMCapabilitiesVersion = "v1.0.0-beta.10" + SdkVersion = "v1.10.0" + EVMCapabilitiesVersion = "v1.0.0-beta.11" HTTPCapabilitiesVersion = "v1.3.0" CronCapabilitiesVersion = "v1.3.0" diff --git a/internal/templaterepo/builtin/hello-world-ts/workflow/package.json b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json index a703a03b..2f24a363 100644 --- a/internal/templaterepo/builtin/hello-world-ts/workflow/package.json +++ b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.7.0" + "@chainlink/cre-sdk": "^1.8.0" }, "devDependencies": { "typescript": "5.9.3" From 80ceb2a8e4ab9d12bae4d19b247913053c1d3965 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Thu, 21 May 2026 12:56:01 +0100 Subject: [PATCH 09/29] Add input validation guard (#438) --- cmd/workflow/delete/delete.go | 4 ++++ cmd/workflow/deploy/deploy.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/cmd/workflow/delete/delete.go b/cmd/workflow/delete/delete.go index 928a1546..60db53c3 100644 --- a/cmd/workflow/delete/delete.go +++ b/cmd/workflow/delete/delete.go @@ -129,6 +129,10 @@ func (h *handler) ValidateInputs() error { } func (h *handler) Execute() error { + if !h.validated { + return fmt.Errorf("handler inputs not validated") + } + adapter, err := newRegistryDeleteStrategy(h.runtimeContext.ResolvedRegistry, h) if err != nil { return err diff --git a/cmd/workflow/deploy/deploy.go b/cmd/workflow/deploy/deploy.go index ba6c37c6..b525748b 100644 --- a/cmd/workflow/deploy/deploy.go +++ b/cmd/workflow/deploy/deploy.go @@ -205,6 +205,10 @@ func (h *handler) ValidateInputs() error { } func (h *handler) Execute(ctx context.Context) error { + if !h.validated { + return fmt.Errorf("handler inputs not validated") + } + deployAccess, err := h.credentials.GetDeploymentAccessStatus() if err != nil { return fmt.Errorf("failed to check deployment access: %w", err) From 14e29b8f5554d528c7527af9eb6579dd45043ba4 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Thu, 21 May 2026 13:21:42 +0100 Subject: [PATCH 10/29] Fix CheckWorkflowExists() (#439) --- cmd/workflow/deploy/deploy.go | 3 +++ cmd/workflow/deploy/private_registry_test.go | 11 +++++++---- cmd/workflow/deploy/registry_deploy_strategy.go | 6 ++++++ .../deploy/registry_deploy_strategy_onchain.go | 3 ++- .../deploy/registry_deploy_strategy_private.go | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/workflow/deploy/deploy.go b/cmd/workflow/deploy/deploy.go index b525748b..1ce740a9 100644 --- a/cmd/workflow/deploy/deploy.go +++ b/cmd/workflow/deploy/deploy.go @@ -241,6 +241,9 @@ func (h *handler) Execute(ctx context.Context) error { h.workflowArtifact.WorkflowID, ) if err != nil { + if errors.Is(err, errWorkflowUnchanged) { + return err + } return fmt.Errorf("failed to check if workflow exists: %w", err) } h.existingWorkflowStatus = existingStatus diff --git a/cmd/workflow/deploy/private_registry_test.go b/cmd/workflow/deploy/private_registry_test.go index 7ba1047b..714b2918 100644 --- a/cmd/workflow/deploy/private_registry_test.go +++ b/cmd/workflow/deploy/private_registry_test.go @@ -232,7 +232,7 @@ func TestCheckWorkflowExists_PrivateRegistry(t *testing.T) { wantErr: false, }, { - name: "found workflow with same ID returns error", + name: "found workflow with same ID returns unchanged error", serverStatus: http.StatusOK, response: map[string]any{ "data": map[string]any{ @@ -254,10 +254,10 @@ func TestCheckWorkflowExists_PrivateRegistry(t *testing.T) { }, }, workflowID: "00a2b96d2f06961c3e0cf6fbba5cfa30d3b577026de094e5202d5fc3e3aabb87", - wantExists: false, - wantStatus: nil, + wantExists: true, + wantStatus: uint8Ptr(0), wantErr: true, - errMsg: "workflow with id 00a2b96d2f06961c3e0cf6fbba5cfa30d3b577026de094e5202d5fc3e3aabb87 already exists", + errMsg: "workflow with id 00a2b96d2f06961c3e0cf6fbba5cfa30d3b577026de094e5202d5fc3e3aabb87 is already registered and unchanged; re-deployment skipped: workflow unchanged", }, { name: "not found returns no error and no status", @@ -320,6 +320,9 @@ func TestCheckWorkflowExists_PrivateRegistry(t *testing.T) { if tt.errMsg != "" { assert.Equal(t, tt.errMsg, err.Error()) } + if tt.wantExists { + assert.ErrorIs(t, err, errWorkflowUnchanged) + } } else { require.NoError(t, err) } diff --git a/cmd/workflow/deploy/registry_deploy_strategy.go b/cmd/workflow/deploy/registry_deploy_strategy.go index 434b62de..34a5c431 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy.go +++ b/cmd/workflow/deploy/registry_deploy_strategy.go @@ -11,6 +11,10 @@ import ( // re-running the command). var errDeployHalted = errors.New("deploy halted") +// errWorkflowUnchanged is a sentinel returned by CheckWorkflowExists when a +// registered workflow has the same ID as the artifact being deployed. +var errWorkflowUnchanged = errors.New("workflow unchanged") + // registryDeployStrategy encapsulates target-specific deployment logic. // The orchestrator calls these methods in a fixed sequence with common steps // (artifact upload) between RunPreDeployChecks and Upsert. @@ -22,6 +26,8 @@ type registryDeployStrategy interface { // CheckWorkflowExists returns whether a same-name workflow exists for this // registry target and includes the existing workflow status for updates. + // When the existing workflow ID matches workflowID, exists is true and + // errWorkflowUnchanged is returned to block redeployment of identical artifacts. CheckWorkflowExists(workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) // Upsert registers or updates the workflow in the target registry diff --git a/cmd/workflow/deploy/registry_deploy_strategy_onchain.go b/cmd/workflow/deploy/registry_deploy_strategy_onchain.go index ea6f1583..0d1943cd 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy_onchain.go +++ b/cmd/workflow/deploy/registry_deploy_strategy_onchain.go @@ -77,7 +77,8 @@ func (a *onchainRegistryDeployStrategy) CheckWorkflowExists(workflowOwner, workf return false, nil, err } if workflow.WorkflowId == [32]byte(common.Hex2Bytes(workflowID)) { - return false, nil, fmt.Errorf("workflow with id %s already exists", workflowID) + status := workflow.Status + return true, &status, fmt.Errorf("workflow with id %s is already registered and unchanged; re-deployment skipped: %w", workflowID, errWorkflowUnchanged) } if workflow.WorkflowName == workflowName { status := workflow.Status diff --git a/cmd/workflow/deploy/registry_deploy_strategy_private.go b/cmd/workflow/deploy/registry_deploy_strategy_private.go index dc14b21c..dee39315 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy_private.go +++ b/cmd/workflow/deploy/registry_deploy_strategy_private.go @@ -37,7 +37,7 @@ func (a *privateRegistryDeployStrategy) CheckWorkflowExists(_, workflowName, _, workflow, err := a.prc.GetWorkflowByName(workflowName) if err == nil { if workflow.WorkflowID == workflowID { - return false, nil, fmt.Errorf("workflow with id %s already exists", workflowID) + return true, offchainStatusToUint8(workflow.Status), fmt.Errorf("workflow with id %s is already registered and unchanged; re-deployment skipped: %w", workflowID, errWorkflowUnchanged) } return true, offchainStatusToUint8(workflow.Status), nil } From f11bf057c632128d0aef367d5240721dda21fc68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarc=C3=ADsio=20Zotelli=20Ferraz?= Date: Thu, 21 May 2026 11:04:40 -0300 Subject: [PATCH 11/29] Fix non-interactive cron trigger blocking in simulation (#442) --- cmd/workflow/simulate/simulate.go | 5 +-- cmd/workflow/simulate/simulate_test.go | 60 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/cmd/workflow/simulate/simulate.go b/cmd/workflow/simulate/simulate.go index 9f374991..7d0ca445 100644 --- a/cmd/workflow/simulate/simulate.go +++ b/cmd/workflow/simulate/simulate.go @@ -807,12 +807,9 @@ func makeBeforeStartNonInteractive(holder *TriggerInfoAndBeforeStart, inputs Inp case "cron-trigger@1.0.0": holder.TriggerFunc = func() error { skipWaitSignal := make(chan struct{}, 1) - if err := manualTriggerCaps.ManualCronTrigger.ManualTrigger(ctx, triggerRegistrationID, skipWaitSignal); err != nil { - return err - } // With cron schedule on non-interactive mode skipWaitSignal <- struct{}{} - return nil + return manualTriggerCaps.ManualCronTrigger.ManualTrigger(ctx, triggerRegistrationID, skipWaitSignal) } case "http-trigger@1.0.0-alpha": if strings.TrimSpace(inputs.HTTPPayload) == "" { diff --git a/cmd/workflow/simulate/simulate_test.go b/cmd/workflow/simulate/simulate_test.go index 1d24423a..847d7ae2 100644 --- a/cmd/workflow/simulate/simulate_test.go +++ b/cmd/workflow/simulate/simulate_test.go @@ -1,6 +1,7 @@ package simulate import ( + "context" "encoding/base64" "fmt" "io" @@ -8,11 +9,19 @@ import ( "path/filepath" rt "runtime" "testing" + "time" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + commoncaps "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + crontypedapi "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/triggers/cron" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + pb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/fakes" + simulator "github.com/smartcontractkit/chainlink/v2/core/services/workflows/cmd/cre/utils" + cmdcommon "github.com/smartcontractkit/cre-cli/cmd/common" "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/settings" @@ -438,6 +447,57 @@ func TestSimulateResolveInputs_InvocationDir(t *testing.T) { assert.Equal(t, invocationDir, inputs.InvocationDir) } +// TestNonInteractiveCronTriggerDoesNotBlockOnSchedule verifies that when the +// simulator runs in non-interactive mode with a cron trigger, TriggerFunc +// completes immediately without waiting for the actual cron schedule. +// +// The previous broken implementation sent skipWaitSignal *after* ManualTrigger +// returned, so ManualTrigger blocked in its select until the real cron job fired +// (up to 60 s). The fix pre-fills the channel before calling ManualTrigger. +func TestNonInteractiveCronTriggerDoesNotBlockOnSchedule(t *testing.T) { + t.Parallel() + + cronSvc, err := fakes.NewManualCronTriggerService(logger.Test(t)) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + require.NoError(t, cronSvc.Start(ctx)) + t.Cleanup(func() { _ = cronSvc.Close() }) + + // Register the trigger with the ID that makeBeforeStartNonInteractive will use. + triggerIndex := 0 + triggerRegistrationID := fmt.Sprintf("trigger_reg_1111111111111111111111111111111111111111111111111111111111111111_%d", triggerIndex) + _, capErr := cronSvc.RegisterTrigger(ctx, triggerRegistrationID, + commoncaps.RequestMetadata{WorkflowID: "test-workflow"}, + &crontypedapi.Config{Schedule: "* * * * *"}, + ) + require.Nil(t, capErr) + + holder := &TriggerInfoAndBeforeStart{} + inputs := Inputs{TriggerIndex: triggerIndex} + manualTriggers := &ManualTriggers{ManualCronTrigger: cronSvc} + + beforeStart := makeBeforeStartNonInteractive(holder, inputs, func() *ManualTriggers { + return manualTriggers + }) + + triggerSub := []*pb.TriggerSubscription{{Id: "cron-trigger@1.0.0"}} + beforeStart(ctx, simulator.RunnerConfig{}, nil, nil, triggerSub) + require.NotNil(t, holder.TriggerFunc) + + done := make(chan error, 1) + go func() { done <- holder.TriggerFunc() }() + + select { + case err := <-done: + require.NoError(t, err) + case <-time.After(3 * time.Second): + t.Fatal("TriggerFunc blocked waiting for cron schedule; skipWaitSignal must be sent before ManualTrigger is called") + } +} + func TestSimulateConfigFlagsMutuallyExclusive(t *testing.T) { t.Parallel() From 04dcbf55c3902469dad100558b5fe36bef50c6fa Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Thu, 21 May 2026 13:52:00 -0500 Subject: [PATCH 12/29] Add private-testnet-rhyolite support (#447) Added private-testnet-rhyolite support --- cmd/common/compile_test.go | 2 +- cmd/workflow/convert/convert_test.go | 2 +- .../simulate/chain/evm/supported_chains.go | 3 +++ go.mod | 8 ++++---- go.sum | 16 ++++++++-------- internal/constants/constants.go | 4 ++-- .../builtin/hello-world-ts/workflow/package.json | 2 +- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/cmd/common/compile_test.go b/cmd/common/compile_test.go index 4e811aec..fdc7dc3d 100644 --- a/cmd/common/compile_test.go +++ b/cmd/common/compile_test.go @@ -118,7 +118,7 @@ func TestCompileWorkflowToWasm_TS_Success(t *testing.T) { mainPath := filepath.Join(dir, "main.ts") require.NoError(t, os.WriteFile(mainPath, []byte(`export async function main() { return "ok"; } `), 0600)) - require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.8.0"}} + require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.9.0"}} `), 0600)) install := exec.Command("bun", "install") install.Dir = dir diff --git a/cmd/workflow/convert/convert_test.go b/cmd/workflow/convert/convert_test.go index 6be7c933..d15bf3fb 100644 --- a/cmd/workflow/convert/convert_test.go +++ b/cmd/workflow/convert/convert_test.go @@ -303,7 +303,7 @@ production-settings: ` require.NoError(t, os.WriteFile(workflowYAML, []byte(yamlContent), 0600)) require.NoError(t, os.WriteFile(mainTS, []byte("export default function run() { return Promise.resolve({ result: \"ok\" }); }\n"), 0600)) - require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.8.0"}}`), 0600)) + require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.9.0"}}`), 0600)) h := newHandler(nil) err := h.Execute(Inputs{WorkflowFolder: dir, Force: true}) diff --git a/cmd/workflow/simulate/chain/evm/supported_chains.go b/cmd/workflow/simulate/chain/evm/supported_chains.go index 25db7edb..dde4d4fe 100644 --- a/cmd/workflow/simulate/chain/evm/supported_chains.go +++ b/cmd/workflow/simulate/chain/evm/supported_chains.go @@ -138,4 +138,7 @@ var SupportedChains = []chain.ChainConfig{ // ADI {Selector: chainselectors.ADI_TESTNET.Selector, Forwarder: "0x9eF6468C5f37b976E57d52054c693269479A784d"}, {Selector: chainselectors.ADI_MAINNET.Selector, Forwarder: "0x6Aa382fb8762E1232936478DD9DbC04F637028f1"}, + + // Rhyolite (private testnet) + {Selector: chainselectors.PRIVATE_TESTNET_RHYOLITE.Selector, Forwarder: "0xBefF2190E6F56C108cD748844Bbd18D4a70F1E21"}, } diff --git a/go.mod b/go.mod index 677bbc68..2935f6b7 100644 --- a/go.mod +++ b/go.mod @@ -23,17 +23,17 @@ require ( github.com/machinebox/graphql v0.2.2 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.34.0 - github.com/smartcontractkit/chain-selectors v1.0.98 + github.com/smartcontractkit/chain-selectors v1.0.100 github.com/smartcontractkit/chainlink-common v0.11.2-0.20260421191147-d10b9943ac71 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260119171452-39c98c3b33cd - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520185909-e3aaa299df11 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42 github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5 github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 - github.com/smartcontractkit/cre-sdk-go v1.10.0 - github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.11 + github.com/smartcontractkit/cre-sdk-go v1.11.0 + github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12 github.com/smartcontractkit/mcms v0.41.1 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index e969dcc5..d258352c 100644 --- a/go.sum +++ b/go.sum @@ -1305,8 +1305,8 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9LsA7vTMPv+0n7ClhSFnZFAk= github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= -github.com/smartcontractkit/chain-selectors v1.0.98 h1:fuI7CQ1o5cX64eO4/LvwtfhdpGFH5vnsM/bFHRwEiww= -github.com/smartcontractkit/chain-selectors v1.0.98/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= +github.com/smartcontractkit/chain-selectors v1.0.100 h1:wpiSpmI/eFjY+wx/nPr5VuNF4hki0prIBMKEaQWn3g4= +github.com/smartcontractkit/chain-selectors v1.0.100/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260407161350-a86b1969da65 h1:b6+ZvoZxXSj7HywoZ0CfWtC6k47eBSaxNzc2LqtiXBA= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260407161350-a86b1969da65/go.mod h1:BbVsx2VcwSVWkd0C5TcAkQBnFaeYFnogJgUa9BUla18= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= @@ -1361,8 +1361,8 @@ github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0. github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:ATjAPIVJibHRcIfiG47rEQkUIOoYa6KDvWj3zwCAw6g= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d h1:AJy55QJ/pBhXkZjc7N+ATnWfxrcjq9BI9DmdtdjwDUQ= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:5JdppgngCOUS76p61zCinSCgOhPeYQ+OcDUuome5THQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520185909-e3aaa299df11 h1:soTVlFw+eVEHLtAL/ZeSR8HcFd6z3w4c4F9K+esr7vs= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520185909-e3aaa299df11/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42 h1:tyEgGOaYa7PBsmDIIvctOQPUc5c4Jtd/p4X1i96K+yo= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= @@ -1401,10 +1401,10 @@ github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5/go.mod h1:pMBypeRoT8Nnb5gbkW6kIs+fBVsc/OzUx2eR9MtyFaw= github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 h1:G2TvBxwvzLMIU5hMPsQlVAY027mT1P/VTiWA6yegmpM= github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5/go.mod h1:8/a/Nlo4ZndgBEumXoE+gvBA5LH40BfDM+6SZa4Q89M= -github.com/smartcontractkit/cre-sdk-go v1.10.0 h1:M8aLM+4450nMb0A3vQjzBIgqJtQJdLCSEgGfJPD1kjY= -github.com/smartcontractkit/cre-sdk-go v1.10.0/go.mod h1:vo7PQluFp5IWXz7p4vUxdfSLy5L8D45EMi5b3s4kBxY= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.11 h1:JO4fuKL6inYovJBgogF4SzAwTqC7VlK3OOv1gbr46Po= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.11/go.mod h1:ciOILa3cmdATsOyshnySWMMwU6M4z/FQYfx1fwm2e2s= +github.com/smartcontractkit/cre-sdk-go v1.11.0 h1:E3MG0j8O9qDv6lDz71HPD3/WRKh/PX2/hfxO1+9YL2w= +github.com/smartcontractkit/cre-sdk-go v1.11.0/go.mod h1:8SDE/e+eDAFpbRjRyKnIalUkQk9BcNbo2aLnda9BM48= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12 h1:cNUnDjtg88OXcmNQRbGgpRZ5/ZU5SqLp0wrHCxHn4M8= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12/go.mod h1:UNizO1xcv154THYM2hufcrWAp2rfuErC7Yeq8oJUrbk= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgHxTHuzJIF3Vj6LSMOnjhqKgRqYW+0MV2SExtCYL1Q= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 0dc5f5d4..065446e4 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -58,8 +58,8 @@ const ( WorkflowLanguageWasm = "wasm" // SDK dependency versions (used by generate-bindings and go module init) - SdkVersion = "v1.10.0" - EVMCapabilitiesVersion = "v1.0.0-beta.11" + SdkVersion = "v1.11.0" + EVMCapabilitiesVersion = "v1.0.0-beta.12" HTTPCapabilitiesVersion = "v1.3.0" CronCapabilitiesVersion = "v1.3.0" diff --git a/internal/templaterepo/builtin/hello-world-ts/workflow/package.json b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json index 2f24a363..e0c1970a 100644 --- a/internal/templaterepo/builtin/hello-world-ts/workflow/package.json +++ b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.8.0" + "@chainlink/cre-sdk": "^1.9.0" }, "devDependencies": { "typescript": "5.9.3" From e414fa07b50c86a583c33048919e20054e811b31 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Thu, 21 May 2026 19:55:14 +0100 Subject: [PATCH 13/29] Fix: Propagate ctx to GQL Methods (#436) * Fix: Propagate ctx to GQL Methods * lint --- cmd/workflow/activate/activate.go | 8 +++-- cmd/workflow/activate/activate_test.go | 5 +-- .../registry_activate_strategy_private.go | 4 +-- cmd/workflow/delete/delete.go | 8 +++-- .../registry_delete_strategy_private.go | 4 +-- cmd/workflow/deploy/deploy.go | 4 +++ cmd/workflow/deploy/private_registry_test.go | 2 ++ .../registry_deploy_strategy_private.go | 4 +-- cmd/workflow/pause/pause.go | 8 +++-- cmd/workflow/pause/pause_test.go | 5 +-- .../pause/registry_pause_strategy_private.go | 4 +-- .../privateregistryclient.go | 34 +++++++++---------- .../privateregistryclient_test.go | 28 ++++++++------- 13 files changed, 70 insertions(+), 48 deletions(-) diff --git a/cmd/workflow/activate/activate.go b/cmd/workflow/activate/activate.go index d250fd61..7e63cf1a 100644 --- a/cmd/workflow/activate/activate.go +++ b/cmd/workflow/activate/activate.go @@ -1,6 +1,7 @@ package activate import ( + "context" "fmt" "github.com/rs/zerolog" @@ -47,7 +48,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { if err := handler.ValidateInputs(); err != nil { return err } - return handler.Execute() + return handler.Execute(cmd.Context()) }, } @@ -66,6 +67,7 @@ type handler struct { runtimeContext *runtime.Context validated bool + execCtx context.Context } func newHandler(ctx *runtime.Context) *handler { @@ -110,7 +112,9 @@ func (h *handler) ValidateInputs() error { return nil } -func (h *handler) Execute() error { +func (h *handler) Execute(ctx context.Context) error { + h.execCtx = ctx + if !h.validated { return fmt.Errorf("handler inputs not validated") } diff --git a/cmd/workflow/activate/activate_test.go b/cmd/workflow/activate/activate_test.go index f94522aa..0e9e753a 100644 --- a/cmd/workflow/activate/activate_test.go +++ b/cmd/workflow/activate/activate_test.go @@ -1,6 +1,7 @@ package activate import ( + "context" "errors" "testing" @@ -35,7 +36,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) { } handler.validated = true - err := handler.Execute() + err := handler.Execute(context.Background()) require.Error(t, err) require.Contains(t, err.Error(), "missing required flags for --non-interactive mode") } @@ -62,7 +63,7 @@ func TestNonInteractive_WithYes_PassesGuard(t *testing.T) { } handler.validated = true - err := handler.Execute() + err := handler.Execute(context.Background()) // Guard passes; error comes from WRC (no matching workflow), not the guard require.Error(t, err) require.NotContains(t, err.Error(), "missing required flags for --non-interactive mode") diff --git a/cmd/workflow/activate/registry_activate_strategy_private.go b/cmd/workflow/activate/registry_activate_strategy_private.go index 1b1e1df9..b73bb4bd 100644 --- a/cmd/workflow/activate/registry_activate_strategy_private.go +++ b/cmd/workflow/activate/registry_activate_strategy_private.go @@ -32,7 +32,7 @@ func (a *privateRegistryActivateStrategy) Activate() error { ui.Dim(fmt.Sprintf("Fetching workflow to activate... Name=%s", workflowName)) - workflow, err := a.prc.GetWorkflowByName(workflowName) + workflow, err := a.prc.GetWorkflowByName(a.h.execCtx, workflowName) if err != nil { return fmt.Errorf("failed to get workflow: %w", err) } @@ -45,7 +45,7 @@ func (a *privateRegistryActivateStrategy) Activate() error { ui.Dim(fmt.Sprintf("Processing activation for workflow ID %s...", workflow.WorkflowID)) - result, err := a.prc.ActivateWorkflowInRegistry(workflow.WorkflowID) + result, err := a.prc.ActivateWorkflowInRegistry(a.h.execCtx, workflow.WorkflowID) if err != nil { return fmt.Errorf("failed to activate workflow in private registry: %w", err) } diff --git a/cmd/workflow/delete/delete.go b/cmd/workflow/delete/delete.go index 60db53c3..f15fb95c 100644 --- a/cmd/workflow/delete/delete.go +++ b/cmd/workflow/delete/delete.go @@ -1,6 +1,7 @@ package delete import ( + "context" "fmt" "io" @@ -44,7 +45,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { if err != nil { return err } - return handler.Execute() + return handler.Execute(cmd.Context()) }, } @@ -66,6 +67,7 @@ type handler struct { runtimeContext *runtime.Context validated bool + execCtx context.Context } func newHandler(ctx *runtime.Context, stdin io.Reader) *handler { @@ -128,11 +130,13 @@ func (h *handler) ValidateInputs() error { return nil } -func (h *handler) Execute() error { +func (h *handler) Execute(ctx context.Context) error { if !h.validated { return fmt.Errorf("handler inputs not validated") } + h.execCtx = ctx + adapter, err := newRegistryDeleteStrategy(h.runtimeContext.ResolvedRegistry, h) if err != nil { return err diff --git a/cmd/workflow/delete/registry_delete_strategy_private.go b/cmd/workflow/delete/registry_delete_strategy_private.go index 22f832bf..b5d52864 100644 --- a/cmd/workflow/delete/registry_delete_strategy_private.go +++ b/cmd/workflow/delete/registry_delete_strategy_private.go @@ -32,7 +32,7 @@ func (a *privateRegistryDeleteStrategy) FetchWorkflows() ([]WorkflowToDelete, er ui.Dim(fmt.Sprintf("Fetching workflow to delete... Name=%s", workflowName)) - workflow, err := a.prc.GetWorkflowByName(workflowName) + workflow, err := a.prc.GetWorkflowByName(a.h.execCtx, workflowName) if err != nil { return nil, fmt.Errorf("failed to get workflow: %w", err) } @@ -55,7 +55,7 @@ func (a *privateRegistryDeleteStrategy) DeleteWorkflows(workflows []WorkflowToDe for _, wf := range workflows { workflowID := wf.RawID.(string) - deletedID, err := a.prc.DeleteWorkflowInRegistry(workflowID) + deletedID, err := a.prc.DeleteWorkflowInRegistry(a.h.execCtx, workflowID) if err != nil { h.log.Error(). Err(err). diff --git a/cmd/workflow/deploy/deploy.go b/cmd/workflow/deploy/deploy.go index 1ce740a9..34f68f9b 100644 --- a/cmd/workflow/deploy/deploy.go +++ b/cmd/workflow/deploy/deploy.go @@ -73,6 +73,8 @@ type handler struct { // existingWorkflowStatus stores the status of an existing workflow when updating. // nil means this is a new workflow, otherwise it contains the current status (0=active, 1=paused). existingWorkflowStatus *uint8 + + execCtx context.Context } var defaultOutputPath = "./binary.wasm.br.b64" @@ -209,6 +211,8 @@ func (h *handler) Execute(ctx context.Context) error { return fmt.Errorf("handler inputs not validated") } + h.execCtx = ctx + deployAccess, err := h.credentials.GetDeploymentAccessStatus() if err != nil { return fmt.Errorf("failed to check deployment access: %w", err) diff --git a/cmd/workflow/deploy/private_registry_test.go b/cmd/workflow/deploy/private_registry_test.go index 714b2918..db0ed61b 100644 --- a/cmd/workflow/deploy/private_registry_test.go +++ b/cmd/workflow/deploy/private_registry_test.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "encoding/base64" "encoding/hex" "encoding/json" @@ -312,6 +313,7 @@ func TestCheckWorkflowExists_PrivateRegistry(t *testing.T) { defer gqlServer.Close() h.environmentSet.GraphQLURL = gqlServer.URL + h.execCtx = context.Background() strategy := newPrivateRegistryDeployStrategy(h) exists, status, err := strategy.CheckWorkflowExists("", "jnowak-workflow-test-v5", "", tt.workflowID) diff --git a/cmd/workflow/deploy/registry_deploy_strategy_private.go b/cmd/workflow/deploy/registry_deploy_strategy_private.go index dee39315..9909800e 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy_private.go +++ b/cmd/workflow/deploy/registry_deploy_strategy_private.go @@ -34,7 +34,7 @@ func (a *privateRegistryDeployStrategy) RunPreDeployChecks() error { func (a *privateRegistryDeployStrategy) CheckWorkflowExists(_, workflowName, _, workflowID string) (bool, *uint8, error) { a.ensureClient() - workflow, err := a.prc.GetWorkflowByName(workflowName) + workflow, err := a.prc.GetWorkflowByName(a.h.execCtx, workflowName) if err == nil { if workflow.WorkflowID == workflowID { return true, offchainStatusToUint8(workflow.Status), fmt.Errorf("workflow with id %s is already registered and unchanged; re-deployment skipped: %w", workflowID, errWorkflowUnchanged) @@ -57,7 +57,7 @@ func (a *privateRegistryDeployStrategy) Upsert() error { ui.Line() ui.Dim(fmt.Sprintf("Registering workflow in private registry (workflowID: %s)...", input.WorkflowID)) - result, err := a.prc.UpsertWorkflowInRegistry(input) + result, err := a.prc.UpsertWorkflowInRegistry(a.h.execCtx, input) if err != nil { return fmt.Errorf("failed to register workflow in private registry: %w", err) } diff --git a/cmd/workflow/pause/pause.go b/cmd/workflow/pause/pause.go index 19eaadf2..fe3382ea 100644 --- a/cmd/workflow/pause/pause.go +++ b/cmd/workflow/pause/pause.go @@ -1,6 +1,7 @@ package pause import ( + "context" "fmt" "github.com/rs/zerolog" @@ -46,7 +47,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { if err := handler.ValidateInputs(); err != nil { return err } - return handler.Execute() + return handler.Execute(cmd.Context()) }, } @@ -64,6 +65,7 @@ type handler struct { runtimeContext *runtime.Context validated bool + execCtx context.Context } func newHandler(ctx *runtime.Context) *handler { @@ -107,7 +109,9 @@ func (h *handler) ValidateInputs() error { return nil } -func (h *handler) Execute() error { +func (h *handler) Execute(ctx context.Context) error { + h.execCtx = ctx + if !h.validated { return fmt.Errorf("handler inputs not validated") } diff --git a/cmd/workflow/pause/pause_test.go b/cmd/workflow/pause/pause_test.go index 3af6e2f6..04ffa6cf 100644 --- a/cmd/workflow/pause/pause_test.go +++ b/cmd/workflow/pause/pause_test.go @@ -1,6 +1,7 @@ package pause import ( + "context" "errors" "testing" @@ -34,7 +35,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) { } h.validated = true - err := h.Execute() + err := h.Execute(context.Background()) require.Error(t, err) require.Contains(t, err.Error(), "missing required flags for --non-interactive mode") } @@ -60,7 +61,7 @@ func TestNonInteractive_WithYes_PassesGuard(t *testing.T) { } h.validated = true - err := h.Execute() + err := h.Execute(context.Background()) // Guard passes; error comes from WRC (no matching workflow), not the guard require.Error(t, err) require.NotContains(t, err.Error(), "missing required flags for --non-interactive mode") diff --git a/cmd/workflow/pause/registry_pause_strategy_private.go b/cmd/workflow/pause/registry_pause_strategy_private.go index f0f05534..3c583e07 100644 --- a/cmd/workflow/pause/registry_pause_strategy_private.go +++ b/cmd/workflow/pause/registry_pause_strategy_private.go @@ -32,7 +32,7 @@ func (a *privateRegistryPauseStrategy) Pause() error { ui.Dim(fmt.Sprintf("Fetching workflow to pause... Name=%s", workflowName)) - workflow, err := a.prc.GetWorkflowByName(workflowName) + workflow, err := a.prc.GetWorkflowByName(a.h.execCtx, workflowName) if err != nil { return fmt.Errorf("failed to get workflow: %w", err) } @@ -45,7 +45,7 @@ func (a *privateRegistryPauseStrategy) Pause() error { ui.Dim(fmt.Sprintf("Processing pause for workflow ID %s...", workflow.WorkflowID)) - result, err := a.prc.PauseWorkflowInRegistry(workflow.WorkflowID) + result, err := a.prc.PauseWorkflowInRegistry(a.h.execCtx, workflow.WorkflowID) if err != nil { return fmt.Errorf("failed to pause workflow in private registry: %w", err) } diff --git a/internal/client/privateregistryclient/privateregistryclient.go b/internal/client/privateregistryclient/privateregistryclient.go index 5e8f084b..e91b6f3b 100644 --- a/internal/client/privateregistryclient/privateregistryclient.go +++ b/internal/client/privateregistryclient/privateregistryclient.go @@ -29,8 +29,8 @@ func (c *Client) SetServiceTimeout(timeout time.Duration) { c.serviceTimeout = timeout } -func (c *Client) CreateServiceContextWithTimeout() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), c.serviceTimeout) //nolint:gosec // G118 -- cancel is deferred by callers +func (c *Client) CreateServiceContextWithTimeout(parent context.Context) (context.Context, context.CancelFunc) { + return context.WithTimeout(parent, c.serviceTimeout) //nolint:gosec // G118 -- cancel is deferred by callers } type OffchainWorkflow struct { @@ -131,7 +131,7 @@ type GetOffchainWorkflowByNameResponse struct { Workflow OffchainWorkflow `json:"workflow"` } -func (c *Client) GetWorkflowByName(workflowName string) (OffchainWorkflow, error) { +func (c *Client) GetWorkflowByName(ctx context.Context, workflowName string) (OffchainWorkflow, error) { if workflowName == "" { return OffchainWorkflow{}, fmt.Errorf("workflowName is required") } @@ -165,10 +165,10 @@ query GetOffchainWorkflowByName($request: GetOffchainWorkflowByNameRequest!) { GetOffchainWorkflowByName GetOffchainWorkflowByNameResponse `json:"getOffchainWorkflowByName"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + callCtx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() - if err := c.graphql.Execute(ctx, req, &container); err != nil { + if err := c.graphql.Execute(callCtx, req, &container); err != nil { return OffchainWorkflow{}, fmt.Errorf("get workflow by name in registry: %w", err) } @@ -178,7 +178,7 @@ query GetOffchainWorkflowByName($request: GetOffchainWorkflowByNameRequest!) { return container.GetOffchainWorkflowByName.Workflow, nil } -func (c *Client) UpsertWorkflowInRegistry(workflow OffchainWorkflowInput) (OffchainWorkflow, error) { +func (c *Client) UpsertWorkflowInRegistry(ctx context.Context, workflow OffchainWorkflowInput) (OffchainWorkflow, error) { if err := validateUpsertWorkflowInput(workflow); err != nil { return OffchainWorkflow{}, err } @@ -209,10 +209,10 @@ mutation UpsertOffchainWorkflow($request: UpsertOffchainWorkflowRequest!) { UpsertOffchainWorkflow UpsertOffchainWorkflowResponse `json:"upsertOffchainWorkflow"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + callCtx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() - if err := c.graphql.Execute(ctx, req, &container); err != nil { + if err := c.graphql.Execute(callCtx, req, &container); err != nil { return OffchainWorkflow{}, fmt.Errorf("upsert workflow in registry: %w", err) } @@ -222,7 +222,7 @@ mutation UpsertOffchainWorkflow($request: UpsertOffchainWorkflowRequest!) { return container.UpsertOffchainWorkflow.Workflow, nil } -func (c *Client) PauseWorkflowInRegistry(workflowID string) (OffchainWorkflow, error) { +func (c *Client) PauseWorkflowInRegistry(ctx context.Context, workflowID string) (OffchainWorkflow, error) { if workflowID == "" { return OffchainWorkflow{}, fmt.Errorf("workflowId is required") } @@ -253,10 +253,10 @@ mutation PauseOffchainWorkflow($request: PauseOffchainWorkflowRequest!) { PauseOffchainWorkflow PauseOffchainWorkflowResponse `json:"pauseOffchainWorkflow"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + callCtx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() - if err := c.graphql.Execute(ctx, req, &container); err != nil { + if err := c.graphql.Execute(callCtx, req, &container); err != nil { return OffchainWorkflow{}, fmt.Errorf("pause workflow in registry: %w", err) } @@ -266,7 +266,7 @@ mutation PauseOffchainWorkflow($request: PauseOffchainWorkflowRequest!) { return container.PauseOffchainWorkflow.Workflow, nil } -func (c *Client) ActivateWorkflowInRegistry(workflowID string) (OffchainWorkflow, error) { +func (c *Client) ActivateWorkflowInRegistry(ctx context.Context, workflowID string) (OffchainWorkflow, error) { if workflowID == "" { return OffchainWorkflow{}, fmt.Errorf("workflowId is required") } @@ -297,10 +297,10 @@ mutation ActivateOffchainWorkflow($request: ActivateOffchainWorkflowRequest!) { ActivateOffchainWorkflow ActivateOffchainWorkflowResponse `json:"activateOffchainWorkflow"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + callCtx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() - if err := c.graphql.Execute(ctx, req, &container); err != nil { + if err := c.graphql.Execute(callCtx, req, &container); err != nil { return OffchainWorkflow{}, fmt.Errorf("activate workflow in registry: %w", err) } @@ -310,7 +310,7 @@ mutation ActivateOffchainWorkflow($request: ActivateOffchainWorkflowRequest!) { return container.ActivateOffchainWorkflow.Workflow, nil } -func (c *Client) DeleteWorkflowInRegistry(workflowID string) (string, error) { +func (c *Client) DeleteWorkflowInRegistry(ctx context.Context, workflowID string) (string, error) { if workflowID == "" { return "", fmt.Errorf("workflowId is required") } @@ -329,10 +329,10 @@ mutation DeleteOffchainWorkflow($request: DeleteOffchainWorkflowRequest!) { DeleteOffchainWorkflow DeleteOffchainWorkflowResponse `json:"deleteOffchainWorkflow"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + callCtx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() - if err := c.graphql.Execute(ctx, req, &container); err != nil { + if err := c.graphql.Execute(callCtx, req, &container); err != nil { return "", fmt.Errorf("delete workflow in registry: %w", err) } diff --git a/internal/client/privateregistryclient/privateregistryclient_test.go b/internal/client/privateregistryclient/privateregistryclient_test.go index 578fbd06..8dc4c5db 100644 --- a/internal/client/privateregistryclient/privateregistryclient_test.go +++ b/internal/client/privateregistryclient/privateregistryclient_test.go @@ -1,6 +1,7 @@ package privateregistryclient import ( + "context" "encoding/json" "net/http" "net/http/httptest" @@ -147,7 +148,7 @@ func TestUpsertWorkflowInRegistry(t *testing.T) { configURL := "s3://config" tag := "v1" attributes := "{\"region\":\"us-east-1\"}" - result, err := client.UpsertWorkflowInRegistry(OffchainWorkflowInput{ + result, err := client.UpsertWorkflowInRegistry(context.Background(), OffchainWorkflowInput{ WorkflowID: "wf-123", Status: WorkflowStatusActive, WorkflowName: "registry-workflow", @@ -187,7 +188,7 @@ func TestUpsertWorkflowInRegistry_GQLError(t *testing.T) { defer srv.Close() client := newTestPrivateRegistryClient(t, srv.URL) - _, err := client.UpsertWorkflowInRegistry(OffchainWorkflowInput{ + _, err := client.UpsertWorkflowInRegistry(context.Background(), OffchainWorkflowInput{ WorkflowID: "wf-123", Status: WorkflowStatusActive, WorkflowName: "registry-workflow", @@ -236,7 +237,7 @@ func TestGetWorkflowByName(t *testing.T) { defer srv.Close() client := newTestPrivateRegistryClient(t, srv.URL) - result, err := client.GetWorkflowByName("registry-workflow") + result, err := client.GetWorkflowByName(context.Background(), "registry-workflow") require.NoError(t, err) assert.Contains(t, capturedQuery, "query GetOffchainWorkflowByName") @@ -262,7 +263,7 @@ func TestGetWorkflowByName_GQLError(t *testing.T) { defer srv.Close() client := newTestPrivateRegistryClient(t, srv.URL) - _, err := client.GetWorkflowByName("registry-workflow") + _, err := client.GetWorkflowByName(context.Background(), "registry-workflow") require.Error(t, err) assert.Contains(t, err.Error(), "get workflow by name in registry") assert.Contains(t, err.Error(), "cre api error: workflow not found") @@ -273,7 +274,7 @@ func TestGetWorkflowByName_EmptyName(t *testing.T) { logger := testutil.NewTestLogger() client := New(nil, logger) - _, err := client.GetWorkflowByName("") + _, err := client.GetWorkflowByName(context.Background(), "") require.EqualError(t, err, "workflowName is required") } @@ -306,7 +307,7 @@ func TestPauseWorkflowInRegistry(t *testing.T) { defer srv.Close() client := newTestPrivateRegistryClient(t, srv.URL) - result, err := client.PauseWorkflowInRegistry("wf-123") + result, err := client.PauseWorkflowInRegistry(context.Background(), "wf-123") require.NoError(t, err) assert.Equal(t, "wf-123", result.WorkflowID) assert.Equal(t, WorkflowStatusPaused, result.Status) @@ -344,7 +345,7 @@ func TestActivateWorkflowInRegistry(t *testing.T) { defer srv.Close() client := newTestPrivateRegistryClient(t, srv.URL) - result, err := client.ActivateWorkflowInRegistry("wf-123") + result, err := client.ActivateWorkflowInRegistry(context.Background(), "wf-123") require.NoError(t, err) assert.Equal(t, WorkflowStatusActive, result.Status) @@ -374,7 +375,7 @@ func TestDeleteWorkflowInRegistry(t *testing.T) { defer srv.Close() client := newTestPrivateRegistryClient(t, srv.URL) - deletedWorkflowID, err := client.DeleteWorkflowInRegistry("wf-123") + deletedWorkflowID, err := client.DeleteWorkflowInRegistry(context.Background(), "wf-123") require.NoError(t, err) assert.Equal(t, "wf-123", deletedWorkflowID) @@ -387,13 +388,13 @@ func TestWorkflowMutations_RequireWorkflowID(t *testing.T) { logger := testutil.NewTestLogger() client := New(nil, logger) - _, pauseErr := client.PauseWorkflowInRegistry("") + _, pauseErr := client.PauseWorkflowInRegistry(context.Background(), "") require.EqualError(t, pauseErr, "workflowId is required") - _, activateErr := client.ActivateWorkflowInRegistry("") + _, activateErr := client.ActivateWorkflowInRegistry(context.Background(), "") require.EqualError(t, activateErr, "workflowId is required") - _, deleteErr := client.DeleteWorkflowInRegistry("") + _, deleteErr := client.DeleteWorkflowInRegistry(context.Background(), "") require.EqualError(t, deleteErr, "workflowId is required") } @@ -414,10 +415,11 @@ func TestCreateServiceContextWithTimeout(t *testing.T) { client := New(nil, &logger) client.SetServiceTimeout(150 * time.Millisecond) - ctx, cancel := client.CreateServiceContextWithTimeout() + parent := context.Background() + callCtx, cancel := client.CreateServiceContextWithTimeout(parent) defer cancel() - deadline, ok := ctx.Deadline() + deadline, ok := callCtx.Deadline() require.True(t, ok) assert.WithinDuration(t, time.Now().Add(150*time.Millisecond), deadline, 100*time.Millisecond) } From b1aee3a7fd29ca1e6ac2b28f33dbb7249801a80d Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 13:46:31 +0400 Subject: [PATCH 14/29] Refactor: Standardize service timeout across clients and improve context handling (#448) - Set service timeout to 1 minute in PrivateRegistryClient, StorageClient, and WorkflowDataClient. - Introduce CreateServiceContextWithTimeout method in Validator and WorkflowDataClient for consistent context management. - Update ValidateCredentials method in Validator to use the new context handling approach. --- internal/authvalidation/validator.go | 16 ++++++++++++- .../privateregistryclient.go | 2 +- .../client/storageclient/storageclient.go | 4 ++-- .../workflowdataclient/workflowdataclient.go | 24 ++++++++++++++----- .../workflowdataclient_test.go | 16 +++++++++++++ 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/internal/authvalidation/validator.go b/internal/authvalidation/validator.go index 6448ca51..11660a9f 100644 --- a/internal/authvalidation/validator.go +++ b/internal/authvalidation/validator.go @@ -3,6 +3,7 @@ package authvalidation import ( "context" "fmt" + "time" "github.com/machinebox/graphql" "github.com/rs/zerolog" @@ -30,6 +31,7 @@ type ValidationResult struct { type Validator struct { gqlClient *graphqlclient.Client log *zerolog.Logger + timeout time.Duration } // NewValidator creates a new credential validator @@ -38,13 +40,25 @@ func NewValidator(creds *credentials.Credentials, environmentSet *environments.E return &Validator{ gqlClient: gqlClient, log: log, + timeout: time.Minute, } } +func (v *Validator) SetServiceTimeout(timeout time.Duration) { + v.timeout = timeout +} + +func (v *Validator) CreateServiceContextWithTimeout(parent context.Context) (context.Context, context.CancelFunc) { + return context.WithTimeout(parent, v.timeout) //nolint:gosec // G118 -- cancel is deferred by callers +} + // ValidateCredentials validates the provided credentials by making a lightweight GraphQL query // and returns organization info including the derived workflow owner. // The GraphQL client automatically handles token refresh if needed. func (v *Validator) ValidateCredentials(validationCtx context.Context, creds *credentials.Credentials) (*ValidationResult, error) { + ctx, cancel := v.CreateServiceContextWithTimeout(validationCtx) + defer cancel() + if creds == nil { return nil, fmt.Errorf("credentials not provided") } @@ -62,7 +76,7 @@ func (v *Validator) ValidateCredentials(validationCtx context.Context, creds *cr } `json:"getCreOrganizationInfo"` } - if err := v.gqlClient.Execute(validationCtx, req, &respEnvelope); err != nil { + if err := v.gqlClient.Execute(ctx, req, &respEnvelope); err != nil { return nil, fmt.Errorf("authentication failed: unable to retrieve organization info. Your account may not be fully set up yet — please try again in a few minutes: %w", err) } diff --git a/internal/client/privateregistryclient/privateregistryclient.go b/internal/client/privateregistryclient/privateregistryclient.go index e91b6f3b..6e9d9e9d 100644 --- a/internal/client/privateregistryclient/privateregistryclient.go +++ b/internal/client/privateregistryclient/privateregistryclient.go @@ -21,7 +21,7 @@ func New(gql *graphqlclient.Client, log *zerolog.Logger) *Client { return &Client{ graphql: gql, log: log, - serviceTimeout: 2 * time.Minute, + serviceTimeout: time.Minute, } } diff --git a/internal/client/storageclient/storageclient.go b/internal/client/storageclient/storageclient.go index 5eb9a829..de16d790 100644 --- a/internal/client/storageclient/storageclient.go +++ b/internal/client/storageclient/storageclient.go @@ -32,8 +32,8 @@ func New(graphql *graphqlclient.Client, workflowOwnerAddress string, log *zerolo graphql: graphql, workflowOwnerAddress: workflowOwnerAddress, log: log, - serviceTimeout: time.Minute * 2, - httpTimeout: time.Minute * 1, + serviceTimeout: time.Minute, + httpTimeout: time.Minute, } } diff --git a/internal/client/workflowdataclient/workflowdataclient.go b/internal/client/workflowdataclient/workflowdataclient.go index 275b16c0..5c9fb087 100644 --- a/internal/client/workflowdataclient/workflowdataclient.go +++ b/internal/client/workflowdataclient/workflowdataclient.go @@ -3,6 +3,7 @@ package workflowdataclient import ( "context" "fmt" + "time" "github.com/machinebox/graphql" "github.com/rs/zerolog" @@ -25,11 +26,20 @@ type Workflow struct { type Client struct { graphql *graphqlclient.Client log *zerolog.Logger + timeout time.Duration } // New creates a WorkflowDataClient backed by the provided GraphQL client. func New(gql *graphqlclient.Client, log *zerolog.Logger) *Client { - return &Client{graphql: gql, log: log} + return &Client{graphql: gql, log: log, timeout: time.Minute} +} + +func (c *Client) SetServiceTimeout(timeout time.Duration) { + c.timeout = timeout +} + +func (c *Client) CreateServiceContextWithTimeout(parent context.Context) (context.Context, context.CancelFunc) { + return context.WithTimeout(parent, c.timeout) //nolint:gosec // G118 -- cancel is deferred by callers } const listWorkflowsQuery = ` @@ -63,17 +73,19 @@ type listWorkflowsEnvelope struct { } // ListAll pages through the ListWorkflows query and returns all workflows. -func (c *Client) ListAll(ctx context.Context, pageSize int) ([]Workflow, error) { - return c.list(ctx, pageSize, "") +func (c *Client) ListAll(parent context.Context, pageSize int) ([]Workflow, error) { + return c.list(parent, pageSize, "") } // SearchByName pages through the ListWorkflows query with the given search // filter (server-side contains match on workflow name). -func (c *Client) SearchByName(ctx context.Context, name string, pageSize int) ([]Workflow, error) { - return c.list(ctx, pageSize, name) +func (c *Client) SearchByName(parent context.Context, name string, pageSize int) ([]Workflow, error) { + return c.list(parent, pageSize, name) } -func (c *Client) list(ctx context.Context, pageSize int, search string) ([]Workflow, error) { +func (c *Client) list(parent context.Context, pageSize int, search string) ([]Workflow, error) { + ctx, cancel := c.CreateServiceContextWithTimeout(parent) + defer cancel() if pageSize <= 0 { pageSize = DefaultPageSize } diff --git a/internal/client/workflowdataclient/workflowdataclient_test.go b/internal/client/workflowdataclient/workflowdataclient_test.go index 503c244f..370d7874 100644 --- a/internal/client/workflowdataclient/workflowdataclient_test.go +++ b/internal/client/workflowdataclient/workflowdataclient_test.go @@ -7,7 +7,9 @@ import ( "net/http/httptest" "sync/atomic" "testing" + "time" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,6 +31,20 @@ func newTestClient(t *testing.T, serverURL string) *Client { return New(gql, logger) } +func TestCreateServiceContextWithTimeout(t *testing.T) { + logger := zerolog.Nop() + client := New(nil, &logger) + client.SetServiceTimeout(150 * time.Millisecond) + + parent := context.Background() + callCtx, cancel := client.CreateServiceContextWithTimeout(parent) + defer cancel() + + deadline, ok := callCtx.Deadline() + require.True(t, ok) + assert.WithinDuration(t, time.Now().Add(150*time.Millisecond), deadline, 100*time.Millisecond) +} + func TestListAll_SinglePage(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") From fe35aa09273dd641aa062999cc098b8cdf305f6a Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 13:46:48 +0400 Subject: [PATCH 15/29] Refactor: Pass context to login flow and GQL methods (#449) Updated the login flow to accept a context parameter, ensuring proper context propagation throughout the login process. This change includes modifications to the `Run` and `execute` methods in the login package, as well as adjustments in the tenant context fetching to utilize the provided context for GQL requests. --- cmd/login/login.go | 16 ++++++++-------- cmd/root.go | 2 +- internal/tenantctx/tenantctx.go | 4 ++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cmd/login/login.go b/cmd/login/login.go index 045a1c3e..5baa5ea8 100644 --- a/cmd/login/login.go +++ b/cmd/login/login.go @@ -63,7 +63,7 @@ it automatically — no login needed.`, return fmt.Errorf("login is not supported in non-interactive mode, use CRE_API_KEY instead") } h := newHandler(runtimeCtx) - return h.execute() + return h.execute(cmd.Context()) }, } @@ -72,9 +72,9 @@ it automatically — no login needed.`, // Run executes the login flow directly without going through Cobra. // This is useful for prompting login from other commands when auth is required. -func Run(runtimeCtx *runtime.Context) error { +func Run(ctx context.Context, runtimeCtx *runtime.Context) error { h := newHandler(runtimeCtx) - return h.execute() + return h.execute(ctx) } type handler struct { @@ -96,7 +96,7 @@ func newHandler(ctx *runtime.Context) *handler { } } -func (h *handler) execute() error { +func (h *handler) execute(ctx context.Context) error { // Welcome message (no spinner yet) ui.Title("CRE Login") ui.Line() @@ -111,7 +111,7 @@ func (h *handler) execute() error { // Use spinner for the token exchange h.spinner.Start("Exchanging authorization code...") - tokenSet, err := oauth.ExchangeAuthorizationCode(context.Background(), nil, h.environmentSet, code, h.lastPKCEVerifier, "", "") + tokenSet, err := oauth.ExchangeAuthorizationCode(ctx, nil, h.environmentSet, code, h.lastPKCEVerifier, "", "") if err != nil { h.spinner.StopAll() h.log.Error().Err(err).Msg("code exchange failed") @@ -126,7 +126,7 @@ func (h *handler) execute() error { } h.spinner.Update("Fetching user context...") - if err := h.fetchTenantConfig(tokenSet); err != nil { + if err := h.fetchTenantConfig(ctx, tokenSet); err != nil { h.log.Debug().Err(err).Msgf("failed to fetch user context — %s not written", tenantctx.ContextFile) } @@ -295,7 +295,7 @@ func (h *handler) buildAuthURL(codeChallenge, state string) string { return h.environmentSet.AuthBase + constants.AuthAuthorizePath + "?" + params.Encode() } -func (h *handler) fetchTenantConfig(tokenSet *credentials.CreLoginTokenSet) error { +func (h *handler) fetchTenantConfig(ctx context.Context, tokenSet *credentials.CreLoginTokenSet) error { creds := &credentials.Credentials{ Tokens: tokenSet, AuthType: credentials.AuthTypeBearer, @@ -307,5 +307,5 @@ func (h *handler) fetchTenantConfig(tokenSet *credentials.CreLoginTokenSet) erro envName = environments.DefaultEnv } - return tenantctx.FetchAndWriteContext(context.Background(), gqlClient, envName, h.log) + return tenantctx.FetchAndWriteContext(ctx, gqlClient, envName, h.log) } diff --git a/cmd/root.go b/cmd/root.go index a7306216..aa149602 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -221,7 +221,7 @@ func newRootCommand() *cobra.Command { // Run login flow ui.Line() - if loginErr := login.Run(runtimeContext); loginErr != nil { + if loginErr := login.Run(cmd.Context(), runtimeContext); loginErr != nil { return fmt.Errorf("login failed: %w", loginErr) } diff --git a/internal/tenantctx/tenantctx.go b/internal/tenantctx/tenantctx.go index ea20230e..5fcd7b6e 100644 --- a/internal/tenantctx/tenantctx.go +++ b/internal/tenantctx/tenantctx.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/machinebox/graphql" "github.com/rs/zerolog" @@ -91,6 +92,9 @@ const getTenantConfigQuery = `query GetTenantConfig { // FetchAndWriteContext fetches the user context from the service // and writes the registry manifest to ~/.cre/. func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client, envName string, log *zerolog.Logger) error { + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + req := graphql.NewRequest(getTenantConfigQuery) var resp getTenantConfigResponse From ac34575bd6d4ba16d8df313f88457a9075d3795e Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 14:16:45 +0400 Subject: [PATCH 16/29] Refactor workflow fetching logic to a common function (#451) - Removed duplicate workflow fetching code from multiple files. - Introduced `FetchAllWorkflowsByOwnerAndName` in `workflow_list.go` to streamline workflow retrieval. - Updated `registry_activate_strategy_onchain.go`, `registry_delete_strategy_onchain.go`, and `registry_pause_strategy_onchain.go` to use the new common function. --- .../registry_activate_strategy_onchain.go | 5 +- cmd/workflow/common/workflow_list.go | 48 +++++++++++++++++++ .../registry_delete_strategy_onchain.go | 4 +- .../pause/registry_pause_strategy_onchain.go | 40 +--------------- 4 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 cmd/workflow/common/workflow_list.go diff --git a/cmd/workflow/activate/registry_activate_strategy_onchain.go b/cmd/workflow/activate/registry_activate_strategy_onchain.go index ea9df4dd..a6878606 100644 --- a/cmd/workflow/activate/registry_activate_strategy_onchain.go +++ b/cmd/workflow/activate/registry_activate_strategy_onchain.go @@ -3,7 +3,6 @@ package activate import ( "encoding/hex" "fmt" - "math/big" "sort" "sync" "time" @@ -12,6 +11,7 @@ import ( "github.com/smartcontractkit/cre-cli/cmd/client" cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common" + workflowcommon "github.com/smartcontractkit/cre-cli/cmd/workflow/common" "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/types" "github.com/smartcontractkit/cre-cli/internal/ui" @@ -58,8 +58,7 @@ func (a *onchainRegistryActivateStrategy) Activate() error { ownerAddr := common.HexToAddress(workflowOwner) - const pageLimit = 200 - workflows, err := a.wrc.GetWorkflowListByOwnerAndName(ownerAddr, workflowName, big.NewInt(0), big.NewInt(pageLimit)) + workflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(a.wrc, ownerAddr, workflowName) if err != nil { return fmt.Errorf("failed to get workflow list: %w", err) } diff --git a/cmd/workflow/common/workflow_list.go b/cmd/workflow/common/workflow_list.go new file mode 100644 index 00000000..f51d9485 --- /dev/null +++ b/cmd/workflow/common/workflow_list.go @@ -0,0 +1,48 @@ +package common + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + + workflow_registry_v2_wrapper "github.com/smartcontractkit/chainlink-evm/gethwrappers/workflow/generated/workflow_registry_wrapper_v2" +) + +const workflowListPageSize = int64(200) + +// WorkflowListByOwnerAndNameClient fetches workflow metadata pages from the registry. +type WorkflowListByOwnerAndNameClient interface { + GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) +} + +// FetchAllWorkflowsByOwnerAndName returns every workflow version for owner+name, paginating until exhausted. +func FetchAllWorkflowsByOwnerAndName( + wrc WorkflowListByOwnerAndNameClient, + owner common.Address, + name string, +) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { + var ( + start = big.NewInt(0) + limit = big.NewInt(workflowListPageSize) + workflows = make([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, 0, workflowListPageSize) + ) + + for { + list, err := wrc.GetWorkflowListByOwnerAndName(owner, name, start, limit) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + + workflows = append(workflows, list...) + + start = big.NewInt(start.Int64() + int64(len(list))) + if int64(len(list)) < workflowListPageSize { + break + } + } + + return workflows, nil +} diff --git a/cmd/workflow/delete/registry_delete_strategy_onchain.go b/cmd/workflow/delete/registry_delete_strategy_onchain.go index 1dd7335b..5cd3a9b5 100644 --- a/cmd/workflow/delete/registry_delete_strategy_onchain.go +++ b/cmd/workflow/delete/registry_delete_strategy_onchain.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "errors" "fmt" - "math/big" "sync" "time" @@ -12,6 +11,7 @@ import ( "github.com/smartcontractkit/cre-cli/cmd/client" cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common" + workflowcommon "github.com/smartcontractkit/cre-cli/cmd/workflow/common" "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/types" "github.com/smartcontractkit/cre-cli/internal/ui" @@ -56,7 +56,7 @@ func (a *onchainRegistryDeleteStrategy) FetchWorkflows() ([]WorkflowToDelete, er workflowName := h.inputs.WorkflowName workflowOwner := common.HexToAddress(h.inputs.WorkflowOwner) - allWorkflows, err := a.wrc.GetWorkflowListByOwnerAndName(workflowOwner, workflowName, big.NewInt(0), big.NewInt(100)) + allWorkflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(a.wrc, workflowOwner, workflowName) if err != nil { return nil, fmt.Errorf("failed to get workflow list: %w", err) } diff --git a/cmd/workflow/pause/registry_pause_strategy_onchain.go b/cmd/workflow/pause/registry_pause_strategy_onchain.go index f038d6aa..1003b739 100644 --- a/cmd/workflow/pause/registry_pause_strategy_onchain.go +++ b/cmd/workflow/pause/registry_pause_strategy_onchain.go @@ -3,16 +3,14 @@ package pause import ( "encoding/hex" "fmt" - "math/big" "sync" "time" "github.com/ethereum/go-ethereum/common" - workflow_registry_v2_wrapper "github.com/smartcontractkit/chainlink-evm/gethwrappers/workflow/generated/workflow_registry_wrapper_v2" - "github.com/smartcontractkit/cre-cli/cmd/client" cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common" + workflowcommon "github.com/smartcontractkit/cre-cli/cmd/workflow/common" "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/types" "github.com/smartcontractkit/cre-cli/internal/ui" @@ -59,7 +57,7 @@ func (a *onchainRegistryPauseStrategy) Pause() error { ui.Dim(fmt.Sprintf("Fetching workflows to pause... Name=%s, Owner=%s", workflowName, workflowOwner.Hex())) - workflows, err := fetchAllWorkflows(a.wrc, workflowOwner, workflowName) + workflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(a.wrc, workflowOwner, workflowName) if err != nil { return fmt.Errorf("failed to list workflows: %w", err) } @@ -161,37 +159,3 @@ func (a *onchainRegistryPauseStrategy) Pause() error { } return nil } - -func fetchAllWorkflows( - wrc interface { - GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) - }, - owner common.Address, - name string, -) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { - const pageSize = int64(200) - var ( - start = big.NewInt(0) - limit = big.NewInt(pageSize) - workflows = make([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, 0, pageSize) - ) - - for { - list, err := wrc.GetWorkflowListByOwnerAndName(owner, name, start, limit) - if err != nil { - return nil, err - } - if len(list) == 0 { - break - } - - workflows = append(workflows, list...) - - start = big.NewInt(start.Int64() + int64(len(list))) - if int64(len(list)) < pageSize { - break - } - } - - return workflows, nil -} From 3fa2f5982589559f98ff6cd5182205fdf231e928 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Fri, 22 May 2026 15:08:23 +0100 Subject: [PATCH 17/29] Support updated Oauth secrets ownership model (#443) * Remove org owned feature flag * Support udpated Oauth secrets ownership model * Mod tidy --- cmd/secrets/common/browser_flow.go | 17 +- cmd/secrets/common/handler.go | 117 ++--- cmd/secrets/common/handler_test.go | 20 +- cmd/secrets/delete/delete.go | 2 +- cmd/secrets/list/list.go | 2 +- go.mod | 194 ++++---- go.sum | 772 ++++++++--------------------- internal/settings/cld_settings.go | 4 +- 8 files changed, 375 insertions(+), 753 deletions(-) diff --git a/cmd/secrets/common/browser_flow.go b/cmd/secrets/common/browser_flow.go index 7cc5022c..8fe264af 100644 --- a/cmd/secrets/common/browser_flow.go +++ b/cmd/secrets/common/browser_flow.go @@ -66,14 +66,15 @@ func (h *Handler) executeBrowserUpsert(ctx context.Context, inputs UpsertSecrets if h.Credentials.AuthType == credentials.AuthTypeApiKey { return fmt.Errorf("this sign-in flow requires an interactive login; API keys are not supported") } - orgID := h.Credentials.OrgID - if orgID == "" { - return fmt.Errorf("organization information is missing from your session; sign in again or use --secrets-auth=onchain") + + owner, err := h.ResolveVaultIdentifierOwnerForAuth(SecretsAuthBrowser) + if err != nil { + return err } ui.Dim("Using your account to authorize vault access for your organization...") - encSecrets, err := h.EncryptSecretsForBrowserOrg(inputs, orgID) + encSecrets, err := h.EncryptSecrets(inputs, owner) if err != nil { return fmt.Errorf("failed to encrypt secrets: %w", err) } @@ -127,12 +128,12 @@ func (h *Handler) executeBrowserUpsert(ctx context.Context, inputs UpsertSecrets return fmt.Errorf("unsupported method %q (expected %q or %q)", method, vaulttypes.MethodSecretsCreate, vaulttypes.MethodSecretsUpdate) } - return h.ExecuteBrowserVaultAuthorization(ctx, method, digest, requestBody) + return h.ExecuteBrowserVaultAuthorization(ctx, method, digest, requestBody, owner) } // ExecuteBrowserVaultAuthorization completes platform OAuth for a vault JSON-RPC digest (create/update/delete/list), // then POSTs the same request body to the gateway with the vault JWT in the Authorization header. -func (h *Handler) ExecuteBrowserVaultAuthorization(ctx context.Context, method string, digest [32]byte, requestBody []byte) error { +func (h *Handler) ExecuteBrowserVaultAuthorization(ctx context.Context, method string, digest [32]byte, requestBody []byte, workflowOwner string) error { if h.Credentials.AuthType == credentials.AuthTypeApiKey { return fmt.Errorf("this sign-in flow requires an interactive login; API keys are not supported") } @@ -158,8 +159,8 @@ func (h *Handler) ExecuteBrowserVaultAuthorization(ctx context.Context, method s "requestDigest": digestHexString(digest), "permission": perm, } - // Optional: bind authorization to workflow owner when configured (omit if unset). - if w := strings.TrimSpace(h.OwnerAddress); w != "" { + // Bind authorization to the JWT-derived workflow owner so digests align with gateway validation. + if w := strings.TrimSpace(workflowOwner); w != "" { reqVars["workflowOwnerAddress"] = w } gqlReq.Var("request", reqVars) diff --git a/cmd/secrets/common/handler.go b/cmd/secrets/common/handler.go index cbf057e6..12ff42eb 100644 --- a/cmd/secrets/common/handler.go +++ b/cmd/secrets/common/handler.go @@ -3,7 +3,6 @@ package common import ( "context" "crypto/ecdsa" - "crypto/sha256" "encoding/hex" "encoding/json" "fmt" @@ -35,6 +34,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/constants" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" + "github.com/smartcontractkit/cre-cli/internal/ethkeys" "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/types" @@ -57,16 +57,17 @@ type SecretsYamlConfig struct { } type Handler struct { - Log *zerolog.Logger - ClientFactory client.Factory - SecretsFilePath string - PrivateKey *ecdsa.PrivateKey - OwnerAddress string - EnvironmentSet *environments.EnvironmentSet - Gw GatewayClient - Wrc *client.WorkflowRegistryV2Client - Credentials *credentials.Credentials - Settings *settings.Settings + Log *zerolog.Logger + ClientFactory client.Factory + SecretsFilePath string + PrivateKey *ecdsa.PrivateKey + OwnerAddress string + DerivedWorkflowOwner string + EnvironmentSet *environments.EnvironmentSet + Gw GatewayClient + Wrc *client.WorkflowRegistryV2Client + Credentials *credentials.Credentials + Settings *settings.Settings } // NewHandler creates a new handler instance. @@ -87,14 +88,15 @@ func NewHandler(ctx *runtime.Context, secretsFilePath, secretsAuth string) (*Han } h := &Handler{ - Log: ctx.Logger, - ClientFactory: ctx.ClientFactory, - SecretsFilePath: secretsFilePath, - PrivateKey: pk, - OwnerAddress: ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, - EnvironmentSet: ctx.EnvironmentSet, - Credentials: ctx.Credentials, - Settings: ctx.Settings, + Log: ctx.Logger, + ClientFactory: ctx.ClientFactory, + SecretsFilePath: secretsFilePath, + PrivateKey: pk, + OwnerAddress: ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, + DerivedWorkflowOwner: ctx.DerivedWorkflowOwner, + EnvironmentSet: ctx.EnvironmentSet, + Credentials: ctx.Credentials, + Settings: ctx.Settings, } h.Gw = &HTTPClient{URL: h.EnvironmentSet.GatewayURL, Client: &http.Client{Timeout: 90 * time.Second}} @@ -285,74 +287,44 @@ func (h *Handler) ResolveEffectiveOwner() (string, error) { return common.HexToAddress(h.OwnerAddress).Hex(), nil } -// ResolveVaultIdentifierOwnerForAuth returns the owner string used in vault JSON-RPC payloads -// (SecretIdentifier.Owner and list request Owner). Browser auth always uses the signed-in -// organization ID so digests and identifiers align with JWT AuthorizedOwner() on the gateway; -// onchain auth uses ResolveEffectiveOwner() (linked workflow owner address). +// ResolveVaultIdentifierOwnerForAuth returns the owner used in vault JSON-RPC payloads +// (SecretIdentifier.Owner, list Owner, TDH2 labels). Onchain auth uses the linked EOA from +// settings; browser auth uses DerivedWorkflowOwner from runtime.Context (getCreOrganizationInfo at login). func (h *Handler) ResolveVaultIdentifierOwnerForAuth(secretsAuth string) (string, error) { - if IsBrowserFlow(secretsAuth) { - if h.Credentials == nil { - return "", fmt.Errorf("organization information is missing from your session; sign in again or use --secrets-auth=onchain") - } - if h.Credentials.AuthType == credentials.AuthTypeApiKey { - return "", fmt.Errorf("this sign-in flow requires an interactive login; API keys are not supported") - } - if h.Credentials.OrgID == "" { - return "", fmt.Errorf("organization information is missing from your session; sign in again or use --secrets-auth=onchain") - } - return h.Credentials.OrgID, nil + if !IsBrowserFlow(secretsAuth) { + return h.ResolveEffectiveOwner() } - return h.ResolveEffectiveOwner() -} - -// EncryptSecrets takes the raw secrets and encrypts them for the owner-key (onchain) flow. -// TDH2 label is the workflow owner address left-padded to 32 bytes; SecretIdentifier.Owner is the same hex address string. -func (h *Handler) EncryptSecrets(rawSecrets UpsertSecretsInputs) ([]*vault.EncryptedSecret, error) { - pubKeyHex, err := h.fetchVaultMasterPublicKeyHex() - if err != nil { - return nil, err + if h.Credentials == nil { + return "", fmt.Errorf("organization information is missing from your session; sign in again or use --secrets-auth=onchain") } - - encryptedSecrets := make([]*vault.EncryptedSecret, 0, len(rawSecrets)) - for _, item := range rawSecrets { - cipherHex, err := EncryptSecret(item.Value, pubKeyHex, h.OwnerAddress) - if err != nil { - return nil, fmt.Errorf("failed to encrypt secret (key=%s ns=%s): %w", item.ID, item.Namespace, err) - } - secID := &vault.SecretIdentifier{ - Key: item.ID, - Namespace: item.Namespace, - Owner: h.OwnerAddress, - } - encryptedSecrets = append(encryptedSecrets, &vault.EncryptedSecret{ - Id: secID, - EncryptedValue: cipherHex, - }) + if h.Credentials.AuthType == credentials.AuthTypeApiKey { + return "", fmt.Errorf("this sign-in flow requires an interactive login; API keys are not supported") } - return encryptedSecrets, nil + owner := strings.TrimSpace(h.DerivedWorkflowOwner) + if owner == "" { + return "", fmt.Errorf("derived workflow owner is not available; sign in again with cre login") + } + return ethkeys.FormatWorkflowOwnerAddress(owner) } -// EncryptSecretsForBrowserOrg encrypts secrets scoped to the signed-in organization (interactive sign-in flow). -// TDH2 label is SHA256(orgID); SecretIdentifier.Owner is the org id string. This is a separate binding from the -// owner-key path (EOA left-padded label + workflow owner address); both remain supported via their respective entrypoints. -func (h *Handler) EncryptSecretsForBrowserOrg(rawSecrets UpsertSecretsInputs, orgID string) ([]*vault.EncryptedSecret, error) { +// EncryptSecrets encrypts secrets for the given workflow owner address. +// TDH2 label is the workflow owner address left-padded to 32 bytes; SecretIdentifier.Owner is the same hex address string. +func (h *Handler) EncryptSecrets(rawSecrets UpsertSecretsInputs, owner string) ([]*vault.EncryptedSecret, error) { pubKeyHex, err := h.fetchVaultMasterPublicKeyHex() if err != nil { return nil, err } - label := sha256.Sum256([]byte(orgID)) - encryptedSecrets := make([]*vault.EncryptedSecret, 0, len(rawSecrets)) for _, item := range rawSecrets { - cipherHex, err := encryptSecretWithLabel(item.Value, pubKeyHex, label) + cipherHex, err := EncryptSecret(item.Value, pubKeyHex, owner) if err != nil { return nil, fmt.Errorf("failed to encrypt secret (key=%s ns=%s): %w", item.ID, item.Namespace, err) } secID := &vault.SecretIdentifier{ Key: item.ID, Namespace: item.Namespace, - Owner: orgID, + Owner: owner, } encryptedSecrets = append(encryptedSecrets, &vault.EncryptedSecret{ Id: secID, @@ -448,8 +420,13 @@ func (h *Handler) Execute( return err } + owner, err := h.ResolveVaultIdentifierOwnerForAuth(secretsAuth) + if err != nil { + return err + } + // Build from YAML inputs - encSecrets, err := h.EncryptSecrets(inputs) + encSecrets, err := h.EncryptSecrets(inputs, owner) if err != nil { return fmt.Errorf("failed to encrypt secrets: %w", err) } @@ -499,7 +476,7 @@ func (h *Handler) Execute( return fmt.Errorf("unsupported method %q (expected %q or %q)", method, vaulttypes.MethodSecretsCreate, vaulttypes.MethodSecretsUpdate) } - ownerAddr := common.HexToAddress(h.OwnerAddress) + ownerAddr := common.HexToAddress(owner) allowlisted, err := h.Wrc.IsRequestAllowlisted(ownerAddr, digest) if err != nil { diff --git a/cmd/secrets/common/handler_test.go b/cmd/secrets/common/handler_test.go index 21cc51fc..cd58c70f 100644 --- a/cmd/secrets/common/handler_test.go +++ b/cmd/secrets/common/handler_test.go @@ -71,7 +71,7 @@ func TestEncryptSecrets(t *testing.T) { {ID: "test-secret-2", Value: "another-value", Namespace: "ns2"}, } - enc, err := h.EncryptSecrets(raw) + enc, err := h.EncryptSecrets(raw, "0xabc") require.NoError(t, err) require.Len(t, enc, 2) @@ -100,7 +100,7 @@ func TestEncryptSecrets(t *testing.T) { }, } - enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: "v", Namespace: "n"}}) + enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: "v", Namespace: "n"}}, "0xabc") require.Error(t, err) require.Nil(t, enc) require.Contains(t, err.Error(), "gateway POST failed") @@ -126,7 +126,7 @@ func TestEncryptSecrets(t *testing.T) { }, } - enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: "v", Namespace: "n"}}) + enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: "v", Namespace: "n"}}, "0xabc") require.Error(t, err) require.Nil(t, enc) require.Contains(t, err.Error(), "vault public key fetch error") @@ -163,15 +163,15 @@ func TestResolveEffectiveOwner(t *testing.T) { } func TestResolveVaultIdentifierOwnerForAuth(t *testing.T) { - t.Run("browser returns org ID", func(t *testing.T) { + t.Run("browser returns derived workflow owner from session", func(t *testing.T) { h, _, _ := newMockHandler(t) - h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" h.Credentials.AuthType = credentials.AuthTypeBearer h.Credentials.OrgID = "org-browser" + h.DerivedWorkflowOwner = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" owner, err := h.ResolveVaultIdentifierOwnerForAuth(SecretsAuthBrowser) require.NoError(t, err) - require.Equal(t, "org-browser", owner) + require.Equal(t, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", owner) }) t.Run("browser errors on api key auth", func(t *testing.T) { @@ -184,14 +184,14 @@ func TestResolveVaultIdentifierOwnerForAuth(t *testing.T) { require.Contains(t, err.Error(), "interactive login") }) - t.Run("browser errors when org ID is empty", func(t *testing.T) { + t.Run("browser errors when derived workflow owner is empty", func(t *testing.T) { h, _, _ := newMockHandler(t) h.Credentials.AuthType = credentials.AuthTypeBearer - h.Credentials.OrgID = "" + h.Credentials.OrgID = "org-1" _, err := h.ResolveVaultIdentifierOwnerForAuth(SecretsAuthBrowser) require.Error(t, err) - require.Contains(t, err.Error(), "organization information is missing") + require.Contains(t, err.Error(), "derived workflow owner is not available") }) t.Run("onchain delegates to ResolveEffectiveOwner", func(t *testing.T) { @@ -226,7 +226,7 @@ func TestEncryptSecrets_UsesWorkflowOwnerAddress(t *testing.T) { enc, err := h.EncryptSecrets(UpsertSecretsInputs{ {ID: "secret-1", Value: "val1", Namespace: "main"}, - }) + }, "0xabc") require.NoError(t, err) require.Len(t, enc, 1) require.Equal(t, "0xabc", enc[0].Id.Owner) diff --git a/cmd/secrets/delete/delete.go b/cmd/secrets/delete/delete.go index 039b9b62..e691971a 100644 --- a/cmd/secrets/delete/delete.go +++ b/cmd/secrets/delete/delete.go @@ -171,7 +171,7 @@ func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Durati if common.IsBrowserFlow(secretsAuth) { ui.Dim("Using your account to authorize vault access for this delete request...") - return h.ExecuteBrowserVaultAuthorization(context.Background(), vaulttypes.MethodSecretsDelete, digest, requestBody) + return h.ExecuteBrowserVaultAuthorization(context.Background(), vaulttypes.MethodSecretsDelete, digest, requestBody, owner) } gatewayPost := func() error { diff --git a/cmd/secrets/list/list.go b/cmd/secrets/list/list.go index ca6a3c54..03f8adf0 100644 --- a/cmd/secrets/list/list.go +++ b/cmd/secrets/list/list.go @@ -135,7 +135,7 @@ func Execute(h *common.Handler, namespace string, duration time.Duration, secret if common.IsBrowserFlow(secretsAuth) { ui.Dim("Using your account to authorize vault access for this list request...") - return h.ExecuteBrowserVaultAuthorization(context.Background(), vaulttypes.MethodSecretsList, digest, body) + return h.ExecuteBrowserVaultAuthorization(context.Background(), vaulttypes.MethodSecretsList, digest, body, owner) } ownerAddr := ethcommon.HexToAddress(owner) diff --git a/go.mod b/go.mod index 2935f6b7..f2b862ff 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,18 @@ go 1.26.2 require ( github.com/BurntSushi/toml v1.5.0 - github.com/Masterminds/semver/v3 v3.4.0 - github.com/andybalholm/brotli v1.2.0 + github.com/Masterminds/semver/v3 v3.5.0 + github.com/andybalholm/brotli v1.2.1 github.com/avast/retry-go/v4 v4.7.0 github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/huh v0.8.0 github.com/charmbracelet/lipgloss v1.1.0 github.com/denisbrodbeck/machineid v1.0.1 - github.com/ethereum/go-ethereum v1.17.2 + github.com/ethereum/go-ethereum v1.17.3 github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 - github.com/go-playground/validator/v10 v10.30.1 + github.com/go-playground/validator/v10 v10.30.2 github.com/google/uuid v1.6.0 github.com/jarcoal/httpmock v1.4.1 github.com/jedib0t/go-pretty/v6 v6.6.5 @@ -24,25 +24,25 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.34.0 github.com/smartcontractkit/chain-selectors v1.0.100 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260421191147-d10b9943ac71 - github.com/smartcontractkit/chainlink-common/keystore v1.0.2 - github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260119171452-39c98c3b33cd + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260520194751-11a4f360f4e2 + github.com/smartcontractkit/chainlink-common/keystore v1.1.0 + github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6 github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42 github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 - github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5 - github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 + github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8 + github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8 github.com/smartcontractkit/cre-sdk-go v1.11.0 github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12 - github.com/smartcontractkit/mcms v0.41.1 + github.com/smartcontractkit/mcms v0.45.0 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/test-go/testify v1.1.4 - go.uber.org/zap v1.27.1 - golang.org/x/term v0.41.0 + go.uber.org/zap v1.28.0 + golang.org/x/term v0.43.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -68,38 +68,37 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/NethermindEth/juno v0.12.5 // indirect github.com/NethermindEth/starknet.go v0.8.0 // indirect - github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260416073033-7c2071eaa8d4 // indirect github.com/VictoriaMetrics/fastcache v1.13.0 // indirect - github.com/XSAM/otelsql v0.37.0 // indirect - github.com/apache/arrow-go/v18 v18.3.1 // indirect - github.com/aptos-labs/aptos-go-sdk v1.12.1 // indirect + github.com/XSAM/otelsql v0.42.0 // indirect + github.com/apache/arrow-go/v18 v18.6.0 // indirect + github.com/aptos-labs/aptos-go-sdk v1.13.0 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect github.com/aws/aws-sdk-go v1.55.8 // indirect - github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect - github.com/bits-and-blooms/bitset v1.24.0 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/blendle/zapdriver v1.3.1 // indirect - github.com/block-vision/sui-go-sdk v1.1.4 // indirect + github.com/block-vision/sui-go-sdk v1.2.1 // indirect github.com/btcsuite/btcd v0.24.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect - github.com/buger/goterm v1.0.4 // indirect - github.com/buger/jsonparser v1.1.2 // indirect + github.com/buger/jsonparser v1.2.0 // indirect github.com/buraksezer/consistent v0.10.0 // indirect github.com/bytecodealliance/wasmtime-go/v28 v28.0.0 // indirect github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect @@ -108,6 +107,7 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.2 // indirect github.com/cloudevents/sdk-go/v2 v2.16.2 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -121,7 +121,7 @@ require ( github.com/coder/websocket v1.8.14 // indirect github.com/cometbft/cometbft v0.38.21 // indirect github.com/cometbft/cometbft-db v1.0.1 // indirect - github.com/consensys/gnark-crypto v0.19.2 // indirect + github.com/consensys/gnark-crypto v0.20.1 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.1.1 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect @@ -137,12 +137,12 @@ require ( github.com/danieljoos/wincred v1.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dchest/siphash v1.2.3 // indirect - github.com/deckarep/golang-set/v2 v2.8.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/deckarep/golang-set/v2 v2.9.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/dgraph-io/badger/v4 v4.7.0 // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect github.com/digital-asset/dazl-client/v8 v8.9.0 // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -150,27 +150,24 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/esote/minmaxheap v1.0.0 // indirect - github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.7 // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect - github.com/expr-lang/expr v1.17.7 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/expr-lang/expr v1.17.8 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/fbsobreira/gotron-sdk v0.0.0-20250403083053-2943ce8c759b // indirect github.com/ferranbt/fastssz v0.1.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect - github.com/gagliardetto/anchor-go v1.0.0 // indirect github.com/gagliardetto/binary v0.8.0 // indirect - github.com/gagliardetto/metaplex-go v0.2.1 // indirect github.com/gagliardetto/solana-go v1.13.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect - github.com/gagliardetto/utilz v0.1.3 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/gin-contrib/sessions v0.0.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect github.com/go-co-op/gocron/v2 v2.18.0 // indirect - github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect + github.com/go-json-experiment/json v0.0.0-20260505212615-e40f80bf6836 // indirect github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect @@ -180,7 +177,7 @@ require ( github.com/go-openapi/jsonreference v0.21.5 // indirect github.com/go-openapi/swag v0.25.5 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.12.1 // indirect @@ -190,7 +187,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/google/flatbuffers v25.12.19+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc // indirect github.com/gorilla/context v1.1.1 // indirect @@ -198,20 +195,19 @@ require ( github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect - github.com/grafana/pyroscope-go v1.2.8 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect + github.com/grafana/pyroscope-go v1.3.0 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.10 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect - github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-metrics v0.5.4 // indirect - github.com/hashicorp/go-plugin v1.7.0 // indirect + github.com/hashicorp/go-plugin v1.8.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hasura/go-graphql-client v0.15.1 // indirect @@ -222,12 +218,9 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/invopop/jsonschema v0.13.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.3 // indirect + github.com/invopop/jsonschema v0.14.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.4 // indirect github.com/jackc/pgx/v5 v5.9.2 // indirect @@ -241,34 +234,33 @@ require ( github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/karalabe/hid v1.0.1-0.20260315100226-f5d04adeffeb // indirect - github.com/klauspost/compress v1.18.5 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.11.1 // indirect + github.com/lib/pq v1.12.3 // indirect github.com/linxGnu/grocksdb v1.9.3 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/marcboeker/go-duckdb v1.8.5 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/miekg/dns v1.1.72 // indirect + github.com/mattn/go-runewidth v0.0.20 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/moby/moby/api v1.54.2 // indirect + github.com/moby/moby/client v0.4.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect + github.com/mr-tron/base58 v1.3.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect @@ -277,10 +269,11 @@ require ( github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/oklog/run v1.2.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.3.0 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect - github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/stun/v2 v2.0.0 // indirect @@ -291,16 +284,15 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect - github.com/samber/lo v1.52.0 // indirect + github.com/samber/lo v1.53.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect @@ -310,45 +302,43 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/smartcontractkit/ccip-owner-contracts v0.1.0 // indirect - github.com/smartcontractkit/chainlink-aptos v0.0.0-20260407161350-a86b1969da65 // indirect + github.com/smartcontractkit/chainlink-aptos v0.0.0-20260507123701-77fc93b573bb // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260417153334-3b564ef614de // indirect - github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260415165642-49f23e4d76cc // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.13 // indirect - github.com/smartcontractkit/chainlink-deployments-framework v0.95.0 // indirect - github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260416173445-80f6efde0a03 // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260520194751-11a4f360f4e2 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a // indirect + github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 // indirect + github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260521145337-fdf89453516c // indirect github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260403151002-2c91155b5501 // indirect - github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563 // indirect - github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57 // indirect - github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260410144512-ca02ad6ed16a // indirect - github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a // indirect + github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20260423135514-5b1a7565a99c // indirect + github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260423135514-5b1a7565a99c // indirect + github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260508154216-3ed6f623098f // indirect + github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260505202410-b350dca113b4 // indirect github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 // indirect - github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785 // indirect + github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd // indirect github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 // indirect - github.com/smartcontractkit/chainlink-protos/svr v1.1.1-0.20260203131522-bb8bc5c423b3 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.2-0.20260421131224-c46cbfe7bc6c // indirect - github.com/smartcontractkit/chainlink-sui v0.0.0-20260409184948-5b16fae57fe0 // indirect - github.com/smartcontractkit/chainlink-ton v0.0.0-20260415120434-cecc380f8d87 // indirect + github.com/smartcontractkit/chainlink-protos/svr v1.2.0 // indirect + github.com/smartcontractkit/chainlink-sui v0.0.0-20260429183453-39df0198aed8 // indirect + github.com/smartcontractkit/chainlink-ton v1.0.5-0.20260520103847-15ca4de9dba9 // indirect github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20260408092456-3c6369888d4a // indirect + github.com/smartcontractkit/cld-changesets v0.4.0 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect - github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d // indirect + github.com/smartcontractkit/libocr v0.0.0-20260508200755-99940c85383c // indirect github.com/smartcontractkit/smdkg v0.0.0-20251029093710-c38905e58aeb // indirect github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/stellar/go-stellar-sdk v0.1.0 // indirect - github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 // indirect + github.com/stellar/go-stellar-sdk v0.5.0 // indirect + github.com/stellar/go-xdr v0.0.0-20260423131911-a87d4d0789c3 // indirect github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.16 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect @@ -357,30 +347,28 @@ require ( github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect - github.com/tklauser/go-sysconf v0.3.16 // indirect - github.com/tklauser/numcpus v0.11.0 // indirect + github.com/tklauser/go-sysconf v0.4.0 // indirect + github.com/tklauser/numcpus v0.12.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/urfave/cli/v2 v2.27.7 // indirect github.com/valyala/fastjson v1.6.10 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/xssnick/tonutils-go v1.14.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zeebo/xxh3 v1.0.2 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.dedis.ch/fixbuf v1.0.3 // indirect go.dedis.ch/kyber/v3 v3.1.0 // indirect go.etcd.io/bbolt v1.4.2 // indirect - go.mongodb.org/mongo-driver v1.17.2 // indirect + go.mongodb.org/mongo-driver v1.17.9 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect @@ -400,28 +388,28 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect golang.org/x/arch v0.11.0 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.42.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + golang.org/x/tools v0.45.0 // indirect gonum.org/v1/gonum v0.17.0 // indirect google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/grpc v1.80.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/grpc v1.81.0 // indirect gopkg.in/guregu/null.v4 v4.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gotest.tools/v3 v3.5.2 // indirect @@ -429,7 +417,7 @@ require ( k8s.io/client-go v0.35.3 // indirect k8s.io/klog/v2 v2.140.0 // indirect nhooyr.io/websocket v1.8.14 // indirect - pgregory.net/rapid v1.1.0 // indirect + pgregory.net/rapid v1.2.0 // indirect ) replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 diff --git a/go.sum b/go.sum index d258352c..85e83cf5 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= -contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= cosmossdk.io/api v0.7.6 h1:PC20PcXy1xYKH2KU4RMurVoFjjKkCgYRbVAD4PdqUuY= cosmossdk.io/api v0.7.6/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38= cosmossdk.io/collections v0.4.0 h1:PFmwj2W8szgpD5nOd8GWH6AbYNi1f2J6akWXJ7P5t9s= @@ -44,10 +20,8 @@ cosmossdk.io/x/tx v0.13.7 h1:8WSk6B/OHJLYjiZeMKhq7DK7lHDMyK0UfDbBMxVmeOI= cosmossdk.io/x/tx v0.13.7/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8= filippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= @@ -61,42 +35,37 @@ github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw= +github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Depado/ginprom v1.8.0 h1:zaaibRLNI1dMiiuj1MKzatm8qrcHzikMlCc1anqOdyo= github.com/Depado/ginprom v1.8.0/go.mod h1:XBaKzeNBqPF4vxJpNLincSQZeMDnZp1tIbU0FU0UKgg= -github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= -github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/NethermindEth/juno v0.12.5 h1:a+KYQg8MxzNJIbbqGHq+vU9nTyuWu3acbyXxcUPUDOY= github.com/NethermindEth/juno v0.12.5/go.mod h1:XonWmZVRwCVHv1gjoVCoTFiZnYObwdukpd3NCsl04bA= github.com/NethermindEth/starknet.go v0.8.0 h1:mGh7qDWrvuXJPcgGJP31DpifzP6+Ef2gt/BQhaqsV40= github.com/NethermindEth/starknet.go v0.8.0/go.mod h1:slNA8PxtxA/0LQv0FwHnL3lHFDNhVZfTK6U2gjVb7l8= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260416073033-7c2071eaa8d4 h1:/97whAzwYxMNHXeTfhAtCRzNCpyblmxCtSYpsfzCszM= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260416073033-7c2071eaa8d4/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/XSAM/otelsql v0.37.0 h1:ya5RNw028JW0eJW8Ma4AmoKxAYsJSGuNVbC7F1J457A= -github.com/XSAM/otelsql v0.37.0/go.mod h1:LHbCu49iU8p255nCn1oi04oX2UjSoRcUMiKEHo2a5qM= +github.com/XSAM/otelsql v0.42.0 h1:Li0xF4eJUxG2e0x3D4rvRlys1f27yJKvjTh7ljkUP5o= +github.com/XSAM/otelsql v0.42.0/go.mod h1:4mOrEv+cS1KmKzrvTktvJnstr5GtKSAK+QHvFR9OcpI= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -104,22 +73,18 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow-go/v18 v18.3.1 h1:oYZT8FqONiK74JhlH3WKVv+2NKYoyZ7C2ioD4Dj3ixk= -github.com/apache/arrow-go/v18 v18.3.1/go.mod h1:12QBya5JZT6PnBihi5NJTzbACrDGXYkrgjujz3MRQXU= -github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= -github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/apache/arrow-go/v18 v18.6.0 h1:GX/Jyd3R7mCLiECAwY9FWbbaYblie2WXBSz4Sw8fNpM= +github.com/apache/arrow-go/v18 v18.6.0/go.mod h1:gm3MiPpY82fLYK5VKPB3WoJbsiLVDfT7flD5/vHReKw= +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/aptos-labs/aptos-go-sdk v1.12.1 h1:EXtA9GF9fJndRcjWVZZ3Hf5hXxvGWNPu+1k3A6eGOfM= -github.com/aptos-labs/aptos-go-sdk v1.12.1/go.mod h1:FTgKp0RLfEefllCdkCj0jPU14xWk11yA7SFVfCDLUj8= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/aptos-labs/aptos-go-sdk v1.13.0 h1:epv7K/tIbAEO2RfogwGacICBig8rrigJj24fDsy6KTg= +github.com/aptos-labs/aptos-go-sdk v1.13.0/go.mod h1:FTgKp0RLfEefllCdkCj0jPU14xWk11yA7SFVfCDLUj8= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c h1:cxQVoh6kY+c4b0HUchHjGWBI8288VhH50qxKG3hdEg0= github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c/go.mod h1:3XzxudkrYVUvbduN/uI2fl4lSrMSzU0+3RCu2mpnfx8= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -130,12 +95,10 @@ github.com/avast/retry-go/v4 v4.7.0 h1:yjDs35SlGvKwRNSykujfjdMxMhMQQM0TnIjJaHB+Z github.com/avast/retry-go/v4 v4.7.0/go.mod h1:ZMPDa3sY2bKgpLtap9JRUgk2yTAba7cgiFhqxY2Sg6Q= github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E= github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= -github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= -github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= @@ -164,10 +127,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= -github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= @@ -185,16 +146,14 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= -github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= -github.com/block-vision/sui-go-sdk v1.1.4 h1:1PPgYxQjo1P9UCgFOPTvDCuGEglRL32NwjKPulR4FQk= -github.com/block-vision/sui-go-sdk v1.1.4/go.mod h1:t8mWASwfyv+EyqHGO9ZrcDiCJWGOFEXqq50TMJ8GQco= +github.com/block-vision/sui-go-sdk v1.2.1 h1:uwvGbzfcrS4SsIaakclYxy0qgEF1XWIUtTYWXB4PoAw= +github.com/block-vision/sui-go-sdk v1.2.1/go.mod h1:t8mWASwfyv+EyqHGO9ZrcDiCJWGOFEXqq50TMJ8GQco= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= @@ -226,12 +185,8 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U= -github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= -github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= -github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g= +github.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU= github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw= github.com/bytecodealliance/wasmtime-go/v28 v28.0.0 h1:aBU8cexP2rPZ0Qz488kvn2NXvWZHL2aG1/+n7Iv+xGc= @@ -252,7 +207,6 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -292,6 +246,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.2 h1:ydUjnKn4RoCeN8rge3F/deT52w2WJMmIC5mHNUq+Ut8= github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.2/go.mod h1:Bny999RuVUtNjzTGa9HCHpXjrLGMipJVq5kqVpudBl0= github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= @@ -323,10 +279,8 @@ github.com/cometbft/cometbft v0.38.21 h1:qcIJSH9LiwU5s6ZgKR5eRbsLNucbubfraDs5bzg github.com/cometbft/cometbft v0.38.21/go.mod h1:UCu8dlHqvkAsmAFmWDRWNZJPlu6ya2fTWZlDrWsivwo= github.com/cometbft/cometbft-db v1.0.1 h1:SylKuLseMLQKw3+i8y8KozZyJcQSL98qEe2CGMCGTYE= github.com/cometbft/cometbft-db v1.0.1/go.mod h1:EBrFs1GDRiTqrWXYi4v90Awf/gcdD5ExzdPbg4X8+mk= -github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts= -github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8= -github.com/consensys/gnark-crypto v0.19.2 h1:qrEAIXq3T4egxqiliFFoNrepkIWVEeIYwt3UL0fvS80= -github.com/consensys/gnark-crypto v0.19.2/go.mod h1:rT23F0XSZqE0mUA0+pRtnL56IbPxs6gp4CeRsBk4XS0= +github.com/consensys/gnark-crypto v0.20.1 h1:PXDUBvk8AzhvWowHLWBEAfUQcV1/aZgWIqD6eMpXmDg= +github.com/consensys/gnark-crypto v0.20.1/go.mod h1:RBWrSgy+IDbGR69RRV313th3M/aZU1ubk2om+qHuTSc= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -335,15 +289,11 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM= @@ -366,7 +316,6 @@ github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9E github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -386,7 +335,6 @@ github.com/cucumber/godog v0.15.1 h1:rb/6oHDdvVZKS66hrhpjFQFHjthFSrQBCOI1LwshNTI github.com/cucumber/godog v0.15.1/go.mod h1:qju+SQDewOljHuq9NSM66s0xEhogx0q30flfxL4WUk8= github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= -github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= @@ -399,14 +347,14 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= -github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= -github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/deckarep/golang-set/v2 v2.9.0 h1:prva4eP9UysWagLyKrtn074ughi0NnkIf0A4M5yOCKI= +github.com/deckarep/golang-set/v2 v2.9.0/go.mod h1:EWknQXbs0mcFpat2QOoXV0Ee57cD+w6ZEN76BR2JVrM= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= @@ -414,24 +362,18 @@ github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMS github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dfuse-io/logging v0.0.0-20201110202154-26697de88c79/go.mod h1:V+ED4kT/t/lKtH99JQmKIb0v9WL3VaYkJ36CfHlVECI= -github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70/go.mod h1:EoK/8RFbMEteaCaz89uessDTnCWjbbcr+DXcBh4el5o= github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digital-asset/dazl-client/v8 v8.9.0 h1:F2qTUWtHAjhGyRGV+xTim+VAFwM99FpcOx4+wowvPnY= github.com/digital-asset/dazl-client/v8 v8.9.0/go.mod h1:q1KevCJ8FpH8je2MnnjN8/QUfhstB4fKpyKyqDtqFh0= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.3-0.20260325154711-31a1689cb0a1+incompatible h1:f51eIlZsZqGKXyNeCHs5oVo/xQiR9zh+pDYMfnu3VPQ= -github.com/docker/docker v28.5.3-0.20260325154711-31a1689cb0a1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= @@ -457,21 +399,19 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/esote/minmaxheap v1.0.0 h1:rgA7StnXXpZG6qlM0S7pUmEv1KpWe32rYT4x8J8ntaA= github.com/esote/minmaxheap v1.0.0/go.mod h1:Ln8+i7fS1k3PLgZI2JAo0iA1as95QnIYiGCrqSJ5FZk= -github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls= -github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= +github.com/ethereum/c-kzg-4844/v2 v2.1.7 h1:aat3CuITdDbPC6pmEGRT0zJ5eOxzrZj8TJT5z7Xk//M= +github.com/ethereum/c-kzg-4844/v2 v2.1.7/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.2 h1:ag6geu0kn8Hv5FLKTpH+Hm2DHD+iuFtuqKxEuwUsDOI= -github.com/ethereum/go-ethereum v1.17.2/go.mod h1:KHcRXfGOUfUmKg51IhQ0IowiqZ6PqZf08CMtk0g5K1o= -github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= -github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= +github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= +github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/failsafe-go/failsafe-go v0.9.0 h1:w0g7iv48RpQvV3UH1VlgUnLx9frQfCwI7ljnJzqEhYg= github.com/failsafe-go/failsafe-go v0.9.0/go.mod h1:sX5TZ4HrMLYSzErWeckIHRZWgZj9PbKMAEKOVLFWtfM= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= @@ -483,30 +423,20 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= +github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/gagliardetto/anchor-go v1.0.0 h1:YNt9I/9NOrNzz5uuzfzByAcbp39Ft07w63iPqC/wi34= -github.com/gagliardetto/anchor-go v1.0.0/go.mod h1:X6c9bx9JnmwNiyy8hmV5pAsq1c/zzPvkdzeq9/qmlCg= -github.com/gagliardetto/binary v0.6.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= -github.com/gagliardetto/hashsearch v0.0.0-20191005111333-09dd671e19f9/go.mod h1:513DXpQPzeRo7d4dsCP3xO3XI8hgvruMl9njxyQeraQ= -github.com/gagliardetto/metaplex-go v0.2.1 h1:NMBsgJe3I2avKZ39dfYQvXsGsr2BxUgARkA9LZ6szBg= -github.com/gagliardetto/metaplex-go v0.2.1/go.mod h1:6ZLYBvlWcXktXQ/QcBJYRzKgK7Q3WgiGD7BjE7Zxpw4= -github.com/gagliardetto/solana-go v1.4.0/go.mod h1:NFuoDwHPvw858ZMHUJr6bkhN8qHt4x6e+U3EYHxAwNY= github.com/gagliardetto/solana-go v1.13.0 h1:uNzhjwdAdbq9xMaX2DF0MwXNMw6f8zdZ7JPBtkJG7Ig= github.com/gagliardetto/solana-go v1.13.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= -github.com/gagliardetto/utilz v0.1.1/go.mod h1:b+rGFkRHz3HWJD0RYMzat47JyvbTtpE0iEcYTRJTLLA= -github.com/gagliardetto/utilz v0.1.3 h1:A+asc+6/3a9qNBrgticApj3yW5F7y4TaJd8Ijg+o0zM= -github.com/gagliardetto/utilz v0.1.3/go.mod h1:b+rGFkRHz3HWJD0RYMzat47JyvbTtpE0iEcYTRJTLLA= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 h1:Uc+IZ7gYqAf/rSGFplbWBSHaGolEQlNLgMgSE3ccnIQ= @@ -534,13 +464,10 @@ github.com/go-co-op/gocron/v2 v2.18.0 h1:DS3Uhru66q1jy/5f9V0itmi3cLXcn2b7N+duGfg github.com/go-co-op/gocron/v2 v2.18.0/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= -github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-json-experiment/json v0.0.0-20260505212615-e40f80bf6836 h1:5KGUhXZFTN1PrCY4zUZLe1J8n7uBNmPDbCLCn78EbPQ= +github.com/go-json-experiment/json v0.0.0-20260505212615-e40f80bf6836/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= @@ -598,8 +525,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= -github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= +github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= @@ -613,8 +540,8 @@ github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6l github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -632,15 +559,7 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -665,12 +584,10 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= -github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= +github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -678,7 +595,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -692,14 +608,8 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw= github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= @@ -707,60 +617,46 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= -github.com/grafana/pyroscope-go v1.2.8 h1:UvCwIhlx9DeV7F6TW/z8q1Mi4PIm3vuUJ2ZlCEvmA4M= -github.com/grafana/pyroscope-go v1.2.8/go.mod h1:SSi59eQ1/zmKoY/BKwa5rSFsJaq+242Bcrr4wPix1g8= -github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= -github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope-go v1.3.0 h1:t3Jehad8vvqN4oRAB0LdmfQ5ZSUXQw3asoft+K4GAT8= +github.com/grafana/pyroscope-go v1.3.0/go.mod h1:XA7I3usNx+UdjOZfQnl1WV8y924vsJo9KIVrKB+9jx4= +github.com/grafana/pyroscope-go/godeltaprof v0.1.10 h1:dvhndEbyavTb59vFCd6PsrAG5qi69/qZZtegh/TJKSY= +github.com/grafana/pyroscope-go/godeltaprof v0.1.10/go.mod h1:XnWRGg2XO5uxZdiz1rfeJH6w1eZ+YICCBVXNWOfH86g= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ= -github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= @@ -774,33 +670,19 @@ github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+ github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= -github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-plugin v1.8.0 h1:ie8S6RRY8RvB2usYZv+AAZ/wBvx2AU5p5QeP5j/FORs= +github.com/hashicorp/go-plugin v1.8.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hasura/go-graphql-client v0.15.1 h1:mCb5I+8Bk3FU3GKWvf/zDXkTh7FbGlqJmP3oisBdnN8= @@ -820,13 +702,11 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= @@ -837,8 +717,9 @@ github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsD github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY= github.com/influxdata/tdigest v0.0.1/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -855,10 +736,10 @@ github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -866,8 +747,9 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.4-0.20250125160525-bc041643406d h1:tfqquD0m2XihRYJ4SQ5dS3J0l4vIMXUT2dhvBib4d0Q= +github.com/jackc/pgproto3/v2 v2.3.4-0.20250125160525-bc041643406d/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -906,7 +788,6 @@ github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5Xum github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -917,7 +798,6 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -929,9 +809,6 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karalabe/hid v1.0.1-0.20260315100226-f5d04adeffeb h1:Ag83At00qa4FLkcdMgrwHVSakqky/eZczOlxd4q336E= @@ -939,15 +816,12 @@ github.com/karalabe/hid v1.0.1-0.20260315100226-f5d04adeffeb/go.mod h1:qk1sX/IBg github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= -github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -973,10 +847,8 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= -github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= -github.com/linkedin/goavro/v2 v2.12.0 h1:rIQQSj8jdAUlKQh6DttK8wCRv4t4QO09g1C4aBWXslg= -github.com/linkedin/goavro/v2 v2.12.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/linxGnu/grocksdb v1.9.3 h1:s1cbPcOd0cU2SKXRG1nEqCOWYAELQjdqg3RVI2MH9ik= github.com/linxGnu/grocksdb v1.9.3/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= @@ -988,11 +860,8 @@ github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6 github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo= github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 h1:ykXz+pRRTibcSjG1yRhpdSHInF8yZY/mfn+Rz2Nd1rE= github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739/go.mod h1:zUx1mhth20V3VKgL5jbd1BSQcW4Fy6Qs4PZvQwRFwzM= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= @@ -1001,30 +870,25 @@ github.com/marcboeker/go-duckdb v1.8.5 h1:tkYp+TANippy0DaIOP5OEfBEwbUINqiFqgwMQ4 github.com/marcboeker/go-duckdb v1.8.5/go.mod h1:6mK7+WQE4P4u5AFLvVBmhFxY5fvhymFptghgJX6B+/8= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= +github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1033,33 +897,18 @@ github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= -github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY= github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -1069,8 +918,12 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -1091,12 +944,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= -github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI= +github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -1112,7 +963,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1122,7 +972,6 @@ github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dl github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1139,30 +988,30 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= -github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= +github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= -github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= @@ -1187,14 +1036,12 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -1207,8 +1054,6 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= @@ -1216,30 +1061,24 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/prometheus/prometheus v0.311.2-0.20260410083055-07c6232d159b h1:tjxqNQlYTJzrQrY7HM2SbnxqzuE64vnvlSmSbAvBBDE= -github.com/prometheus/prometheus v0.311.2-0.20260410083055-07c6232d159b/go.mod h1:h4Ogksuo6VUZmnm6q/ruKTUzrg9Vvu6u/6O/rQ5xPMg= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/prometheus/prometheus v0.311.3 h1:3IrVxQv6v5i/ZCGi6OrYeBhtCwaPTn6Z3DYruXoYm3M= +github.com/prometheus/prometheus v0.311.3/go.mod h1:gjsCxTKtHO1Q8T9333u1s+lUR1OjPyM7ruuGH8RvVyo= github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= -github.com/riferrei/srclient v0.5.4 h1:dfwyR5u23QF7beuVl2WemUY2KXh5+Sc4DHKyPXBNYuc= -github.com/riferrei/srclient v0.5.4/go.mod h1:vbkLmWcgYa7JgfPvuy/+K8fTS0p1bApqadxrxi/S1MI= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -1253,18 +1092,14 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= -github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= +github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -1274,7 +1109,6 @@ github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6v github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ= github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 h1:S4OC0+OBKz6mJnzuHioeEat74PuQ4Sgvbf8eus695sc= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2/go.mod h1:8zLRYR5npGjaOXgPSKat5+oOh+UHd8OdbS18iqX9F6Y= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= @@ -1285,8 +1119,8 @@ github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKl github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= -github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= -github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -1294,7 +1128,6 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1307,50 +1140,48 @@ github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9L github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= github.com/smartcontractkit/chain-selectors v1.0.100 h1:wpiSpmI/eFjY+wx/nPr5VuNF4hki0prIBMKEaQWn3g4= github.com/smartcontractkit/chain-selectors v1.0.100/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-aptos v0.0.0-20260407161350-a86b1969da65 h1:b6+ZvoZxXSj7HywoZ0CfWtC6k47eBSaxNzc2LqtiXBA= -github.com/smartcontractkit/chainlink-aptos v0.0.0-20260407161350-a86b1969da65/go.mod h1:BbVsx2VcwSVWkd0C5TcAkQBnFaeYFnogJgUa9BUla18= +github.com/smartcontractkit/chainlink-aptos v0.0.0-20260507123701-77fc93b573bb h1:6UjHnVanvb+6yJuefhyVlfv6YKFGMeZY5jv+a7Sexyo= +github.com/smartcontractkit/chainlink-aptos v0.0.0-20260507123701-77fc93b573bb/go.mod h1:FEm5fvIQe5O8Qdx6GvQcXsk7rDFpmYdIWXea5i4tpjw= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260417153334-3b564ef614de h1:coysmw4zHm6TLOZawoe2h0hHh/25ft+hq9+9mRNkqTs= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260417153334-3b564ef614de/go.mod h1:1XxxpkgCmG/z6y30yRuVrcxre6zixIVX3xzi706Db/8= -github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm v0.0.0-20260406180811-0ec22f0243a4 h1:b6IxxglkWivZ5nfYdYkHF4w0l2BJyEmSdyMYRm47aB4= -github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm v0.0.0-20260406180811-0ec22f0243a4/go.mod h1:zLqdD2kBX7NsntBneclb2yrHhjFaJdoyA8dK5eimlrE= -github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260415165642-49f23e4d76cc h1:dP1ERzdTbiJbHVXfHYdBAi1+8NjgkyQuY2oFNWWWDsQ= -github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260415165642-49f23e4d76cc/go.mod h1:7XR5wfgT8hjSsiV+t0EAWvna+rYQeMPaoZf/0g+dios= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 h1:p0nFrTYrOQzDhWYm6suaM5CoWiXV5NV7llHnp6/Kn/8= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1/go.mod h1:1XxxpkgCmG/z6y30yRuVrcxre6zixIVX3xzi706Db/8= +github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm v0.0.0-20260408145530-22e2d05695cd h1:Jtw6p5iisjXZyFOcBvWh6PDQKtvryrRU2JMmezdutjo= +github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm v0.0.0-20260408145530-22e2d05695cd/go.mod h1:zLqdD2kBX7NsntBneclb2yrHhjFaJdoyA8dK5eimlrE= +github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260506144252-c100eabfda74 h1:uRvSogvgIi3JhQGNYGmRr3GqTSbD0yG1jSgO7lHL5z4= +github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260506144252-c100eabfda74/go.mod h1:LDCeKlQ6Ne0DYjI2RiqY2ZIO449FzjSHGc04TLszh68= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc h1:mvobZx5JV5PhG/9IXPReV+8mAGnupl0HIWQZ43zxzd4= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc/go.mod h1:gzCVLUlNov/zFXSC7G6zcGkZU1IfNOHaakbAPDe5Woc= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc h1:War93neyFmv7pzuElZeZC3qc/OfGtLvEXvqL3qeBfM0= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= -github.com/smartcontractkit/chainlink-ccv v0.0.0-20260408181529-b5080e662563 h1:1sYQ2lG3zbAG2vASNF5kLke8DhGk5lNaJirwPDx3Vi4= -github.com/smartcontractkit/chainlink-ccv v0.0.0-20260408181529-b5080e662563/go.mod h1:nEuyjUh4wrK6mNXEAaOncl/AhCl31oaxOS160gNW0vc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260421191147-d10b9943ac71 h1:WSNUds78NMlwDttROK/hJZ6ZOremyrR5JXJmPlT8hO8= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260421191147-d10b9943ac71/go.mod h1:kOIIjzxuRXK31j1JdZgUAGjqbGwmJ5gU5qI+FMkP6/I= -github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= -github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= -github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk= -github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340/go.mod h1:P/0OSXUlFaxxD4B/P6HWbxYtIRmmWGDJAvanq19879c= -github.com/smartcontractkit/chainlink-data-streams v0.1.13 h1:YOmt545DW6U0SyaqBf+NTGDLm1yMurVI7yOvxP5hlJk= -github.com/smartcontractkit/chainlink-data-streams v0.1.13/go.mod h1:00aL7OK0BJdF9gn/4t4f/pctUu2VLwwfA8G/tl9rCrM= -github.com/smartcontractkit/chainlink-deployments-framework v0.95.0 h1:PHncc++Xk9OIP7JDiCvpqdMdP85YZWKQCpnRR7l477g= -github.com/smartcontractkit/chainlink-deployments-framework v0.95.0/go.mod h1:pTA1JrdlMSfb9WkrIfphq2KV/+paW7GHf15Oc/uJBxs= -github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260416173445-80f6efde0a03 h1:z+Au1CpZhVYpn7mkmG/mYFBFkdZoqibQ3LngEHm8Fqs= -github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260416173445-80f6efde0a03/go.mod h1:6vCMfxz7cMW0wWseNKtct+b1JJbbRVJJhh/t6pQWN3M= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 h1:H4elXlsDnREQpx8JESKxIuHzMCwGlJbL5+MpFCoLZZc= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= +github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h1:IMopuENFVS63AerRELdfWo6o60UNUidcldJOxJLmk24= +github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260520194751-11a4f360f4e2 h1:Ne11+eg/uuVJ5duEfr4ec+1EoeZt/dHS9IFIGDdTr00= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260520194751-11a4f360f4e2/go.mod h1:Pu4czYGiGRAJo+a1M3ZXY+wEyItMe9wtJCVp0pBgAzg= +github.com/smartcontractkit/chainlink-common/keystore v1.1.0 h1:2wzySccgk2fpWusPKO0bpeAZzfSU9eq6CS5U+JwYaVo= +github.com/smartcontractkit/chainlink-common/keystore v1.1.0/go.mod h1:6JexOOhPhknQ0QMuppFIlOpm6wCp54yZMxai+tWugwY= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260520194751-11a4f360f4e2 h1:22H/CQy2L1unVJ2KEViEqvM8evJLSIqJxEdfDeXB4o8= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260520194751-11a4f360f4e2/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= +github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a h1:8bIqv4r7SgDWkXL2Qz/Ijw+YjZY1uroIte3E2v2keVk= +github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a/go.mod h1:dF5JiHWueHjYguUUUrFeb03MkcDqha/tssEkqTkgzp4= +github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 h1:Vp4EwkvxcBzgahIZdbWyCExDXLha93cS63xvwd2xwx8= +github.com/smartcontractkit/chainlink-deployments-framework v0.105.0/go.mod h1:xFLOOpIz7vqqno4YngHZlF9MKqk8rnvQa9adVElUXaE= +github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260521145337-fdf89453516c h1:Udi8eopVcBCcr3+M3Jzbe3SksQdl1HxMaBMvD1XJh90= +github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260521145337-fdf89453516c/go.mod h1:pTPpE5TQD0re8MJ9mWx5VjmXXPXF/2ZYAdS5KcBcc/c= github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260403151002-2c91155b5501 h1:QJiXTG9CmaQAuMRn5JGi+Jhji7fSkehVnKpjc8oNJJY= github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260403151002-2c91155b5501/go.mod h1:4cT1BeNF8DAn6In9zr3LayVCv1KzFeuxT7zcuNkfIb0= -github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260119171452-39c98c3b33cd h1:sK+pK4epQp20yQ7XztwrVgkTkRAr4FY+TvEegW8RuQk= -github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260119171452-39c98c3b33cd/go.mod h1:7Jlt72+V9891y3LnGwHzmQwt9tfEGYryRKiGlQHo/o8= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6 h1:JFo7C3FilwhfwGBLAyj2umbL+P4QxGmVi/b8yt9kqvI= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6/go.mod h1:a260YnLyWq2NHLUN5cSVyMGk9nhO6RguCaTI2rsVqyA= github.com/smartcontractkit/chainlink-feeds v0.1.2-0.20250227211209-7cd000095135 h1:8u9xUrC+yHrTDexOKDd+jrA6LCzFFHeX1G82oj2fsSI= github.com/smartcontractkit/chainlink-feeds v0.1.2-0.20250227211209-7cd000095135/go.mod h1:NkvE4iQgiT7dMCP6U3xPELHhWhN5Xr6rHC0axRebyMU= -github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563 h1:ACpDbAxG4fa4sA83dbtYcrnlpE/y7thNIZfHxTv2ZLs= -github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563/go.mod h1:jP5mrOLFEYZZkl7EiCHRRIMSSHCQsYypm1OZSus//iI= -github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57 h1:sCrr1Oy/JZstf/Oi2cRuU4mDN1BRUKfXP2CKByCMADg= -github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57/go.mod h1:kGprqyjsz6qFNVszOQoHc24wfvCjyipNZFste/3zcbs= -github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260410144512-ca02ad6ed16a h1:QbP7JIzDNvgmGL9TLM5VdzvCA90Ncg7E0ommuodKzEc= -github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260410144512-ca02ad6ed16a/go.mod h1:HG/aei0MgBOpsyRLexdKGtOUO8yjSJO3iUu0Uu8KBm4= -github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a h1:PsFckZp3Dhb5pVc0Xccj1lvnOEg0H3eQdjtZgnCKd+4= -github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a/go.mod h1:7ketk4ischPQW/JQgmyHz6zdzLUJv1VC29SiSgosydQ= +github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20260423135514-5b1a7565a99c h1:AYRSQarVw1EJXUrGvHSwmRTtNHHww/i3xwLat5CshUE= +github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20260423135514-5b1a7565a99c/go.mod h1:HcwehCao5k5C2NGuKJUVoX/AYtoH6njGFiV44dBOcY4= +github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260423135514-5b1a7565a99c h1:0c+bCKo47vy/ItRtGa3S/vCpE5LRlgXpGnVKQX8TgjE= +github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260423135514-5b1a7565a99c/go.mod h1:kGprqyjsz6qFNVszOQoHc24wfvCjyipNZFste/3zcbs= +github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260508154216-3ed6f623098f h1:C6eGGsdTVyB3mtz1VF/o9Znshwrr/VkKsLYGinOVM/k= +github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260508154216-3ed6f623098f/go.mod h1:HG/aei0MgBOpsyRLexdKGtOUO8yjSJO3iUu0Uu8KBm4= +github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260505202410-b350dca113b4 h1:nXU0s4WAVU2cAR76Ke7h9z55NuEtRq1WvT4wVEs7jwk= +github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260505202410-b350dca113b4/go.mod h1:7ketk4ischPQW/JQgmyHz6zdzLUJv1VC29SiSgosydQ= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/committee-verifier v0.0.0-20251211142334-5c3421fe2c8d h1:VYoBBNnQpZ5p+enPTl8SkKBRaubqyGpO0ul3B1np++I= @@ -1363,12 +1194,14 @@ github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251 github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:5JdppgngCOUS76p61zCinSCgOhPeYQ+OcDUuome5THQ= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42 h1:tyEgGOaYa7PBsmDIIvctOQPUc5c4Jtd/p4X1i96K+yo= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= +github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 h1:SG+wAsNyAcA6Kk19ljuxi3HK9Ll2lpHik8OKoY4x7A0= +github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36/go.mod h1:vL1bDgPSJjV0EqHYs4dDlR+EEE0cJchgvGLYXhwIjXY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= -github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= -github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785 h1:oli+2uLU6jcrJGCuYFqk3475hiwL17SWlITWLv+tx/w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 h1:NJdGFhzT6zMaTod4QkBqVD2sg0I25iw1boOYtTpEwRo= +github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= +github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260512230622-65f10f4cd305 h1:bnSl5p3mFekSJ6QcbZ1TmHn2ffYiX8xk6hNzVmyhstQ= +github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260512230622-65f10f4cd305/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 h1:AEnxv4HM3WD1RbQkRiFyb9cJ6YKAcqBp1CpIcFdZfuo= github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.10.0 h1:0eroOyBwmdoGUwUdvMI0/J7m5wuzNnJDMglSOK1sfNY= @@ -1379,28 +1212,28 @@ github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-1 github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6/go.mod h1:FRwzI3hGj4CJclNS733gfcffmqQ62ONCkbGi49s658w= github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 h1:B7itmjy+CMJ26elVw/cAJqqhBQ3Xa/mBYWK0/rQ5MuI= github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0/go.mod h1:h6kqaGajbNRrezm56zhx03p0mVmmA2xxj7E/M4ytLUA= -github.com/smartcontractkit/chainlink-protos/svr v1.1.1-0.20260203131522-bb8bc5c423b3 h1:X8Pekpv+cy0eW1laZTwATuYLTLZ6gRTxz1ZWOMtU74o= -github.com/smartcontractkit/chainlink-protos/svr v1.1.1-0.20260203131522-bb8bc5c423b3/go.mod h1:TcOliTQU6r59DwG4lo3U+mFM9WWyBHGuFkkxQpvSujo= +github.com/smartcontractkit/chainlink-protos/svr v1.2.0 h1:7jjgqRgORQS/ikL3z0ZgJy95pzjhR9LuU1TVWg4BZ78= +github.com/smartcontractkit/chainlink-protos/svr v1.2.0/go.mod h1:TcOliTQU6r59DwG4lo3U+mFM9WWyBHGuFkkxQpvSujo= github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997 h1:W0HKHO8eE8BckTRnhSdqjHKbJcnk068nEWYnWRu6tJY= github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997/go.mod h1:GTpDgyK0OObf7jpch6p8N281KxN92wbB8serZhU9yRc= -github.com/smartcontractkit/chainlink-solana v1.1.2-0.20260421131224-c46cbfe7bc6c h1:2ZdBZCZWKUMOWLtReaBBHkmDtXc0WtwcqIROHAcm3j4= -github.com/smartcontractkit/chainlink-solana v1.1.2-0.20260421131224-c46cbfe7bc6c/go.mod h1:sUsEwLtVPBlz0wPcysaolS+HVj9cOAt4jYhwE6J8dXg= -github.com/smartcontractkit/chainlink-sui v0.0.0-20260409184948-5b16fae57fe0 h1:nmuT5gKyTHpsHBEJMDM1C+v1d8jR/N8Xfg3KvqJUm8U= -github.com/smartcontractkit/chainlink-sui v0.0.0-20260409184948-5b16fae57fe0/go.mod h1:YQDu2RcdoAzI5xlhtpbjvaQQZwkUt/Q+IhLbP25M614= -github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.16 h1:pzrAgF6QFMQLS/kukXenLN87PCa48SEMlE7QvJxTOHs= -github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.16/go.mod h1:BALK9cj8sk12e15UF6uDhifHgIApa+6N11TcQfInEro= +github.com/smartcontractkit/chainlink-sui v0.0.0-20260429183453-39df0198aed8 h1:sWpTYRucOQQ/wXbKj52UE59JMMEq2Aq5g+sMdjYzfRM= +github.com/smartcontractkit/chainlink-sui v0.0.0-20260429183453-39df0198aed8/go.mod h1:k1HSbHyPaQWPOj6lXDIAe04EuwbC5ge1nK+cpG2E8hE= +github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.1 h1:wZd5hIQRcQaq3FgW1lg/4ilk68Id6cxYKFNU9iTnugs= +github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.1/go.mod h1:wxgGfrJpzIdC1wyMJEGOfN4H4yPQTZD/DdrMRBxA0io= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 h1:RwZXxdIAOyjp6cwc9Quxgr38k8r7ACz+Lxh9o/A6oH0= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= -github.com/smartcontractkit/chainlink-ton v0.0.0-20260415120434-cecc380f8d87 h1:NgA2+Q0wfHicP/QeY1hgULQ1ZBk1sgBpOJi3GpxfjE8= -github.com/smartcontractkit/chainlink-ton v0.0.0-20260415120434-cecc380f8d87/go.mod h1:UmQdvE8BtbLdoOFY0+Adqoc7HT1Hd1bbFY/yymuM0NU= +github.com/smartcontractkit/chainlink-ton v1.0.5-0.20260520103847-15ca4de9dba9 h1:KFu4Hj88Bx7hftWpDnam8TcdYHX8ga1oW5aT7SfP4CQ= +github.com/smartcontractkit/chainlink-ton v1.0.5-0.20260520103847-15ca4de9dba9/go.mod h1:4e/rmLNsaA39KZYQ9BvBbyf2fMsYtf7Da/FX9YEwgtw= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20260408092456-3c6369888d4a h1:Xu8iBnBQEibWIXTCwKYf8okXjFtzJ0KochjL03h+T40= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20260408092456-3c6369888d4a/go.mod h1:1eaXR+Fe6TlpP+CKXozfYlFM8QgN/N5C7OMvTRWNT8I= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014143056-a0c6328c91e9 h1:/Q1gD5gI0glBMztVH9XUVci3aOy8h+qTDV6o42MsqMM= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014143056-a0c6328c91e9/go.mod h1:ea1LESxlSSOgc2zZBqf1RTkXTMthHaspdqUHd7W4lF0= -github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5 h1:5Vh1ulQMReXwg8qgT5by12MqJt+Dc9y3Y/df02QeLuk= -github.com/smartcontractkit/chainlink/deployment v0.0.0-20260422181348-efa818697ce5/go.mod h1:pMBypeRoT8Nnb5gbkW6kIs+fBVsc/OzUx2eR9MtyFaw= -github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5 h1:G2TvBxwvzLMIU5hMPsQlVAY027mT1P/VTiWA6yegmpM= -github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260422181348-efa818697ce5/go.mod h1:8/a/Nlo4ZndgBEumXoE+gvBA5LH40BfDM+6SZa4Q89M= +github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8 h1:xPDpOmxTlT2RW+pPUElGa0/y02V/MAHIPD8DEtEBLfE= +github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8/go.mod h1:WL7W/YQO5pQ1Nexm4lvd/SztM2OzbhaIhJKyrfMU8QQ= +github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8 h1:naZxYMHsgi+JVChySLtaqAfgRM3Az2+HpvIzWNFcPRo= +github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8/go.mod h1:8Ppr/wQrc9GVwQow3Tw2JsNjNjqMhZTyU32cm5OFMzo= +github.com/smartcontractkit/cld-changesets v0.4.0 h1:S6yNRj6FssyyKbxLHTbC9X9U4qsph17xiiBBT6DGyNE= +github.com/smartcontractkit/cld-changesets v0.4.0/go.mod h1:4wOfnbSP8Ior/75QWLbtDntamSA/81SYcXzctBSx9CY= github.com/smartcontractkit/cre-sdk-go v1.11.0 h1:E3MG0j8O9qDv6lDz71HPD3/WRKh/PX2/hfxO1+9YL2w= github.com/smartcontractkit/cre-sdk-go v1.11.0/go.mod h1:8SDE/e+eDAFpbRjRyKnIalUkQk9BcNbo2aLnda9BM48= github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12 h1:cNUnDjtg88OXcmNQRbGgpRZ5/ZU5SqLp0wrHCxHn4M8= @@ -1409,12 +1242,12 @@ github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgH github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= -github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d h1:PvXor5Fjer7FIONSqYXbpd1LkA14hWrlAyxXzOrC9t8= -github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d/go.mod h1:PLdNK6GlqfxIWXzziPkU7dCAVlVFeYkyyW7AQY0R+4Q= -github.com/smartcontractkit/mcms v0.41.1 h1:rK5X7if29gRhL6yqpUwxwaLYV0CqgwSJivdDqEJGFv4= -github.com/smartcontractkit/mcms v0.41.1/go.mod h1:9AJhwHSVwV2mETizHBNfEF9CemL/Fmf0yPxNGdTtL/0= -github.com/smartcontractkit/quarantine v0.0.0-20250909213106-ece491bef618 h1:rN8PnOZj53L70zlm1aYz1k14lXNCt7NoV666TDfcTJA= -github.com/smartcontractkit/quarantine v0.0.0-20250909213106-ece491bef618/go.mod h1:iwy4yWFuK+1JeoIRTaSOA9pl+8Kf//26zezxEXrAQEQ= +github.com/smartcontractkit/libocr v0.0.0-20260508200755-99940c85383c h1:meDKygNIR0tdT3Xmxe9NwyuiaCCDL0a9COqZ+4cL89g= +github.com/smartcontractkit/libocr v0.0.0-20260508200755-99940c85383c/go.mod h1:PLdNK6GlqfxIWXzziPkU7dCAVlVFeYkyyW7AQY0R+4Q= +github.com/smartcontractkit/mcms v0.45.0 h1:6Zx80KKLQOPXLhvrRkJKClANnBJmPa/r69CV5UUq/0I= +github.com/smartcontractkit/mcms v0.45.0/go.mod h1:/uOE69QmF7opKlaBNzp8djypmBoYSW0mk4V2iKWP418= +github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9 h1:MOEuXYogv+RStASb8dWsyescu/xkigSi/Sv45NEjV7A= +github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9/go.mod h1:iwy4yWFuK+1JeoIRTaSOA9pl+8Kf//26zezxEXrAQEQ= github.com/smartcontractkit/smdkg v0.0.0-20251029093710-c38905e58aeb h1:kLHdQQkijaPGsBbtV2rJgpzVpQ96e7T10pzjNlWfK8U= github.com/smartcontractkit/smdkg v0.0.0-20251029093710-c38905e58aeb/go.mod h1:4s5hj/nlMF9WV+T5Uhy4n9IYpRpzfJzT+vTKkNT7T+Y= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de h1:n0w0rKF+SVM+S3WNlup6uabXj2zFlFNfrlsKCMMb/co= @@ -1423,35 +1256,23 @@ github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c h1:S github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c/go.mod h1:NSc7hgOQbXG3DAwkOdWnZzLTZENXSwDJ7Va1nBp0YU0= github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945 h1:zxcODLrFytOKmAd8ty8S/XK6WcIEJEgRBaL7sY/7l4Y= github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945/go.mod h1:m3pdp17i4bD50XgktkzWetcV5yaLsi7Gunbv4ZgN6qg= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/stellar/go-stellar-sdk v0.1.0 h1:MfV7dv4k6xQQrWeKT7npWyKhjoayphLVGwXKtTLNeH8= -github.com/stellar/go-stellar-sdk v0.1.0/go.mod h1:fZPcxQZw1I0zZ+X76uFcVPqmQCaYbWc87lDFW/kQJaY= -github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE= -github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= +github.com/stellar/go-stellar-sdk v0.5.0 h1:xpOO+ZTyvGz54wTm7pwl2Gf1e6lZl0ExrJ/tKb+Roj4= +github.com/stellar/go-stellar-sdk v0.5.0/go.mod h1:tLKAQPxa2I5UvGMabBbUXcY3fmgYnfDudrMeK7CDX4w= +github.com/stellar/go-xdr v0.0.0-20260423131911-a87d4d0789c3 h1:/NnsQpfVYPFr5Xm/V7EOblI8P05zewwkJLOlLItsW7U= +github.com/stellar/go-xdr v0.0.0-20260423131911-a87d4d0789c3/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863 h1:ba4VRWSkRzgdP5hB5OxexIzBXZbSwgcw8bEu06ivGQI= github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863/go.mod h1:oPTjPNrRucLv9mU27iNPj6n0CWWcNFhoXFOLVGJwHCA= github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo= @@ -1461,8 +1282,9 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1479,7 +1301,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE= @@ -1491,20 +1312,16 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= -github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= -github.com/testcontainers/testcontainers-go v0.41.0 h1:mfpsD0D36YgkxGj2LrIyxuwQ9i2wCKAD+ESsYM1wais= -github.com/testcontainers/testcontainers-go v0.41.0/go.mod h1:pdFrEIfaPl24zmBjerWTTYaY0M6UHsqA1YSvsoU40MI= +github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= github.com/testcontainers/testcontainers-go/modules/postgres v0.41.0 h1:AOtFXssrDlLm84A2sTTR/AhvJiYbrIuCO59d+Ro9Tb0= github.com/testcontainers/testcontainers-go/modules/postgres v0.41.0/go.mod h1:k2a09UKhgSp6vNpliIY0QSgm4Hi7GXVTzWvWgUemu/8= github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a h1:YuO+afVc3eqrjiCUizNCxI53bl/BnPiVwXqLzqYTqgU= github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a/go.mod h1:/sfW47zCZp9FrtGcWyo1VjbgDaodxX9ovZvgLb/MxaA= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= -github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1512,15 +1329,12 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= -github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= +github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= -github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= +github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -1544,15 +1358,11 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdrpp/goxdr v0.1.1 h1:E1B2c6E8eYhOVyd7yEpOyopzTPirUeF6mVOfXfGyJyc= github.com/xdrpp/goxdr v0.1.1/go.mod h1:dXo1scL/l6s7iME1gxHWo2XCppbHEKZS7m/KyYWkNzA= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= @@ -1561,7 +1371,6 @@ github.com/xssnick/tonutils-go v1.14.1 h1:zV/iVYl/h3hArS+tPsd9XrSFfGert3r21caMlt github.com/xssnick/tonutils-go v1.14.1/go.mod h1:68xwWjpoGGqiTbLJ0gT63sKu1Z1moCnDLLzA+DKanIg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -1570,8 +1379,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6 h1:VRdX3Gn/I7ITbzUY4ZNfgn65tdQM9Zhf2b7KP0HZllk= github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6/go.mod h1:NWNlQS21isOsSsn+hLRAPpiuv+3P+LcdaZNuRt2T5Yo= @@ -1588,24 +1397,16 @@ go.dedis.ch/kyber/v3 v3.1.0/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1 go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= -go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= +go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0 h1:1f31+6grJmV3X4lxcEvUy13i5/kfDw1nJZwhd8mA4tg= go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0/go.mod h1:1P/02zM3OwkX9uki+Wmxw3a5GVb6KUXRsa7m7bOC9Fg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= @@ -1666,40 +1467,35 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= +go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1709,70 +1505,37 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1785,7 +1548,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -1796,12 +1558,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -1811,7 +1571,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1819,45 +1578,28 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1874,7 +1616,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1897,13 +1638,10 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= -golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= @@ -1911,11 +1649,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1926,53 +1662,23 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1980,8 +1686,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1989,73 +1695,32 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 h1:3WsB1FAbiRIf2tOxscWKs3pQBD9he1NsrnbhMuWfekc= +google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60/go.mod h1:7yoXV7RIh5gblj/xVYoogxAWvA9wUeVbpsK/M694l00= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2085,13 +1750,10 @@ gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -2108,11 +1770,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= @@ -2129,11 +1788,8 @@ k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt nhooyr.io/websocket v1.8.14 h1:3gKlV2P9bMu1U85zh1T2yLOmseFbRTbnYVOprNSEYKQ= nhooyr.io/websocket v1.8.14/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= -pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/internal/settings/cld_settings.go b/internal/settings/cld_settings.go index 68b1c7e9..263748fd 100644 --- a/internal/settings/cld_settings.go +++ b/internal/settings/cld_settings.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" crecontracts "github.com/smartcontractkit/chainlink/deployment/cre/contracts" mcmstypes "github.com/smartcontractkit/mcms/types" ) @@ -80,6 +79,7 @@ func GetMCMSConfig(settings *Settings, chainSelector uint64) (*crecontracts.MCMS return nil, fmt.Errorf("failed to parse valid duration: %w", err) } mcmsAction := mcmstypes.TimelockAction(strings.ToLower(settings.CLDSettings.MCMSSettings.MCMSAction)) + validDur := mcmstypes.NewDuration(validDuration) return &crecontracts.MCMSConfig{ MinDelay: minDelay, @@ -88,6 +88,6 @@ func GetMCMSConfig(settings *Settings, chainSelector uint64) (*crecontracts.MCMS TimelockQualifierPerChain: map[uint64]string{ chainSelector: settings.CLDSettings.MCMSSettings.TimelockQualifier, }, - ValidDuration: commonconfig.MustNewDuration(validDuration), + ValidDuration: &validDur, }, nil } From 31b81c8bddbb66f90c686a23378ea7d3750090eb Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 18:56:30 +0400 Subject: [PATCH 18/29] Refactor: Add context parameter to WorkflowRegistryV2Client methods (#450) This update introduces a context parameter to various methods in the WorkflowRegistryV2Client and related handlers, enhancing the ability to manage request lifetimes and cancellations. The changes include updates to transaction execution, owner linking, and workflow management functions, ensuring that context is consistently passed through the call stack. This refactor improves the overall robustness and responsiveness of the client interactions. --- cmd/account/link_key/link_key.go | 34 +++-- cmd/account/unlink_key/unlink_key.go | 30 +++-- cmd/client/client_factory.go | 7 +- cmd/client/eth_client.go | 6 +- cmd/client/tx.go | 11 +- cmd/client/workflow_registry_v2_client.go | 120 ++++++++++-------- .../workflow_registry_v2_client_test.go | 5 +- cmd/secrets/common/handler.go | 20 +-- cmd/secrets/common/handler_test.go | 14 +- cmd/secrets/common/test_helpers.go | 5 +- cmd/secrets/create/create.go | 4 +- cmd/secrets/delete/delete.go | 12 +- cmd/secrets/execute/execute.go | 4 +- cmd/secrets/list/list.go | 12 +- cmd/secrets/update/update.go | 4 +- .../registry_activate_strategy_onchain.go | 8 +- cmd/workflow/common/workflow_list.go | 6 +- .../registry_delete_strategy_onchain.go | 6 +- cmd/workflow/deploy/auto_link.go | 6 +- cmd/workflow/deploy/deploy_test.go | 10 +- cmd/workflow/deploy/limits.go | 13 +- cmd/workflow/deploy/register.go | 2 +- cmd/workflow/deploy/register_test.go | 4 +- .../registry_deploy_strategy_onchain.go | 5 +- .../pause/registry_pause_strategy_onchain.go | 6 +- .../chainsim/simulated_client_factory.go | 4 +- .../simulated_workflow_registry_contract.go | 9 +- internal/testutil/workflow/test_workflow.go | 3 +- 28 files changed, 211 insertions(+), 159 deletions(-) diff --git a/cmd/account/link_key/link_key.go b/cmd/account/link_key/link_key.go index 6be66976..d9899047 100644 --- a/cmd/account/link_key/link_key.go +++ b/cmd/account/link_key/link_key.go @@ -58,13 +58,13 @@ type initiateLinkingResponse struct { FunctionArgs []string `json:"functionArgs"` } -func Exec(ctx *runtime.Context, in Inputs) error { +func Exec(parentCtx context.Context, ctx *runtime.Context, in Inputs) error { h := newHandler(ctx, nil) if err := h.ValidateInputs(in); err != nil { return err } - return h.Execute(in) + return h.Execute(parentCtx, in) } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -83,7 +83,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { return err } - return h.Execute(inputs) + return h.Execute(cmd.Context(), inputs) }, } settings.AddTxnTypeFlags(cmd) @@ -101,6 +101,7 @@ type handler struct { stdin io.Reader environmentSet *environments.EnvironmentSet wrc *client.WorkflowRegistryV2Client + execCtx context.Context validated bool @@ -109,28 +110,28 @@ type handler struct { } func newHandler(ctx *runtime.Context, stdin io.Reader) *handler { - h := handler{ + return &handler{ settings: ctx.Settings, credentials: ctx.Credentials, clientFactory: ctx.ClientFactory, log: ctx.Logger, environmentSet: ctx.EnvironmentSet, stdin: stdin, - wg: sync.WaitGroup{}, - wrcErr: nil, } +} + +func (h *handler) initWorkflowRegistryClient() error { h.wg.Add(1) go func() { defer h.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) if err != nil { h.wrcErr = fmt.Errorf("failed to create workflow registry client: %w", err) return } h.wrc = wrc }() - - return &h + return nil } func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { @@ -154,11 +155,16 @@ func (h *handler) ValidateInputs(in Inputs) error { return nil } -func (h *handler) Execute(in Inputs) error { +func (h *handler) Execute(ctx context.Context, in Inputs) error { if !h.validated { return fmt.Errorf("inputs not validated") } + h.execCtx = ctx + if err := h.initWorkflowRegistryClient(); err != nil { + return err + } + h.displayDetails() if in.WorkflowOwnerLabel == "" { @@ -191,7 +197,7 @@ func (h *handler) Execute(in Inputs) error { ui.Dim(fmt.Sprintf("Starting linking: owner=%s, label=%s", in.WorkflowOwner, in.WorkflowOwnerLabel)) - resp, err := h.callInitiateLinking(context.Background(), in) + resp, err := h.callInitiateLinking(h.execCtx, in) if err != nil { return err } @@ -296,10 +302,10 @@ func (h *handler) linkOwner(resp initiateLinkingResponse) error { } ownerAddr := common.HexToAddress(h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress) - if err := h.wrc.CanLinkOwner(ownerAddr, ts, proofBytes, sigBytes); err != nil { + if err := h.wrc.CanLinkOwner(h.execCtx, ownerAddr, ts, proofBytes, sigBytes); err != nil { return fmt.Errorf("link request verification failed: %w", err) } - txOut, err := h.wrc.LinkOwner(ts, proofBytes, sigBytes) + txOut, err := h.wrc.LinkOwner(h.execCtx, ts, proofBytes, sigBytes) if err != nil { return fmt.Errorf("LinkOwner failed: %w", err) } @@ -388,7 +394,7 @@ func (h *handler) checkIfAlreadyLinked() (bool, error) { ownerAddr := common.HexToAddress(h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress) ui.Dim("Checking existing registrations...") - linked, err := h.wrc.IsOwnerLinked(ownerAddr) + linked, err := h.wrc.IsOwnerLinked(h.execCtx, ownerAddr) if err != nil { return false, fmt.Errorf("failed to check owner link status: %w", err) } diff --git a/cmd/account/unlink_key/unlink_key.go b/cmd/account/unlink_key/unlink_key.go index 3c50e373..05afc0ca 100644 --- a/cmd/account/unlink_key/unlink_key.go +++ b/cmd/account/unlink_key/unlink_key.go @@ -61,6 +61,7 @@ type handler struct { stdin io.Reader environmentSet *environments.EnvironmentSet wrc *client.WorkflowRegistryV2Client + execCtx context.Context validated bool @@ -83,7 +84,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { if err := h.ValidateInputs(in); err != nil { return err } - return h.Execute(in) + return h.Execute(cmd.Context(), in) }, } settings.AddTxnTypeFlags(cmd) @@ -92,28 +93,28 @@ func New(runtimeContext *runtime.Context) *cobra.Command { } func newHandler(ctx *runtime.Context, stdin io.Reader) *handler { - h := handler{ + return &handler{ settings: ctx.Settings, credentials: ctx.Credentials, clientFactory: ctx.ClientFactory, log: ctx.Logger, environmentSet: ctx.EnvironmentSet, stdin: stdin, - wg: sync.WaitGroup{}, - wrcErr: nil, } +} + +func (h *handler) initWorkflowRegistryClient() error { h.wg.Add(1) go func() { defer h.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) if err != nil { h.wrcErr = fmt.Errorf("failed to create workflow registry client: %w", err) return } h.wrc = wrc }() - - return &h + return nil } func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { @@ -137,11 +138,16 @@ func (h *handler) ValidateInputs(in Inputs) error { return nil } -func (h *handler) Execute(in Inputs) error { +func (h *handler) Execute(ctx context.Context, in Inputs) error { if !h.validated { return fmt.Errorf("inputs not validated") } + h.execCtx = ctx + if err := h.initWorkflowRegistryClient(); err != nil { + return err + } + h.displayDetails() ui.Dim(fmt.Sprintf("Starting unlinking: owner=%s", in.WorkflowOwner)) @@ -181,7 +187,7 @@ func (h *handler) Execute(in Inputs) error { } } - resp, err := h.callInitiateUnlinking(context.Background(), in) + resp, err := h.callInitiateUnlinking(h.execCtx, in) if err != nil { return err } @@ -255,10 +261,10 @@ func (h *handler) unlinkOwner(owner string, resp initiateUnlinkingResponse) erro } addr := common.HexToAddress(owner) - if err := h.wrc.CanUnlinkOwner(addr, ts, sigBytes); err != nil { + if err := h.wrc.CanUnlinkOwner(h.execCtx, addr, ts, sigBytes); err != nil { return fmt.Errorf("unlink request verification failed: %w", err) } - txOut, err := h.wrc.UnlinkOwner(addr, ts, sigBytes) + txOut, err := h.wrc.UnlinkOwner(h.execCtx, addr, ts, sigBytes) if err != nil { return fmt.Errorf("UnlinkOwner failed: %w", err) } @@ -346,7 +352,7 @@ func (h *handler) unlinkOwner(owner string, resp initiateUnlinkingResponse) erro func (h *handler) checkIfAlreadyLinked() (bool, error) { ownerAddr := common.HexToAddress(h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress) - linked, err := h.wrc.IsOwnerLinked(ownerAddr) + linked, err := h.wrc.IsOwnerLinked(h.execCtx, ownerAddr) if err != nil { return false, fmt.Errorf("failed to check owner link status: %w", err) } diff --git a/cmd/client/client_factory.go b/cmd/client/client_factory.go index 82e75882..bb496a82 100644 --- a/cmd/client/client_factory.go +++ b/cmd/client/client_factory.go @@ -1,6 +1,7 @@ package client import ( + "context" "fmt" "strings" @@ -15,7 +16,7 @@ import ( ) type Factory interface { - NewWorkflowRegistryV2Client() (*WorkflowRegistryV2Client, error) + NewWorkflowRegistryV2Client(ctx context.Context) (*WorkflowRegistryV2Client, error) GetTxType() TxType GetSkipConfirmation() bool } @@ -32,7 +33,7 @@ func NewFactory(logger *zerolog.Logger, viper *viper.Viper) Factory { } } -func (f *factoryImpl) NewWorkflowRegistryV2Client() (*WorkflowRegistryV2Client, error) { +func (f *factoryImpl) NewWorkflowRegistryV2Client(ctx context.Context) (*WorkflowRegistryV2Client, error) { environmentSet, err := environments.New() if err != nil { return nil, fmt.Errorf("failed to load environment details: %w", err) @@ -60,7 +61,7 @@ func (f *factoryImpl) NewWorkflowRegistryV2Client() (*WorkflowRegistryV2Client, txcConfig, ) - typeAndVersion, err := workflowRegistryV2Client.TypeAndVersion() + typeAndVersion, err := workflowRegistryV2Client.TypeAndVersion(ctx) if err != nil { return workflowRegistryV2Client, fmt.Errorf("failed to get type and version of workflow registry contract at %s: %w", environmentSet.WorkflowRegistryAddress, err) } diff --git a/cmd/client/eth_client.go b/cmd/client/eth_client.go index dad74dca..7ee1a785 100644 --- a/cmd/client/eth_client.go +++ b/cmd/client/eth_client.go @@ -184,14 +184,16 @@ func readSethConfigFromFile(configPath string) (*seth.Config, error) { } func getChainID(rpcURL string) (uint64, error) { - client, err := rpc.DialContext(context.Background(), rpcURL) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + client, err := rpc.DialContext(ctx, rpcURL) if err != nil { return 0, err } defer client.Close() var chainID string - err = client.CallContext(context.Background(), &chainID, "eth_chainId") + err = client.CallContext(ctx, &chainID, "eth_chainId") if err != nil { return 0, err } diff --git a/cmd/client/tx.go b/cmd/client/tx.go index 1d7715cb..d280eaa0 100644 --- a/cmd/client/tx.go +++ b/cmd/client/tx.go @@ -1,6 +1,7 @@ package client import ( + "context" "encoding/json" "errors" "fmt" @@ -119,7 +120,7 @@ type RawTx struct { // return txOpts, nil //} -func (c *TxClient) executeTransactionByTxType(txFn func(opts *bind.TransactOpts) (*types.Transaction, error), funName string, validationEvent string, args ...any) (TxOutput, error) { +func (c *TxClient) executeTransactionByTxType(ctx context.Context, txFn func(opts *bind.TransactOpts) (*types.Transaction, error), funName string, validationEvent string, args ...any) (TxOutput, error) { switch c.config.TxType { case Regular: simulateTx, err := txFn(cmdCommon.SimTransactOpts()) @@ -138,7 +139,7 @@ func (c *TxClient) executeTransactionByTxType(txFn func(opts *bind.TransactOpts) Value: simulateTx.Value(), Data: simulateTx.Data(), } - estimatedGas, gasErr := c.EthClient.Client.EstimateGas(c.EthClient.Context, msg) + estimatedGas, gasErr := c.EthClient.Client.EstimateGas(ctx, msg) if gasErr != nil { c.Logger.Warn().Err(gasErr).Msg("Failed to estimate gas usage") } @@ -159,7 +160,7 @@ func (c *TxClient) executeTransactionByTxType(txFn func(opts *bind.TransactOpts) // Calculate and print total cost for sending the transaction on-chain if gasErr == nil { - gasPriceWei, gasPriceErr := c.EthClient.Client.SuggestGasPrice(c.EthClient.Context) + gasPriceWei, gasPriceErr := c.EthClient.Client.SuggestGasPrice(ctx) if gasPriceErr != nil { c.Logger.Warn().Err(gasPriceErr).Msg("Failed to fetch gas price") } else { @@ -189,7 +190,9 @@ func (c *TxClient) executeTransactionByTxType(txFn func(opts *bind.TransactOpts) spinner := ui.NewSpinner() spinner.Start("Submitting transaction...") - decodedTx, err := c.EthClient.Decode(txFn(c.EthClient.NewTXOpts())) + txOpts := c.EthClient.NewTXOpts() + txOpts.Context = ctx + decodedTx, err := c.EthClient.Decode(txFn(txOpts)) if err != nil { spinner.Stop() return TxOutput{Type: Regular}, err diff --git a/cmd/client/workflow_registry_v2_client.go b/cmd/client/workflow_registry_v2_client.go index a8dd6c5f..507885db 100644 --- a/cmd/client/workflow_registry_v2_client.go +++ b/cmd/client/workflow_registry_v2_client.go @@ -1,6 +1,7 @@ package client import ( + "context" "encoding/hex" "errors" "fmt" @@ -60,7 +61,19 @@ func NewWorkflowRegistryV2Client(logger *zerolog.Logger, ethClient *seth.Client, } } -func (wrc *WorkflowRegistryV2Client) LinkOwner(validityTimestamp *big.Int, proof [32]byte, signature []byte) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) callOpts(ctx context.Context) *bind.CallOpts { + opts := wrc.EthClient.NewCallOpts() + opts.Context = ctx + return opts +} + +func (wrc *WorkflowRegistryV2Client) txOpts(ctx context.Context) *bind.TransactOpts { + opts := wrc.EthClient.NewTXOpts() + opts.Context = ctx + return opts +} + +func (wrc *WorkflowRegistryV2Client) LinkOwner(ctx context.Context, validityTimestamp *big.Int, proof [32]byte, signature []byte) (*TxOutput, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -73,7 +86,7 @@ func (wrc *WorkflowRegistryV2Client) LinkOwner(validityTimestamp *big.Int, proof txFn := func(opts *bind.TransactOpts) (*types.Transaction, error) { return contract.LinkOwner(opts, validityTimestamp, proof, signature) } - txOut, err := wrc.executeTransactionByTxType(txFn, "LinkOwner", "OwnershipLinkUpdated", validityTimestamp, proof, signature) + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "LinkOwner", "OwnershipLinkUpdated", validityTimestamp, proof, signature) if err != nil { wrc.Logger.Error(). Str("contract", contract.Address().Hex()). @@ -92,7 +105,7 @@ func (wrc *WorkflowRegistryV2Client) LinkOwner(validityTimestamp *big.Int, proof return &txOut, nil } -func (wrc *WorkflowRegistryV2Client) UnlinkOwner(owner common.Address, validityTimestamp *big.Int, signature []byte) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) UnlinkOwner(ctx context.Context, owner common.Address, validityTimestamp *big.Int, signature []byte) (*TxOutput, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -105,7 +118,7 @@ func (wrc *WorkflowRegistryV2Client) UnlinkOwner(owner common.Address, validityT txFn := func(opts *bind.TransactOpts) (*types.Transaction, error) { return contract.UnlinkOwner(opts, owner, validityTimestamp, signature) } - txOut, err := wrc.executeTransactionByTxType(txFn, "UnlinkOwner", "OwnershipLinkUpdated", owner, validityTimestamp, signature) + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "UnlinkOwner", "OwnershipLinkUpdated", owner, validityTimestamp, signature) if err != nil { wrc.Logger.Error(). Str("contract", contract.Address().Hex()). @@ -124,7 +137,7 @@ func (wrc *WorkflowRegistryV2Client) UnlinkOwner(owner common.Address, validityT return &txOut, nil } -func (wrc *WorkflowRegistryV2Client) UpdateAllowedSigners(signers []common.Address, allowed bool) error { +func (wrc *WorkflowRegistryV2Client) UpdateAllowedSigners(ctx context.Context, signers []common.Address, allowed bool) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -135,7 +148,7 @@ func (wrc *WorkflowRegistryV2Client) UpdateAllowedSigners(signers []common.Addre } tx, err := wrc.EthClient.Decode( - contract.UpdateAllowedSigners(wrc.EthClient.NewTXOpts(), signers, allowed), + contract.UpdateAllowedSigners(wrc.txOpts(ctx), signers, allowed), ) if err != nil { wrc.Logger.Error(). @@ -162,7 +175,7 @@ func (wrc *WorkflowRegistryV2Client) UpdateAllowedSigners(signers []common.Addre return nil } -func (wrc *WorkflowRegistryV2Client) SetDonLimit(donFamily string, limit uint32, userDefaultLimit uint32) error { +func (wrc *WorkflowRegistryV2Client) SetDonLimit(ctx context.Context, donFamily string, limit uint32, userDefaultLimit uint32) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -173,7 +186,7 @@ func (wrc *WorkflowRegistryV2Client) SetDonLimit(donFamily string, limit uint32, } tx, err := wrc.EthClient.Decode( - contract.SetDONLimit(wrc.EthClient.NewTXOpts(), donFamily, limit, userDefaultLimit), + contract.SetDONLimit(wrc.txOpts(ctx), donFamily, limit, userDefaultLimit), ) if err != nil { wrc.Logger.Error(). @@ -196,7 +209,7 @@ func (wrc *WorkflowRegistryV2Client) SetDonLimit(donFamily string, limit uint32, return nil } -func (wrc *WorkflowRegistryV2Client) SetDONOverride(donFamily [32]byte, limit uint32, enabled bool) error { +func (wrc *WorkflowRegistryV2Client) SetDONOverride(ctx context.Context, donFamily [32]byte, limit uint32, enabled bool) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -207,7 +220,7 @@ func (wrc *WorkflowRegistryV2Client) SetDONOverride(donFamily [32]byte, limit ui } tx, err := wrc.EthClient.Decode( - contract.SetUserDONOverride(wrc.EthClient.NewTXOpts(), common.Address{}, common.Hash(donFamily).Hex(), limit, enabled), + contract.SetUserDONOverride(wrc.txOpts(ctx), common.Address{}, common.Hash(donFamily).Hex(), limit, enabled), ) if err != nil { wrc.Logger.Error(). @@ -230,7 +243,7 @@ func (wrc *WorkflowRegistryV2Client) SetDONOverride(donFamily [32]byte, limit ui return nil } -func (wrc *WorkflowRegistryV2Client) SetDefaults(maxPerDON, maxPerUserDON uint32) error { +func (wrc *WorkflowRegistryV2Client) SetDefaults(ctx context.Context, maxPerDON, maxPerUserDON uint32) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -261,7 +274,7 @@ func (wrc *WorkflowRegistryV2Client) SetDefaults(maxPerDON, maxPerUserDON uint32 return nil } -func (wrc *WorkflowRegistryV2Client) SetUserDONOverride(user common.Address, donFamily [32]byte, limit uint32, enabled bool) error { +func (wrc *WorkflowRegistryV2Client) SetUserDONOverride(ctx context.Context, user common.Address, donFamily [32]byte, limit uint32, enabled bool) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -272,7 +285,7 @@ func (wrc *WorkflowRegistryV2Client) SetUserDONOverride(user common.Address, don } tx, err := wrc.EthClient.Decode( - contract.SetUserDONOverride(wrc.EthClient.NewTXOpts(), user, common.Hash(donFamily).Hex(), limit, enabled), + contract.SetUserDONOverride(wrc.txOpts(ctx), user, common.Hash(donFamily).Hex(), limit, enabled), ) if err != nil { wrc.Logger.Error(). @@ -295,7 +308,7 @@ func (wrc *WorkflowRegistryV2Client) SetUserDONOverride(user common.Address, don return nil } -func (wrc *WorkflowRegistryV2Client) CanLinkOwner(owner common.Address, validityTimestamp *big.Int, proof [32]byte, signature []byte) error { +func (wrc *WorkflowRegistryV2Client) CanLinkOwner(ctx context.Context, owner common.Address, validityTimestamp *big.Int, proof [32]byte, signature []byte) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -305,7 +318,7 @@ func (wrc *WorkflowRegistryV2Client) CanLinkOwner(owner common.Address, validity } _, err = callContractMethodV2(wrc, func() (struct{}, error) { - return struct{}{}, contract.CanLinkOwner(wrc.EthClient.NewCallOpts(), owner, validityTimestamp, proof, signature) + return struct{}{}, contract.CanLinkOwner(wrc.callOpts(ctx), owner, validityTimestamp, proof, signature) }) if err != nil { wrc.Logger.Error(). @@ -322,7 +335,7 @@ func (wrc *WorkflowRegistryV2Client) CanLinkOwner(owner common.Address, validity return nil } -func (wrc *WorkflowRegistryV2Client) CanUnlinkOwner(owner common.Address, validityTimestamp *big.Int, signature []byte) error { +func (wrc *WorkflowRegistryV2Client) CanUnlinkOwner(ctx context.Context, owner common.Address, validityTimestamp *big.Int, signature []byte) error { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -332,7 +345,7 @@ func (wrc *WorkflowRegistryV2Client) CanUnlinkOwner(owner common.Address, validi } _, err = callContractMethodV2(wrc, func() (struct{}, error) { - return struct{}{}, contract.CanUnlinkOwner(wrc.EthClient.NewCallOpts(), owner, validityTimestamp, signature) + return struct{}{}, contract.CanUnlinkOwner(wrc.callOpts(ctx), owner, validityTimestamp, signature) }) if err != nil { wrc.Logger.Error(). @@ -349,72 +362,72 @@ func (wrc *WorkflowRegistryV2Client) CanUnlinkOwner(owner common.Address, validi return nil } -func (wrc *WorkflowRegistryV2Client) GetLinkedOwners(start, batchSize *big.Int) ([]common.Address, error) { +func (wrc *WorkflowRegistryV2Client) GetLinkedOwners(ctx context.Context, start, batchSize *big.Int) ([]common.Address, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetLinkedOwners") return nil, err } - addrs, err := contract.GetLinkedOwners(wrc.EthClient.NewCallOpts(), start, batchSize) + addrs, err := contract.GetLinkedOwners(wrc.callOpts(ctx), start, batchSize) if err != nil { wrc.Logger.Error().Err(err).Msg("GetLinkedOwners call failed") } return addrs, err } -func (wrc *WorkflowRegistryV2Client) GetMaxWorkflowsPerDON(donFamily [32]byte) (uint32, error) { +func (wrc *WorkflowRegistryV2Client) GetMaxWorkflowsPerDON(ctx context.Context, donFamily [32]byte) (uint32, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetMaxWorkflowsPerDON") return 0, err } - val, err := contract.GetMaxWorkflowsPerDON(wrc.EthClient.NewCallOpts(), common.Hash(donFamily).Hex()) + val, err := contract.GetMaxWorkflowsPerDON(wrc.callOpts(ctx), common.Hash(donFamily).Hex()) if err != nil { wrc.Logger.Error().Err(err).Msg("GetMaxWorkflowsPerDON call failed") } return val.MaxWorkflows, err } -func (wrc *WorkflowRegistryV2Client) GetMaxWorkflowsPerUserDON(user common.Address, donFamily [32]byte) (uint32, error) { +func (wrc *WorkflowRegistryV2Client) GetMaxWorkflowsPerUserDON(ctx context.Context, user common.Address, donFamily [32]byte) (uint32, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetMaxWorkflowsPerUserDON") return 0, err } - val, err := contract.GetMaxWorkflowsPerUserDON(wrc.EthClient.NewCallOpts(), user, common.Hash(donFamily).Hex()) + val, err := contract.GetMaxWorkflowsPerUserDON(wrc.callOpts(ctx), user, common.Hash(donFamily).Hex()) if err != nil { wrc.Logger.Error().Err(err).Msg("GetMaxWorkflowsPerUserDON call failed") } return val, err } -func (wrc *WorkflowRegistryV2Client) GetMaxWorkflowsPerUserDONByFamily(user common.Address, donFamily string) (uint32, error) { +func (wrc *WorkflowRegistryV2Client) GetMaxWorkflowsPerUserDONByFamily(ctx context.Context, user common.Address, donFamily string) (uint32, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetMaxWorkflowsPerUserDONByFamily") return 0, err } - val, err := contract.GetMaxWorkflowsPerUserDON(wrc.EthClient.NewCallOpts(), user, donFamily) + val, err := contract.GetMaxWorkflowsPerUserDON(wrc.callOpts(ctx), user, donFamily) if err != nil { wrc.Logger.Error().Err(err).Msg("GetMaxWorkflowsPerUserDONByFamily call failed") } return val, err } -func (wrc *WorkflowRegistryV2Client) IsAllowedSigner(signer common.Address) (bool, error) { +func (wrc *WorkflowRegistryV2Client) IsAllowedSigner(ctx context.Context, signer common.Address) (bool, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for IsAllowedSigner") return false, err } - ok, err := contract.IsAllowedSigner(wrc.EthClient.NewCallOpts(), signer) + ok, err := contract.IsAllowedSigner(wrc.callOpts(ctx), signer) if err != nil { wrc.Logger.Error().Err(err).Msg("IsAllowedSigner call failed") } return ok, err } -func (wrc *WorkflowRegistryV2Client) IsOwnerLinked(owner common.Address) (bool, error) { +func (wrc *WorkflowRegistryV2Client) IsOwnerLinked(ctx context.Context, owner common.Address) (bool, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -424,7 +437,7 @@ func (wrc *WorkflowRegistryV2Client) IsOwnerLinked(owner common.Address) (bool, } result, err := callContractMethodV2(wrc, func() (bool, error) { - return contract.IsOwnerLinked(wrc.EthClient.NewCallOpts(), owner) + return contract.IsOwnerLinked(wrc.callOpts(ctx), owner) }) if err != nil { wrc.Logger.Error(). @@ -442,33 +455,33 @@ func (wrc *WorkflowRegistryV2Client) IsOwnerLinked(owner common.Address) (bool, return result, nil } -func (wrc *WorkflowRegistryV2Client) TotalLinkedOwners() (*big.Int, error) { +func (wrc *WorkflowRegistryV2Client) TotalLinkedOwners(ctx context.Context) (*big.Int, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for TotalLinkedOwners") return nil, err } - total, err := contract.TotalLinkedOwners(wrc.EthClient.NewCallOpts()) + total, err := contract.TotalLinkedOwners(wrc.callOpts(ctx)) if err != nil { wrc.Logger.Error().Err(err).Msg("TotalLinkedOwners call failed") } return total, err } -func (wrc *WorkflowRegistryV2Client) TypeAndVersion() (string, error) { +func (wrc *WorkflowRegistryV2Client) TypeAndVersion(ctx context.Context) (string, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for TypeAndVersion") return "", err } - tv, err := contract.TypeAndVersion(wrc.EthClient.NewCallOpts()) + tv, err := contract.TypeAndVersion(wrc.callOpts(ctx)) if err != nil { wrc.Logger.Error().Err(err).Msg("TypeAndVersion call failed") } return tv, err } -func (wrc *WorkflowRegistryV2Client) UpsertWorkflow(params RegisterWorkflowV2Parameters) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) UpsertWorkflow(ctx context.Context, params RegisterWorkflowV2Parameters) (*TxOutput, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -492,7 +505,7 @@ func (wrc *WorkflowRegistryV2Client) UpsertWorkflow(params RegisterWorkflowV2Par params.KeepAlive, ) } - txOut, err := wrc.executeTransactionByTxType(txFn, "UpsertWorkflow", "WorkflowRegistered|WorkflowUpdated", + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "UpsertWorkflow", "WorkflowRegistered|WorkflowUpdated", params.WorkflowName, params.Tag, params.WorkflowID, @@ -513,7 +526,7 @@ func (wrc *WorkflowRegistryV2Client) UpsertWorkflow(params RegisterWorkflowV2Par return &txOut, nil } -func (wrc *WorkflowRegistryV2Client) GetWorkflow(owner common.Address, workflowName, tag string) (workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { +func (wrc *WorkflowRegistryV2Client) GetWorkflow(ctx context.Context, owner common.Address, workflowName, tag string) (workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetWorkflow") @@ -521,7 +534,7 @@ func (wrc *WorkflowRegistryV2Client) GetWorkflow(owner common.Address, workflowN } result, err := callContractMethodV2(wrc, func() (workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { - return contract.GetWorkflow(wrc.EthClient.NewCallOpts(), owner, workflowName, tag) + return contract.GetWorkflow(wrc.callOpts(ctx), owner, workflowName, tag) }) if err != nil { wrc.Logger.Error().Err(err).Msg("GetWorkflow call failed") @@ -529,7 +542,7 @@ func (wrc *WorkflowRegistryV2Client) GetWorkflow(owner common.Address, workflowN return result, err } -func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { +func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwnerAndName(ctx context.Context, owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetWorkflowListByOwnerAndName") @@ -537,7 +550,7 @@ func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwnerAndName(owner common. } result, err := callContractMethodV2(wrc, func() ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { - return contract.GetWorkflowListByOwnerAndName(wrc.EthClient.NewCallOpts(), owner, workflowName, start, limit) + return contract.GetWorkflowListByOwnerAndName(wrc.callOpts(ctx), owner, workflowName, start, limit) }) if err != nil { wrc.Logger.Error().Err(err).Msg("GetWorkflowListByOwnerAndName call failed") @@ -545,7 +558,7 @@ func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwnerAndName(owner common. return result, err } -func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwner(owner common.Address, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { +func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwner(ctx context.Context, owner common.Address, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error().Err(err).Msg("Failed to connect for GetWorkflowListByOwner") @@ -553,7 +566,7 @@ func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwner(owner common.Address } result, err := callContractMethodV2(wrc, func() ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { - return contract.GetWorkflowListByOwner(wrc.EthClient.NewCallOpts(), owner, start, limit) + return contract.GetWorkflowListByOwner(wrc.callOpts(ctx), owner, start, limit) }) if err != nil { wrc.Logger.Error().Err(err).Msg("GetWorkflowListByOwner call failed") @@ -562,6 +575,7 @@ func (wrc *WorkflowRegistryV2Client) GetWorkflowListByOwner(owner common.Address } func (wrc *WorkflowRegistryV2Client) CheckUserDonLimit( + ctx context.Context, owner common.Address, donFamily string, pending uint32, @@ -569,7 +583,7 @@ func (wrc *WorkflowRegistryV2Client) CheckUserDonLimit( const workflowStatusActive = uint8(0) const workflowListPageSize = int64(200) - maxAllowed, err := wrc.GetMaxWorkflowsPerUserDONByFamily(owner, donFamily) + maxAllowed, err := wrc.GetMaxWorkflowsPerUserDONByFamily(ctx, owner, donFamily) if err != nil { return fmt.Errorf("failed to fetch per-user workflow limit: %w", err) } @@ -579,7 +593,7 @@ func (wrc *WorkflowRegistryV2Client) CheckUserDonLimit( limit := big.NewInt(workflowListPageSize) for { - list, err := wrc.GetWorkflowListByOwner(owner, start, limit) + list, err := wrc.GetWorkflowListByOwner(ctx, owner, start, limit) if err != nil { return fmt.Errorf("failed to check active workflows for DON %s: %w", donFamily, err) } @@ -606,7 +620,7 @@ func (wrc *WorkflowRegistryV2Client) CheckUserDonLimit( return nil } -func (wrc *WorkflowRegistryV2Client) DeleteWorkflow(workflowID [32]byte) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) DeleteWorkflow(ctx context.Context, workflowID [32]byte) (*TxOutput, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -619,7 +633,7 @@ func (wrc *WorkflowRegistryV2Client) DeleteWorkflow(workflowID [32]byte) (*TxOut txFn := func(opts *bind.TransactOpts) (*types.Transaction, error) { return contract.DeleteWorkflow(opts, workflowID) } - txOut, err := wrc.executeTransactionByTxType(txFn, "DeleteWorkflow", "WorkflowDeleted", workflowID) + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "DeleteWorkflow", "WorkflowDeleted", workflowID) if err != nil { wrc.Logger.Error(). Str("contract", contract.Address().Hex()). @@ -630,7 +644,7 @@ func (wrc *WorkflowRegistryV2Client) DeleteWorkflow(workflowID [32]byte) (*TxOut return &txOut, nil } -func (wrc *WorkflowRegistryV2Client) BatchPauseWorkflows(workflowIDs [][32]byte) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) BatchPauseWorkflows(ctx context.Context, workflowIDs [][32]byte) (*TxOutput, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -646,7 +660,7 @@ func (wrc *WorkflowRegistryV2Client) BatchPauseWorkflows(workflowIDs [][32]byte) workflowIDs, ) } - txOut, err := wrc.executeTransactionByTxType(txFn, "BatchPauseWorkflows", "WorkflowStatusUpdated", workflowIDs) + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "BatchPauseWorkflows", "WorkflowStatusUpdated", workflowIDs) if err != nil { wrc.Logger.Error(). Str("contract", contract.Address().Hex()). @@ -657,7 +671,7 @@ func (wrc *WorkflowRegistryV2Client) BatchPauseWorkflows(workflowIDs [][32]byte) return &txOut, nil } -func (wrc *WorkflowRegistryV2Client) ActivateWorkflow(workflowID [32]byte, donFamily string) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) ActivateWorkflow(ctx context.Context, workflowID [32]byte, donFamily string) (*TxOutput, error) { contract, err := workflow_registry_v2_wrapper.NewWorkflowRegistry(wrc.ContractAddress, wrc.EthClient.Client) if err != nil { wrc.Logger.Error(). @@ -670,7 +684,7 @@ func (wrc *WorkflowRegistryV2Client) ActivateWorkflow(workflowID [32]byte, donFa txFn := func(opts *bind.TransactOpts) (*types.Transaction, error) { return contract.ActivateWorkflow(opts, workflowID, donFamily) } - txOut, err := wrc.executeTransactionByTxType(txFn, "ActivateWorkflow", "WorkflowActivated", workflowID, donFamily) + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "ActivateWorkflow", "WorkflowActivated", workflowID, donFamily) if err != nil { wrc.Logger.Error(). Str("contract", contract.Address().Hex()). @@ -715,7 +729,7 @@ func (wrc *WorkflowRegistryV2Client) validateReceiptAndEvent( // IsRequestAllowlisted queries the registry to check if a given (owner, requestDigest) is allowlisted. // requestDigestHex may include or omit the 0x prefix. -func (wrc *WorkflowRegistryV2Client) IsRequestAllowlisted(owner common.Address, digest [32]byte) (bool, error) { +func (wrc *WorkflowRegistryV2Client) IsRequestAllowlisted(ctx context.Context, owner common.Address, digest [32]byte) (bool, error) { var contract workflowRegistryV2Contract if wrc.Wr != nil { contract = wrc.Wr @@ -731,7 +745,7 @@ func (wrc *WorkflowRegistryV2Client) IsRequestAllowlisted(owner common.Address, var allowlisted bool _, err := callContractMethodV2(wrc, func() (string, error) { var callErr error - allowlisted, callErr = contract.IsRequestAllowlisted(wrc.EthClient.NewCallOpts(), owner, digest) + allowlisted, callErr = contract.IsRequestAllowlisted(wrc.callOpts(ctx), owner, digest) return "", callErr }) if err != nil { @@ -753,7 +767,7 @@ func (wrc *WorkflowRegistryV2Client) IsRequestAllowlisted(owner common.Address, // AllowlistRequest sends the request digest to the WorkflowRegistry allowlist with a default expiry of now + 10 minutes. // `requestDigestHex` should be the hex string produced by utils.CalculateRequestDigest(...), with or without "0x". -func (wrc *WorkflowRegistryV2Client) AllowlistRequest(requestDigest [32]byte, duration time.Duration) (*TxOutput, error) { +func (wrc *WorkflowRegistryV2Client) AllowlistRequest(ctx context.Context, requestDigest [32]byte, duration time.Duration) (*TxOutput, error) { var contract workflowRegistryV2Contract if wrc.Wr != nil { contract = wrc.Wr @@ -772,7 +786,7 @@ func (wrc *WorkflowRegistryV2Client) AllowlistRequest(requestDigest [32]byte, du txFn := func(opts *bind.TransactOpts) (*types.Transaction, error) { return contract.AllowlistRequest(opts, requestDigest, deadline) } - txOut, err := wrc.executeTransactionByTxType(txFn, "AllowlistRequest", "RequestAllowlisted", requestDigest, duration) + txOut, err := wrc.executeTransactionByTxType(ctx, txFn, "AllowlistRequest", "RequestAllowlisted", requestDigest, duration) if err != nil { wrc.Logger.Error(). Str("contract", wrc.ContractAddress.Hex()). diff --git a/cmd/client/workflow_registry_v2_client_test.go b/cmd/client/workflow_registry_v2_client_test.go index fbda002c..967cbe6f 100644 --- a/cmd/client/workflow_registry_v2_client_test.go +++ b/cmd/client/workflow_registry_v2_client_test.go @@ -1,6 +1,7 @@ package client import ( + "context" "errors" "testing" @@ -59,7 +60,7 @@ func TestIsRequestAllowlisted_Success(t *testing.T) { reqDigest, ).Return(true, nil).Once() - ok, err := wrc.IsRequestAllowlisted(owner, reqDigest) + ok, err := wrc.IsRequestAllowlisted(context.Background(), owner, reqDigest) assert.NoError(t, err) assert.True(t, ok) @@ -81,7 +82,7 @@ func TestIsRequestAllowlisted_ContractError(t *testing.T) { reqDigest, ).Return(false, errors.New("revert: not allowed")).Once() - ok, err := wrc.IsRequestAllowlisted(owner, reqDigest) + ok, err := wrc.IsRequestAllowlisted(context.Background(), owner, reqDigest) assert.Error(t, err) assert.Contains(t, err.Error(), "not allowed") assert.False(t, ok) diff --git a/cmd/secrets/common/handler.go b/cmd/secrets/common/handler.go index 12ff42eb..36f81ea6 100644 --- a/cmd/secrets/common/handler.go +++ b/cmd/secrets/common/handler.go @@ -68,13 +68,14 @@ type Handler struct { Wrc *client.WorkflowRegistryV2Client Credentials *credentials.Credentials Settings *settings.Settings + execCtx context.Context } // NewHandler creates a new handler instance. // secretsAuth is the value of the --secrets-auth flag (e.g. "onchain" or "browser"). // For the browser OAuth flow the on-chain WorkflowRegistryV2Client is not needed and is // intentionally skipped to avoid requiring an ethereum-mainnet RPC URL. -func NewHandler(ctx *runtime.Context, secretsFilePath, secretsAuth string) (*Handler, error) { +func NewHandler(execCtx context.Context, ctx *runtime.Context, secretsFilePath, secretsAuth string) (*Handler, error) { var pk *ecdsa.PrivateKey var err error if ctx.Settings.User.EthPrivateKey != "" { @@ -97,11 +98,12 @@ func NewHandler(ctx *runtime.Context, secretsFilePath, secretsAuth string) (*Han EnvironmentSet: ctx.EnvironmentSet, Credentials: ctx.Credentials, Settings: ctx.Settings, + execCtx: execCtx, } h.Gw = &HTTPClient{URL: h.EnvironmentSet.GatewayURL, Client: &http.Client{Timeout: 90 * time.Second}} if !IsBrowserFlow(secretsAuth) { - wrc, err := h.ClientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.ClientFactory.NewWorkflowRegistryV2Client(execCtx) if err != nil { return nil, fmt.Errorf("failed to create workflow registry client: %w", err) } @@ -402,13 +404,15 @@ func HexToBytes32(h string) ([32]byte, error) { // Execute implements secrets create and update from YAML (multisig bundle, owner-key with allowlist, or interactive org sign-in). func (h *Handler) Execute( + ctx context.Context, inputs UpsertSecretsInputs, method string, duration time.Duration, secretsAuth string, ) error { + h.execCtx = ctx if IsBrowserFlow(secretsAuth) { - return h.executeBrowserUpsert(context.Background(), inputs, method) + return h.executeBrowserUpsert(ctx, inputs, method) } if err := h.EnsureDeploymentRPCForOwnerKeySecrets(); err != nil { @@ -416,7 +420,7 @@ func (h *Handler) Execute( } ui.Dim("Verifying ownership...") - if err := h.EnsureOwnerLinkedOrFail(); err != nil { + if err := h.EnsureOwnerLinkedOrFail(ctx); err != nil { return err } @@ -478,13 +482,13 @@ func (h *Handler) Execute( ownerAddr := common.HexToAddress(owner) - allowlisted, err := h.Wrc.IsRequestAllowlisted(ownerAddr, digest) + allowlisted, err := h.Wrc.IsRequestAllowlisted(ctx, ownerAddr, digest) if err != nil { return fmt.Errorf("allowlist check failed: %w", err) } var txOut *client.TxOutput if !allowlisted { - if txOut, err = h.Wrc.AllowlistRequest(digest, duration); err != nil { + if txOut, err = h.Wrc.AllowlistRequest(ctx, digest, duration); err != nil { return fmt.Errorf("allowlist request failed: %w", err) } } @@ -691,13 +695,13 @@ func (h *Handler) ParseVaultGatewayResponse(method string, respBody []byte) erro } // EnsureOwnerLinkedOrFail TODO this reuses the same logic as in auto_link.go which is tied to deploy; consider refactoring to avoid duplication -func (h *Handler) EnsureOwnerLinkedOrFail() error { +func (h *Handler) EnsureOwnerLinkedOrFail(ctx context.Context) error { if !common.IsHexAddress(h.OwnerAddress) { return fmt.Errorf("owner address %q is not a valid hex EVM address; check your workflow settings", h.OwnerAddress) } ownerAddr := common.HexToAddress(h.OwnerAddress) - linked, err := h.Wrc.IsOwnerLinked(ownerAddr) + linked, err := h.Wrc.IsOwnerLinked(ctx, ownerAddr) if err != nil { return fmt.Errorf("failed to check owner link status: %w", err) } diff --git a/cmd/secrets/common/handler_test.go b/cmd/secrets/common/handler_test.go index cd58c70f..4a2d14b0 100644 --- a/cmd/secrets/common/handler_test.go +++ b/cmd/secrets/common/handler_test.go @@ -2,6 +2,7 @@ package common import ( "bytes" + "context" "crypto/rand" "encoding/hex" "encoding/json" @@ -14,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" @@ -315,7 +317,7 @@ func TestNewHandler_WorkflowRegistryClient(t *testing.T) { t.Run("browser flow: WorkflowRegistryV2Client is not created", func(t *testing.T) { ctx, cf := newCtx(t) - h, err := NewHandler(ctx, "", SecretsAuthBrowser) + h, err := NewHandler(context.Background(), ctx, "", SecretsAuthBrowser) require.NoError(t, err) require.Nil(t, h.Wrc, "Wrc must be nil for browser flow") cf.AssertNotCalled(t, "NewWorkflowRegistryV2Client") @@ -323,18 +325,18 @@ func TestNewHandler_WorkflowRegistryClient(t *testing.T) { t.Run("owner-key flow: WorkflowRegistryV2Client is created", func(t *testing.T) { ctx, cf := newCtx(t) - cf.On("NewWorkflowRegistryV2Client").Return(nil, nil) - h, err := NewHandler(ctx, "", SecretsAuthOnchain) + cf.On("NewWorkflowRegistryV2Client", mock.Anything).Return(nil, nil) + h, err := NewHandler(context.Background(), ctx, "", SecretsAuthOnchain) require.NoError(t, err) // Wrc may be nil if the mock returns nil, but the factory must have been called. _ = h - cf.AssertCalled(t, "NewWorkflowRegistryV2Client") + cf.AssertCalled(t, "NewWorkflowRegistryV2Client", mock.Anything) }) t.Run("owner-key flow: factory error is propagated", func(t *testing.T) { ctx, cf := newCtx(t) - cf.On("NewWorkflowRegistryV2Client").Return(nil, errors.New("rpc url not found for chain ethereum-mainnet")) - _, err := NewHandler(ctx, "", SecretsAuthOnchain) + cf.On("NewWorkflowRegistryV2Client", mock.Anything).Return(nil, errors.New("rpc url not found for chain ethereum-mainnet")) + _, err := NewHandler(context.Background(), ctx, "", SecretsAuthOnchain) require.Error(t, err) require.Contains(t, err.Error(), "workflow registry client") }) diff --git a/cmd/secrets/common/test_helpers.go b/cmd/secrets/common/test_helpers.go index 648f6df4..b79ddf55 100644 --- a/cmd/secrets/common/test_helpers.go +++ b/cmd/secrets/common/test_helpers.go @@ -2,6 +2,7 @@ package common import ( "bytes" + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -43,8 +44,8 @@ func (m *MockClientFactory) GetSkipConfirmation() bool { panic("not used in these tests") } -func (m *MockClientFactory) NewWorkflowRegistryV2Client() (*client.WorkflowRegistryV2Client, error) { - args := m.Called() +func (m *MockClientFactory) NewWorkflowRegistryV2Client(ctx context.Context) (*client.WorkflowRegistryV2Client, error) { + args := m.Called(ctx) var c *client.WorkflowRegistryV2Client if v := args.Get(0); v != nil { c = v.(*client.WorkflowRegistryV2Client) diff --git a/cmd/secrets/create/create.go b/cmd/secrets/create/create.go index 4b25d3df..8209b103 100644 --- a/cmd/secrets/create/create.go +++ b/cmd/secrets/create/create.go @@ -41,7 +41,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - h, err := common.NewHandler(ctx, secretsFilePath, secretsAuth) + h, err := common.NewHandler(cmd.Context(), ctx, secretsFilePath, secretsAuth) if err != nil { return err } @@ -71,7 +71,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - return h.Execute(inputs, vaulttypes.MethodSecretsCreate, duration, secretsAuth) + return h.Execute(cmd.Context(), inputs, vaulttypes.MethodSecretsCreate, duration, secretsAuth) }, } diff --git a/cmd/secrets/delete/delete.go b/cmd/secrets/delete/delete.go index e691971a..57126591 100644 --- a/cmd/secrets/delete/delete.go +++ b/cmd/secrets/delete/delete.go @@ -74,7 +74,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - h, err := common.NewHandler(ctx, secretsFilePath, secretsAuth) + h, err := common.NewHandler(cmd.Context(), ctx, secretsFilePath, secretsAuth) if err != nil { return err } @@ -103,7 +103,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - return Execute(h, inputs, duration, secretsAuth) + return Execute(cmd.Context(), h, inputs, duration, secretsAuth) }, } @@ -116,14 +116,14 @@ func New(ctx *runtime.Context) *cobra.Command { // Two paths: // - MSIG step 1: build request, compute digest, write bundle, print steps // - EOA: allowlist if needed, then POST to gateway -func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Duration, secretsAuth string) error { +func Execute(ctx context.Context, h *common.Handler, inputs DeleteSecretsInputs, duration time.Duration, secretsAuth string) error { if !common.IsBrowserFlow(secretsAuth) { if err := h.EnsureDeploymentRPCForOwnerKeySecrets(); err != nil { return err } spinner := ui.NewSpinner() spinner.Start("Verifying ownership...") - if err := h.EnsureOwnerLinkedOrFail(); err != nil { + if err := h.EnsureOwnerLinkedOrFail(ctx); err != nil { spinner.Stop() return err } @@ -187,13 +187,13 @@ func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Durati ownerAddr := ethcommon.HexToAddress(owner) - allowlisted, err := h.Wrc.IsRequestAllowlisted(ownerAddr, digest) + allowlisted, err := h.Wrc.IsRequestAllowlisted(ctx, ownerAddr, digest) if err != nil { return fmt.Errorf("allowlist check failed: %w", err) } var txOut *client.TxOutput if !allowlisted { - if txOut, err = h.Wrc.AllowlistRequest(digest, duration); err != nil { + if txOut, err = h.Wrc.AllowlistRequest(ctx, digest, duration); err != nil { return fmt.Errorf("allowlist request failed: %w", err) } } else { diff --git a/cmd/secrets/execute/execute.go b/cmd/secrets/execute/execute.go index f83a803e..8c89f303 100644 --- a/cmd/secrets/execute/execute.go +++ b/cmd/secrets/execute/execute.go @@ -37,7 +37,7 @@ func New(ctx *runtime.Context) *cobra.Command { return fmt.Errorf("execute expects a bundle .json file; got %q", ext) } - h, err := common.NewHandler(ctx, bundlePath, common.SecretsAuthOnchain) + h, err := common.NewHandler(cmd.Context(), ctx, bundlePath, common.SecretsAuthOnchain) if err != nil { return err } @@ -71,7 +71,7 @@ func New(ctx *runtime.Context) *cobra.Command { ownerAddr := ethcommon.HexToAddress(h.OwnerAddress) - allowlisted, err := h.Wrc.IsRequestAllowlisted(ownerAddr, digest) + allowlisted, err := h.Wrc.IsRequestAllowlisted(cmd.Context(), ownerAddr, digest) if err != nil { return fmt.Errorf("allowlist check failed: %w", err) } diff --git a/cmd/secrets/list/list.go b/cmd/secrets/list/list.go index 03f8adf0..6a4a58b8 100644 --- a/cmd/secrets/list/list.go +++ b/cmd/secrets/list/list.go @@ -52,7 +52,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - h, err := common.NewHandler(ctx, "", secretsAuth) + h, err := common.NewHandler(cmd.Context(), ctx, "", secretsAuth) if err != nil { return err } @@ -74,7 +74,7 @@ func New(ctx *runtime.Context) *cobra.Command { return fmt.Errorf("invalid --timeout: must be greater than 0 and less than %dh (%dd)", maxHours, maxDays) } - return Execute(h, namespace, duration, secretsAuth) + return Execute(cmd.Context(), h, namespace, duration, secretsAuth) }, } @@ -86,14 +86,14 @@ func New(ctx *runtime.Context) *cobra.Command { } // Execute performs: build request → (MSIG step 1 bundle OR EOA allowlist+post) → parse. -func Execute(h *common.Handler, namespace string, duration time.Duration, secretsAuth string) error { +func Execute(ctx context.Context, h *common.Handler, namespace string, duration time.Duration, secretsAuth string) error { if !common.IsBrowserFlow(secretsAuth) { if err := h.EnsureDeploymentRPCForOwnerKeySecrets(); err != nil { return err } spinner := ui.NewSpinner() spinner.Start("Verifying ownership...") - if err := h.EnsureOwnerLinkedOrFail(); err != nil { + if err := h.EnsureOwnerLinkedOrFail(ctx); err != nil { spinner.Stop() return err } @@ -140,13 +140,13 @@ func Execute(h *common.Handler, namespace string, duration time.Duration, secret ownerAddr := ethcommon.HexToAddress(owner) - allowlisted, err := h.Wrc.IsRequestAllowlisted(ownerAddr, digest) + allowlisted, err := h.Wrc.IsRequestAllowlisted(ctx, ownerAddr, digest) if err != nil { return fmt.Errorf("allowlist check failed: %w", err) } var txOut *client.TxOutput if !allowlisted { - if txOut, err = h.Wrc.AllowlistRequest(digest, duration); err != nil { + if txOut, err = h.Wrc.AllowlistRequest(ctx, digest, duration); err != nil { return fmt.Errorf("allowlist request failed: %w", err) } } diff --git a/cmd/secrets/update/update.go b/cmd/secrets/update/update.go index 24a1b0cd..a7bc84ef 100644 --- a/cmd/secrets/update/update.go +++ b/cmd/secrets/update/update.go @@ -41,7 +41,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - h, err := common.NewHandler(ctx, secretsFilePath, secretsAuth) + h, err := common.NewHandler(cmd.Context(), ctx, secretsFilePath, secretsAuth) if err != nil { return err } @@ -72,7 +72,7 @@ func New(ctx *runtime.Context) *cobra.Command { return err } - return h.Execute(inputs, vaulttypes.MethodSecretsUpdate, duration, secretsAuth) + return h.Execute(cmd.Context(), inputs, vaulttypes.MethodSecretsUpdate, duration, secretsAuth) }, } diff --git a/cmd/workflow/activate/registry_activate_strategy_onchain.go b/cmd/workflow/activate/registry_activate_strategy_onchain.go index a6878606..6a9f9919 100644 --- a/cmd/workflow/activate/registry_activate_strategy_onchain.go +++ b/cmd/workflow/activate/registry_activate_strategy_onchain.go @@ -35,7 +35,7 @@ func newOnchainRegistryActivateStrategy(h *handler) (*onchainRegistryActivateStr a.wg.Add(1) go func() { defer a.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) if err != nil { a.initErr = fmt.Errorf("failed to create workflow registry client: %w", err) return @@ -58,7 +58,7 @@ func (a *onchainRegistryActivateStrategy) Activate() error { ownerAddr := common.HexToAddress(workflowOwner) - workflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(a.wrc, ownerAddr, workflowName) + workflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(h.execCtx, a.wrc, ownerAddr, workflowName) if err != nil { return fmt.Errorf("failed to get workflow list: %w", err) } @@ -80,13 +80,13 @@ func (a *onchainRegistryActivateStrategy) Activate() error { return fmt.Errorf("workflow is already active, cancelling transaction") } - if err := a.wrc.CheckUserDonLimit(ownerAddr, h.inputs.DonFamily, 1); err != nil { + if err := a.wrc.CheckUserDonLimit(h.execCtx, ownerAddr, h.inputs.DonFamily, 1); err != nil { return err } ui.Dim(fmt.Sprintf("Activating workflow: Name=%s, Owner=%s, WorkflowID=%s", workflowName, workflowOwner, hex.EncodeToString(latest.WorkflowId[:]))) - txOut, err := a.wrc.ActivateWorkflow(latest.WorkflowId, h.inputs.DonFamily) + txOut, err := a.wrc.ActivateWorkflow(h.execCtx, latest.WorkflowId, h.inputs.DonFamily) if err != nil { return fmt.Errorf("failed to activate workflow: %w", err) } diff --git a/cmd/workflow/common/workflow_list.go b/cmd/workflow/common/workflow_list.go index f51d9485..848945b0 100644 --- a/cmd/workflow/common/workflow_list.go +++ b/cmd/workflow/common/workflow_list.go @@ -1,6 +1,7 @@ package common import ( + "context" "math/big" "github.com/ethereum/go-ethereum/common" @@ -12,11 +13,12 @@ const workflowListPageSize = int64(200) // WorkflowListByOwnerAndNameClient fetches workflow metadata pages from the registry. type WorkflowListByOwnerAndNameClient interface { - GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) + GetWorkflowListByOwnerAndName(ctx context.Context, owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) } // FetchAllWorkflowsByOwnerAndName returns every workflow version for owner+name, paginating until exhausted. func FetchAllWorkflowsByOwnerAndName( + ctx context.Context, wrc WorkflowListByOwnerAndNameClient, owner common.Address, name string, @@ -28,7 +30,7 @@ func FetchAllWorkflowsByOwnerAndName( ) for { - list, err := wrc.GetWorkflowListByOwnerAndName(owner, name, start, limit) + list, err := wrc.GetWorkflowListByOwnerAndName(ctx, owner, name, start, limit) if err != nil { return nil, err } diff --git a/cmd/workflow/delete/registry_delete_strategy_onchain.go b/cmd/workflow/delete/registry_delete_strategy_onchain.go index 5cd3a9b5..58f93dcf 100644 --- a/cmd/workflow/delete/registry_delete_strategy_onchain.go +++ b/cmd/workflow/delete/registry_delete_strategy_onchain.go @@ -35,7 +35,7 @@ func newOnchainRegistryDeleteStrategy(h *handler) (*onchainRegistryDeleteStrateg a.wg.Add(1) go func() { defer a.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) if err != nil { a.initErr = fmt.Errorf("failed to create workflow registry client: %w", err) return @@ -56,7 +56,7 @@ func (a *onchainRegistryDeleteStrategy) FetchWorkflows() ([]WorkflowToDelete, er workflowName := h.inputs.WorkflowName workflowOwner := common.HexToAddress(h.inputs.WorkflowOwner) - allWorkflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(a.wrc, workflowOwner, workflowName) + allWorkflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(h.execCtx, a.wrc, workflowOwner, workflowName) if err != nil { return nil, fmt.Errorf("failed to get workflow list: %w", err) } @@ -83,7 +83,7 @@ func (a *onchainRegistryDeleteStrategy) DeleteWorkflows(workflows []WorkflowToDe var errs []error for _, wf := range workflows { workflowID := wf.RawID.([32]byte) - txOut, err := a.wrc.DeleteWorkflow(workflowID) + txOut, err := a.wrc.DeleteWorkflow(h.execCtx, workflowID) if err != nil { h.log.Error(). Err(err). diff --git a/cmd/workflow/deploy/auto_link.go b/cmd/workflow/deploy/auto_link.go index 2ee140bf..7e393dc3 100644 --- a/cmd/workflow/deploy/auto_link.go +++ b/cmd/workflow/deploy/auto_link.go @@ -25,7 +25,7 @@ const ( func (h *handler) ensureOwnerLinkedOrFail(onChain *settings.OnChainRegistry) error { ownerAddr := common.HexToAddress(h.inputs.WorkflowOwner) - linked, err := h.wrc.IsOwnerLinked(ownerAddr) + linked, err := h.wrc.IsOwnerLinked(h.execCtx, ownerAddr) if err != nil { return fmt.Errorf("failed to check owner link status: %w", err) } @@ -66,7 +66,7 @@ func (h *handler) ensureOwnerLinkedOrFail(onChain *settings.OnChainRegistry) err func (h *handler) autoLinkMSIGAndExit(onChain *settings.OnChainRegistry) (halt bool, err error) { ownerAddr := common.HexToAddress(h.inputs.WorkflowOwner) - linked, err := h.wrc.IsOwnerLinked(ownerAddr) + linked, err := h.wrc.IsOwnerLinked(h.execCtx, ownerAddr) if err != nil { return false, fmt.Errorf("failed to check owner link status: %w", err) } @@ -107,7 +107,7 @@ func (h *handler) tryAutoLink(onChain *settings.OnChainRegistry) error { EnvironmentSet: h.environmentSet, } - return linkkey.Exec(rtx, linkkey.Inputs{ + return linkkey.Exec(h.execCtx, rtx, linkkey.Inputs{ WorkflowOwner: h.inputs.WorkflowOwner, WorkflowRegistryContractAddress: onChain.Address(), WorkflowOwnerLabel: h.inputs.OwnerLabel, diff --git a/cmd/workflow/deploy/deploy_test.go b/cmd/workflow/deploy/deploy_test.go index 72632ce6..aa0a7907 100644 --- a/cmd/workflow/deploy/deploy_test.go +++ b/cmd/workflow/deploy/deploy_test.go @@ -805,7 +805,7 @@ type fakeUserDonLimitClient struct { workflowsByOwnerName []workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView } -func (f fakeUserDonLimitClient) CheckUserDonLimit(owner common.Address, donFamily string, pending uint32) error { +func (f fakeUserDonLimitClient) CheckUserDonLimit(_ context.Context, owner common.Address, donFamily string, pending uint32) error { var currentActive uint32 for _, workflow := range f.workflowsByOwner { if workflow.Owner == owner && workflow.Status == workflowStatusActive && workflow.DonFamily == donFamily { @@ -819,7 +819,7 @@ func (f fakeUserDonLimitClient) CheckUserDonLimit(owner common.Address, donFamil return nil } -func (f fakeUserDonLimitClient) GetWorkflowListByOwnerAndName(common.Address, string, *big.Int, *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { +func (f fakeUserDonLimitClient) GetWorkflowListByOwnerAndName(_ context.Context, _ common.Address, _ string, _, _ *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { return f.workflowsByOwnerName, nil } @@ -879,7 +879,7 @@ func TestCheckUserDonLimitBeforeDeploy(t *testing.T) { } nameLookup := fakeUserDonLimitClient{} - err := checkUserDonLimitBeforeDeploy(client, nameLookup, owner, donFamily, workflowName, true, nil) + err := checkUserDonLimitBeforeDeploy(context.Background(), client, nameLookup, owner, donFamily, workflowName, true, nil) require.Error(t, err) assert.Contains(t, err.Error(), "workflow limit reached") }) @@ -898,7 +898,7 @@ func TestCheckUserDonLimitBeforeDeploy(t *testing.T) { }, } - err := checkUserDonLimitBeforeDeploy(client, nameLookup, owner, donFamily, workflowName, false, nil) + err := checkUserDonLimitBeforeDeploy(context.Background(), client, nameLookup, owner, donFamily, workflowName, false, nil) require.NoError(t, err) }) @@ -912,7 +912,7 @@ func TestCheckUserDonLimitBeforeDeploy(t *testing.T) { nameLookup := fakeUserDonLimitClient{} existingStatus := uint8(0) - err := checkUserDonLimitBeforeDeploy(client, nameLookup, owner, donFamily, workflowName, true, &existingStatus) + err := checkUserDonLimitBeforeDeploy(context.Background(), client, nameLookup, owner, donFamily, workflowName, true, &existingStatus) require.NoError(t, err) }) } diff --git a/cmd/workflow/deploy/limits.go b/cmd/workflow/deploy/limits.go index 4a93f003..a58561ad 100644 --- a/cmd/workflow/deploy/limits.go +++ b/cmd/workflow/deploy/limits.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "fmt" "math/big" @@ -16,14 +17,15 @@ const ( ) type workflowNameLookupClient interface { - GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) + GetWorkflowListByOwnerAndName(ctx context.Context, owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) } type userDonLimitChecker interface { - CheckUserDonLimit(owner common.Address, donFamily string, pending uint32) error + CheckUserDonLimit(ctx context.Context, owner common.Address, donFamily string, pending uint32) error } func checkUserDonLimitBeforeDeploy( + ctx context.Context, limitChecker userDonLimitChecker, nameLookup workflowNameLookupClient, owner common.Address, @@ -38,7 +40,7 @@ func checkUserDonLimitBeforeDeploy( pending := uint32(1) if !keepAlive { - activeSameName, err := countActiveWorkflowsByOwnerNameAndDON(nameLookup, owner, workflowName, donFamily) + activeSameName, err := countActiveWorkflowsByOwnerNameAndDON(ctx, nameLookup, owner, workflowName, donFamily) if err != nil { return fmt.Errorf("failed to check active workflows for %s on DON %s: %w", workflowName, donFamily, err) } @@ -53,10 +55,11 @@ func checkUserDonLimitBeforeDeploy( return nil } - return limitChecker.CheckUserDonLimit(owner, donFamily, pending) + return limitChecker.CheckUserDonLimit(ctx, owner, donFamily, pending) } func countActiveWorkflowsByOwnerNameAndDON( + ctx context.Context, wrc workflowNameLookupClient, owner common.Address, workflowName string, @@ -67,7 +70,7 @@ func countActiveWorkflowsByOwnerNameAndDON( limit := big.NewInt(workflowListPageSize) for { - list, err := wrc.GetWorkflowListByOwnerAndName(owner, workflowName, start, limit) + list, err := wrc.GetWorkflowListByOwnerAndName(ctx, owner, workflowName, start, limit) if err != nil { return 0, err } diff --git a/cmd/workflow/deploy/register.go b/cmd/workflow/deploy/register.go index 6c47e237..29c6ac67 100644 --- a/cmd/workflow/deploy/register.go +++ b/cmd/workflow/deploy/register.go @@ -57,7 +57,7 @@ func (h *handler) handleUpsert(params client.RegisterWorkflowV2Parameters, onCha workflowName := h.inputs.WorkflowName workflowTag := h.inputs.WorkflowTag h.log.Debug().Interface("Workflow parameters", params).Msg("Registering workflow...") - txOut, err := h.wrc.UpsertWorkflow(params) + txOut, err := h.wrc.UpsertWorkflow(h.execCtx, params) if err != nil { return fmt.Errorf("failed to register workflow: %w", err) } diff --git a/cmd/workflow/deploy/register_test.go b/cmd/workflow/deploy/register_test.go index da3b0241..b039aaf5 100644 --- a/cmd/workflow/deploy/register_test.go +++ b/cmd/workflow/deploy/register_test.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "path/filepath" "testing" @@ -47,7 +48,7 @@ func TestWorkflowUpsert(t *testing.T) { ctx, buf := simulatedEnvironment.NewRuntimeContextWithBufferedOutput() handler := newHandler(ctx, buf) - wrc, err := handler.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := handler.clientFactory.NewWorkflowRegistryV2Client(context.Background()) require.NoError(t, err) handler.wrc = wrc @@ -62,6 +63,7 @@ func TestWorkflowUpsert(t *testing.T) { } handler.workflowArtifact = &wfArt + handler.execCtx = context.Background() onChain, err := settings.AsOnChain(ctx.ResolvedRegistry, "test") require.NoError(t, err) diff --git a/cmd/workflow/deploy/registry_deploy_strategy_onchain.go b/cmd/workflow/deploy/registry_deploy_strategy_onchain.go index 0d1943cd..032de838 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy_onchain.go +++ b/cmd/workflow/deploy/registry_deploy_strategy_onchain.go @@ -33,7 +33,7 @@ func newOnchainRegistryDeployStrategy(h *handler) (*onchainRegistryDeployStrateg a.wg.Add(1) go func() { defer a.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) if err != nil { a.initErr = fmt.Errorf("failed to create workflow registry client: %w", err) return @@ -72,7 +72,7 @@ func (a *onchainRegistryDeployStrategy) RunPreDeployChecks() error { } func (a *onchainRegistryDeployStrategy) CheckWorkflowExists(workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) { - workflow, err := a.wrc.GetWorkflow(common.HexToAddress(workflowOwner), workflowName, workflowTag) + workflow, err := a.wrc.GetWorkflow(a.h.execCtx, common.HexToAddress(workflowOwner), workflowName, workflowTag) if err != nil { return false, nil, err } @@ -92,6 +92,7 @@ func (a *onchainRegistryDeployStrategy) Upsert() error { h := a.h if err := checkUserDonLimitBeforeDeploy( + h.execCtx, a.wrc, a.wrc, common.HexToAddress(h.inputs.WorkflowOwner), diff --git a/cmd/workflow/pause/registry_pause_strategy_onchain.go b/cmd/workflow/pause/registry_pause_strategy_onchain.go index 1003b739..9e403dd8 100644 --- a/cmd/workflow/pause/registry_pause_strategy_onchain.go +++ b/cmd/workflow/pause/registry_pause_strategy_onchain.go @@ -34,7 +34,7 @@ func newOnchainRegistryPauseStrategy(h *handler) (*onchainRegistryPauseStrategy, a.wg.Add(1) go func() { defer a.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) if err != nil { a.initErr = fmt.Errorf("failed to create workflow registry client: %w", err) return @@ -57,7 +57,7 @@ func (a *onchainRegistryPauseStrategy) Pause() error { ui.Dim(fmt.Sprintf("Fetching workflows to pause... Name=%s, Owner=%s", workflowName, workflowOwner.Hex())) - workflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(a.wrc, workflowOwner, workflowName) + workflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(h.execCtx, a.wrc, workflowOwner, workflowName) if err != nil { return fmt.Errorf("failed to list workflows: %w", err) } @@ -82,7 +82,7 @@ func (a *onchainRegistryPauseStrategy) Pause() error { ui.Dim(fmt.Sprintf("Processing batch pause... count=%d", len(activeWorkflowIDs))) - txOut, err := a.wrc.BatchPauseWorkflows(activeWorkflowIDs) + txOut, err := a.wrc.BatchPauseWorkflows(h.execCtx, activeWorkflowIDs) if err != nil { return fmt.Errorf("failed to batch pause workflows: %w", err) } diff --git a/internal/testutil/chainsim/simulated_client_factory.go b/internal/testutil/chainsim/simulated_client_factory.go index 9dc8a7b5..6ce1fba2 100644 --- a/internal/testutil/chainsim/simulated_client_factory.go +++ b/internal/testutil/chainsim/simulated_client_factory.go @@ -1,6 +1,8 @@ package chainsim import ( + "context" + "github.com/rs/zerolog" "github.com/smartcontractkit/chainlink-testing-framework/seth" @@ -22,7 +24,7 @@ func NewSimulatedClientFactory(logger *zerolog.Logger, ethClient *seth.Client, s } } -func (f *testFactoryImpl) NewWorkflowRegistryV2Client() (*client.WorkflowRegistryV2Client, error) { +func (f *testFactoryImpl) NewWorkflowRegistryV2Client(ctx context.Context) (*client.WorkflowRegistryV2Client, error) { txcConfig := client.TxClientConfig{ TxType: client.Regular, LedgerConfig: &client.LedgerConfig{LedgerEnabled: false}, diff --git a/internal/testutil/chainsim/simulated_workflow_registry_contract.go b/internal/testutil/chainsim/simulated_workflow_registry_contract.go index 9ccc7cc4..7c68622c 100644 --- a/internal/testutil/chainsim/simulated_workflow_registry_contract.go +++ b/internal/testutil/chainsim/simulated_workflow_registry_contract.go @@ -1,6 +1,7 @@ package chainsim import ( + "context" "crypto/sha256" "encoding/hex" "fmt" @@ -36,11 +37,11 @@ func DeployWorkflowRegistry(t *testing.T, ethClient *seth.Client, chain *Simulat } workflowRegistryClient := client.NewWorkflowRegistryV2Client(logger, ethClient, deployedContract.Address.Hex(), txcConfig) - err = workflowRegistryClient.UpdateAllowedSigners([]common.Address{common.HexToAddress(TestAddress)}, true) + err = workflowRegistryClient.UpdateAllowedSigners(context.Background(), []common.Address{common.HexToAddress(TestAddress)}, true) chain.Backend.Commit() require.NoError(t, err, "Failed to update authorized addresses") - err = workflowRegistryClient.SetDonLimit("zone-a", 1000, 100) + err = workflowRegistryClient.SetDonLimit(context.Background(), "zone-a", 1000, 100) chain.Backend.Commit() require.NoError(t, err, "Failed to update allowed DONs") @@ -63,7 +64,7 @@ func linkOwner(wrc *client.WorkflowRegistryV2Client) error { const LinkRequestType uint8 = 0 - version, err := wrc.TypeAndVersion() + version, err := wrc.TypeAndVersion(context.Background()) if err != nil { return err } @@ -113,7 +114,7 @@ func linkOwner(wrc *client.WorkflowRegistryV2Client) error { //t.Logf("v: %d, r: %s, s: %s", v, r.Text(16), s.Text(16)) - _, err = wrc.LinkOwner(validityTimestamp, common.HexToHash(ownershipProof), signature) + _, err = wrc.LinkOwner(context.Background(), validityTimestamp, common.HexToHash(ownershipProof), signature) if err != nil { return err } diff --git a/internal/testutil/workflow/test_workflow.go b/internal/testutil/workflow/test_workflow.go index 5c43c1cd..7451fd06 100644 --- a/internal/testutil/workflow/test_workflow.go +++ b/internal/testutil/workflow/test_workflow.go @@ -1,6 +1,7 @@ package workflowtest import ( + "context" "testing" "github.com/ethereum/go-ethereum/common" @@ -26,6 +27,6 @@ func RegisterWorkflow(t *testing.T, wrc *client.WorkflowRegistryV2Client, workfl DonFamily: "1", } - _, err = wrc.UpsertWorkflow(params) + _, err = wrc.UpsertWorkflow(context.Background(), params) require.NoError(t, err, "Failed to register workflow") } From 3acaad112d4f9d342328549df07c839cc74cb776 Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 19:40:02 +0400 Subject: [PATCH 19/29] Enhance error handling for workflow ID type assertions in deletion strategies (#452) - Updated `registry_delete_strategy_onchain.go` and `registry_delete_strategy_private.go` to include type assertion checks for `RawID`. - Added error messages for unexpected `RawID` types to improve debugging and maintainability. --- cmd/workflow/delete/registry_delete_strategy_onchain.go | 5 ++++- cmd/workflow/delete/registry_delete_strategy_private.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/workflow/delete/registry_delete_strategy_onchain.go b/cmd/workflow/delete/registry_delete_strategy_onchain.go index 58f93dcf..b2c2911c 100644 --- a/cmd/workflow/delete/registry_delete_strategy_onchain.go +++ b/cmd/workflow/delete/registry_delete_strategy_onchain.go @@ -82,7 +82,10 @@ func (a *onchainRegistryDeleteStrategy) DeleteWorkflows(workflows []WorkflowToDe h := a.h var errs []error for _, wf := range workflows { - workflowID := wf.RawID.([32]byte) + workflowID, ok := wf.RawID.([32]byte) + if !ok { + return fmt.Errorf("unexpected RawID type for workflow %s: %T", wf.ID, wf.RawID) + } txOut, err := a.wrc.DeleteWorkflow(h.execCtx, workflowID) if err != nil { h.log.Error(). diff --git a/cmd/workflow/delete/registry_delete_strategy_private.go b/cmd/workflow/delete/registry_delete_strategy_private.go index b5d52864..5001dd42 100644 --- a/cmd/workflow/delete/registry_delete_strategy_private.go +++ b/cmd/workflow/delete/registry_delete_strategy_private.go @@ -54,7 +54,10 @@ func (a *privateRegistryDeleteStrategy) DeleteWorkflows(workflows []WorkflowToDe h := a.h for _, wf := range workflows { - workflowID := wf.RawID.(string) + workflowID, ok := wf.RawID.(string) + if !ok { + return fmt.Errorf("unexpected RawID type for workflow %s: %T", wf.ID, wf.RawID) + } deletedID, err := a.prc.DeleteWorkflowInRegistry(a.h.execCtx, workflowID) if err != nil { h.log.Error(). From 576a1d379c58fc3243ecef489b798ef17b6f54a7 Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 19:48:04 +0400 Subject: [PATCH 20/29] Enhance registry type mapping with logging for unknown types (#453) * Enhance registry type mapping with logging for unknown types * fix test * fix test --- internal/tenantctx/tenantctx.go | 7 ++++--- internal/tenantctx/tenantctx_test.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/tenantctx/tenantctx.go b/internal/tenantctx/tenantctx.go index 5fcd7b6e..ec3f2064 100644 --- a/internal/tenantctx/tenantctx.go +++ b/internal/tenantctx/tenantctx.go @@ -106,7 +106,7 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client, registries := make([]*Registry, 0, len(tc.Registries)) for _, r := range tc.Registries { - regType := mapRegistryType(r.Type) + regType := mapRegistryType(r.Type, log) id := r.ID label := r.Label @@ -157,14 +157,15 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client, return writeContextFile(contextMap, log) } -func mapRegistryType(gqlType string) string { +func mapRegistryType(gqlType string, log *zerolog.Logger) string { switch gqlType { case "ON_CHAIN": return "on-chain" case "OFF_CHAIN": return "off-chain" default: - return strings.ToLower(gqlType) + log.Warn().Str("type", gqlType).Msg("unknown registry type, skipping") + return "unknown" } } diff --git a/internal/tenantctx/tenantctx_test.go b/internal/tenantctx/tenantctx_test.go index 63ddbb65..683c2f20 100644 --- a/internal/tenantctx/tenantctx_test.go +++ b/internal/tenantctx/tenantctx_test.go @@ -433,7 +433,7 @@ func TestMapRegistryType(t *testing.T) { {"UNKNOWN", "unknown"}, } for _, tt := range tests { - if got := mapRegistryType(tt.input); got != tt.want { + if got := mapRegistryType(tt.input, testutil.NewTestLogger()); got != tt.want { t.Errorf("mapRegistryType(%q) = %q, want %q", tt.input, got, tt.want) } } From 976d68422e03a79fa009331e9b053ced75703dbb Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 22 May 2026 19:50:05 +0400 Subject: [PATCH 21/29] Refactor input validation in UpsertWorkflow function (#454) * Refactor input validation in UpsertWorkflow function * fix test --- .../client/privateregistryclient/privateregistryclient.go | 6 ++---- .../privateregistryclient/privateregistryclient_test.go | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/client/privateregistryclient/privateregistryclient.go b/internal/client/privateregistryclient/privateregistryclient.go index 6e9d9e9d..1c5875fe 100644 --- a/internal/client/privateregistryclient/privateregistryclient.go +++ b/internal/client/privateregistryclient/privateregistryclient.go @@ -349,10 +349,8 @@ func validateUpsertWorkflowInput(input OffchainWorkflowInput) error { if input.Status == "" { return fmt.Errorf("status is required") } - if input.Status != WorkflowStatusUnspecified && - input.Status != WorkflowStatusActive && - input.Status != WorkflowStatusPaused { - return fmt.Errorf("status must be one of %q, %q, %q", WorkflowStatusUnspecified, WorkflowStatusActive, WorkflowStatusPaused) + if input.Status != WorkflowStatusActive && input.Status != WorkflowStatusPaused { + return fmt.Errorf("status must be one of %q, %q", WorkflowStatusActive, WorkflowStatusPaused) } if input.WorkflowName == "" { return fmt.Errorf("workflowName is required") diff --git a/internal/client/privateregistryclient/privateregistryclient_test.go b/internal/client/privateregistryclient/privateregistryclient_test.go index 8dc4c5db..0587dfbc 100644 --- a/internal/client/privateregistryclient/privateregistryclient_test.go +++ b/internal/client/privateregistryclient/privateregistryclient_test.go @@ -86,7 +86,7 @@ func TestValidateUpsertWorkflowInput(t *testing.T) { { name: "invalid status", input: OffchainWorkflowInput{WorkflowID: "wf", Status: "INVALID", WorkflowName: "w", BinaryURL: "b", DonFamily: "f"}, - err: "status must be one of \"WORKFLOW_STATUS_UNSPECIFIED\", \"WORKFLOW_STATUS_ACTIVE\", \"WORKFLOW_STATUS_PAUSED\"", + err: "status must be one of \"WORKFLOW_STATUS_ACTIVE\", \"WORKFLOW_STATUS_PAUSED\"", }, { name: "workflowName exceeds max length", From 4ee55e4c0c01e2036e796a3775c98c03b21a1f76 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Wed, 27 May 2026 17:46:47 +0100 Subject: [PATCH 22/29] Map user context gateway url to secrets module (#446) * Remove org owned feature flag * Support udpated Oauth secrets ownership model * Map user context gateway url to secrets module * lint --- .../{gateway.go => gateway/http_client.go} | 4 +- .../http_client_test.go} | 28 +++++------ cmd/secrets/common/gateway/resolver.go | 27 ++++++++++ cmd/secrets/common/gateway/resolver_test.go | 50 +++++++++++++++++++ cmd/secrets/common/handler.go | 7 ++- cmd/secrets/common/handler_test.go | 40 +++++++++++++++ 6 files changed, 137 insertions(+), 19 deletions(-) rename cmd/secrets/common/{gateway.go => gateway/http_client.go} (99%) rename cmd/secrets/common/{gateway_test.go => gateway/http_client_test.go} (93%) create mode 100644 cmd/secrets/common/gateway/resolver.go create mode 100644 cmd/secrets/common/gateway/resolver_test.go diff --git a/cmd/secrets/common/gateway.go b/cmd/secrets/common/gateway/http_client.go similarity index 99% rename from cmd/secrets/common/gateway.go rename to cmd/secrets/common/gateway/http_client.go index 4043d61f..c1b2595a 100644 --- a/cmd/secrets/common/gateway.go +++ b/cmd/secrets/common/gateway/http_client.go @@ -1,4 +1,4 @@ -package common +package gateway import ( "bytes" @@ -13,7 +13,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/ui" ) -type GatewayClient interface { +type Client interface { Post(body []byte) (respBody []byte, status int, err error) // PostWithBearer sends the JSON-RPC body with Authorization: Bearer for the browser OAuth flow (no allowlist retries). PostWithBearer(body []byte, bearerToken string) (respBody []byte, status int, err error) diff --git a/cmd/secrets/common/gateway_test.go b/cmd/secrets/common/gateway/http_client_test.go similarity index 93% rename from cmd/secrets/common/gateway_test.go rename to cmd/secrets/common/gateway/http_client_test.go index 9a177676..dbfa80a1 100644 --- a/cmd/secrets/common/gateway_test.go +++ b/cmd/secrets/common/gateway/http_client_test.go @@ -1,4 +1,4 @@ -package common +package gateway import ( "bytes" @@ -51,8 +51,6 @@ func makeResp(code int, body string) *http.Response { } func TestPostToGateway(t *testing.T) { - h, _, _ := newMockHandler(t) - t.Run("success (single attempt)", func(t *testing.T) { body := `{"jsonrpc":"2.0","id":"abc","result":{"ok":true}}` @@ -62,14 +60,14 @@ func TestPostToGateway(t *testing.T) { }, } mockedHTTP := &http.Client{Transport: rt} - h.Gw = &HTTPClient{ + gw := &HTTPClient{ URL: "https://unit-test.gw", Client: mockedHTTP, RetryAttempts: 3, RetryDelay: 0, // fast tests } - respBytes, status, err := h.Gw.Post([]byte(`{"x":1}`)) + respBytes, status, err := gw.Post([]byte(`{"x":1}`)) assert.NoError(t, err) assert.Equal(t, 200, status) assert.Equal(t, body, string(respBytes)) @@ -84,14 +82,14 @@ func TestPostToGateway(t *testing.T) { }, } mockedHTTP := &http.Client{Transport: rt} - h.Gw = &HTTPClient{ + gw := &HTTPClient{ URL: "https://unit-test.gw", Client: mockedHTTP, RetryAttempts: 2, RetryDelay: 0, } - _, status, err := h.Gw.Post([]byte(`{}`)) + _, status, err := gw.Post([]byte(`{}`)) assert.Error(t, err) assert.Contains(t, err.Error(), "gateway request failed") assert.Equal(t, 0, status) // last attempt was a transport error -> status 0 @@ -106,14 +104,14 @@ func TestPostToGateway(t *testing.T) { }, } mockedHTTP := &http.Client{Transport: rt} - h.Gw = &HTTPClient{ + gw := &HTTPClient{ URL: "https://unit-test.gw", Client: mockedHTTP, RetryAttempts: 2, RetryDelay: 0, } - _, status, err := h.Gw.Post([]byte(`{}`)) + _, status, err := gw.Post([]byte(`{}`)) assert.Error(t, err) assert.Contains(t, err.Error(), "read response body: read error") assert.Equal(t, 200, status) // postOnce had a resp with 200 but read failed @@ -129,14 +127,14 @@ func TestPostToGateway(t *testing.T) { }, } mockedHTTP := &http.Client{Transport: rt} - h.Gw = &HTTPClient{ + gw := &HTTPClient{ URL: "https://unit-test.gw", Client: mockedHTTP, RetryAttempts: 3, RetryDelay: 0, } - respBytes, status, err := h.Gw.Post([]byte(`{}`)) + respBytes, status, err := gw.Post([]byte(`{}`)) assert.NoError(t, err) assert.Equal(t, 200, status) assert.Equal(t, successBody, string(respBytes)) @@ -152,14 +150,14 @@ func TestPostToGateway(t *testing.T) { }, } mockedHTTP := &http.Client{Transport: rt} - h.Gw = &HTTPClient{ + gw := &HTTPClient{ URL: "https://unit-test.gw", Client: mockedHTTP, RetryAttempts: 3, RetryDelay: 0, } - _, status, err := h.Gw.Post([]byte(`{}`)) + _, status, err := gw.Post([]byte(`{}`)) assert.Error(t, err) assert.Equal(t, 429, status) // from last attempt assert.Contains(t, err.Error(), "gateway POST failed") @@ -175,14 +173,14 @@ func TestPostToGateway(t *testing.T) { }, } mockedHTTP := &http.Client{Transport: rt} - h.Gw = &HTTPClient{ + gw := &HTTPClient{ URL: "https://unit-test.gw", Client: mockedHTTP, RetryAttempts: 5, RetryDelay: 0, } - respBytes, status, err := h.Gw.Post([]byte(`{}`)) + respBytes, status, err := gw.Post([]byte(`{}`)) assert.NoError(t, err) assert.Equal(t, 200, status) assert.Equal(t, body, string(respBytes)) diff --git a/cmd/secrets/common/gateway/resolver.go b/cmd/secrets/common/gateway/resolver.go new file mode 100644 index 00000000..c57782a4 --- /dev/null +++ b/cmd/secrets/common/gateway/resolver.go @@ -0,0 +1,27 @@ +package gateway + +import ( + "os" + "strings" + + "github.com/smartcontractkit/cre-cli/internal/environments" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" +) + +// ResolveVaultGatewayURL returns the vault gateway URL for secrets operations. +// Precedence: CRE_VAULT_DON_GATEWAY_URL env var, then context.yaml vault_gateway_url, +// then the embedded default from EnvironmentSet. +func ResolveVaultGatewayURL(tenantCtx *tenantctx.EnvironmentContext, envSet *environments.EnvironmentSet) string { + if os.Getenv(environments.EnvVarVaultGatewayURL) != "" && envSet != nil { + return strings.TrimSpace(envSet.GatewayURL) + } + if tenantCtx != nil { + if u := strings.TrimSpace(tenantCtx.VaultGatewayURL); u != "" { + return u + } + } + if envSet != nil { + return strings.TrimSpace(envSet.GatewayURL) + } + return "" +} diff --git a/cmd/secrets/common/gateway/resolver_test.go b/cmd/secrets/common/gateway/resolver_test.go new file mode 100644 index 00000000..97c933d2 --- /dev/null +++ b/cmd/secrets/common/gateway/resolver_test.go @@ -0,0 +1,50 @@ +package gateway + +import ( + "testing" + + "github.com/smartcontractkit/cre-cli/internal/environments" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" +) + +func TestResolveVaultGatewayURL(t *testing.T) { + embeddedURL := "https://embedded.example.com/" + contextURL := "https://context.example.com/" + envOverrideURL := "https://env-override.example.com/" + + envSet := &environments.EnvironmentSet{GatewayURL: embeddedURL} + tenantCtx := &tenantctx.EnvironmentContext{VaultGatewayURL: contextURL} + + t.Run("env var wins over context URL", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, envOverrideURL) + envSetWithOverride := &environments.EnvironmentSet{GatewayURL: envOverrideURL} + got := ResolveVaultGatewayURL(tenantCtx, envSetWithOverride) + if got != envOverrideURL { + t.Errorf("got %q, want %q", got, envOverrideURL) + } + }) + + t.Run("context URL when env var unset", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "") + got := ResolveVaultGatewayURL(tenantCtx, envSet) + if got != contextURL { + t.Errorf("got %q, want %q", got, contextURL) + } + }) + + t.Run("embedded default when env var unset and context URL empty", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "") + got := ResolveVaultGatewayURL(&tenantctx.EnvironmentContext{}, envSet) + if got != embeddedURL { + t.Errorf("got %q, want %q", got, embeddedURL) + } + }) + + t.Run("embedded default when tenant context nil", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "") + got := ResolveVaultGatewayURL(nil, envSet) + if got != embeddedURL { + t.Errorf("got %q, want %q", got, embeddedURL) + } + }) +} diff --git a/cmd/secrets/common/handler.go b/cmd/secrets/common/handler.go index 36f81ea6..9d2040f1 100644 --- a/cmd/secrets/common/handler.go +++ b/cmd/secrets/common/handler.go @@ -30,6 +30,7 @@ import ( "github.com/smartcontractkit/cre-cli/cmd/client" cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common" + "github.com/smartcontractkit/cre-cli/cmd/secrets/common/gateway" "github.com/smartcontractkit/cre-cli/internal/client/graphqlclient" "github.com/smartcontractkit/cre-cli/internal/constants" "github.com/smartcontractkit/cre-cli/internal/credentials" @@ -64,7 +65,8 @@ type Handler struct { OwnerAddress string DerivedWorkflowOwner string EnvironmentSet *environments.EnvironmentSet - Gw GatewayClient + GatewayURL string + Gw gateway.Client Wrc *client.WorkflowRegistryV2Client Credentials *credentials.Credentials Settings *settings.Settings @@ -100,7 +102,8 @@ func NewHandler(execCtx context.Context, ctx *runtime.Context, secretsFilePath, Settings: ctx.Settings, execCtx: execCtx, } - h.Gw = &HTTPClient{URL: h.EnvironmentSet.GatewayURL, Client: &http.Client{Timeout: 90 * time.Second}} + h.GatewayURL = gateway.ResolveVaultGatewayURL(ctx.TenantContext, ctx.EnvironmentSet) + h.Gw = &gateway.HTTPClient{URL: h.GatewayURL, Client: &http.Client{Timeout: 90 * time.Second}} if !IsBrowserFlow(secretsAuth) { wrc, err := h.ClientFactory.NewWorkflowRegistryV2Client(execCtx) diff --git a/cmd/secrets/common/handler_test.go b/cmd/secrets/common/handler_test.go index 4a2d14b0..30a236b8 100644 --- a/cmd/secrets/common/handler_test.go +++ b/cmd/secrets/common/handler_test.go @@ -23,10 +23,12 @@ import ( "github.com/smartcontractkit/chainlink-evm/gethwrappers/workflow/generated/workflow_registry_wrapper_v2" "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" + "github.com/smartcontractkit/cre-cli/cmd/secrets/common/gateway" "github.com/smartcontractkit/cre-cli/internal/credentials" "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/tenantctx" ) type mockGatewayClient struct { @@ -341,3 +343,41 @@ func TestNewHandler_WorkflowRegistryClient(t *testing.T) { require.Contains(t, err.Error(), "workflow registry client") }) } + +func TestNewHandler_GatewayURL(t *testing.T) { + logger := zerolog.New(bytes.NewBufferString("")) + cf := new(MockClientFactory) + baseCtx := &runtime.Context{ + Logger: &logger, + ClientFactory: cf, + Settings: &settings.Settings{ + User: settings.UserSettings{EthPrivateKey: ""}, + Workflow: settings.WorkflowSettings{}, + }, + EnvironmentSet: &environments.EnvironmentSet{GatewayURL: "https://embedded.example.com/"}, + Credentials: &credentials.Credentials{}, + TenantContext: &tenantctx.EnvironmentContext{VaultGatewayURL: "https://context.example.com/"}, + } + + t.Run("uses context URL when env var unset", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "") + h, err := NewHandler(context.Background(), baseCtx, "", SecretsAuthBrowser) + require.NoError(t, err) + require.Equal(t, "https://context.example.com/", h.GatewayURL) + gw, ok := h.Gw.(*gateway.HTTPClient) + require.True(t, ok) + require.Equal(t, "https://context.example.com/", gw.URL) + }) + + t.Run("env var wins over context URL", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "https://env-override.example.com/") + envCtx := *baseCtx + envCtx.EnvironmentSet = &environments.EnvironmentSet{GatewayURL: "https://env-override.example.com/"} + h, err := NewHandler(context.Background(), &envCtx, "", SecretsAuthBrowser) + require.NoError(t, err) + require.Equal(t, "https://env-override.example.com/", h.GatewayURL) + gw, ok := h.Gw.(*gateway.HTTPClient) + require.True(t, ok) + require.Equal(t, "https://env-override.example.com/", gw.URL) + }) +} From df3ca606366bed2c0cd08970ca4a42c24a8f04d9 Mon Sep 17 00:00:00 2001 From: Anirudh Warrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:49:48 +0400 Subject: [PATCH 23/29] Refactor secret handling to use byte arrays for values (#444) * Refactor secret handling to use byte arrays for values - Updated SecretItem struct to store secret values as byte arrays instead of strings. - Introduced ZeroUpsertSecretValues function to clear secret values from memory after use. - Modified EncryptSecret and encryptSecretWithLabel functions to accept byte arrays. - Updated tests to reflect changes in secret value type and ensure proper functionality. - Added memory safety measures in various handler methods to prevent sensitive data leakage. * Review feedback * Feedback --------- Co-authored-by: timothyF95 Co-authored-by: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> --- cmd/secrets/common/handler.go | 25 ++++++++++++++++------ cmd/secrets/common/handler_test.go | 34 +++++++++++++++++++++++++----- cmd/secrets/create/create.go | 1 + cmd/secrets/update/update.go | 1 + 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/cmd/secrets/common/handler.go b/cmd/secrets/common/handler.go index 9d2040f1..b30352c1 100644 --- a/cmd/secrets/common/handler.go +++ b/cmd/secrets/common/handler.go @@ -49,10 +49,17 @@ type UpsertSecretsInputs []SecretItem // SecretItem represents a single secret with its ID, value, and optional namespace. type SecretItem struct { ID string `json:"id" validate:"required"` - Value string `json:"value" validate:"required"` + Value []byte `json:"value" validate:"required"` Namespace string `json:"namespace"` } +// ZeroUpsertSecretValues overwrites secret payloads in memory. +func ZeroUpsertSecretValues(inputs UpsertSecretsInputs) { + for i := range inputs { + clear(inputs[i].Value) + } +} + type SecretsYamlConfig struct { SecretsNames map[string][]string `yaml:"secretsNames"` } @@ -168,9 +175,10 @@ func (h *Handler) ResolveInputs() (UpsertSecretsInputs, error) { return nil, fmt.Errorf("value for secret %q (env %q) contains invalid UTF-8", id, envName) } + value := []byte(envVal) out = append(out, SecretItem{ ID: id, - Value: envVal, + Value: value, Namespace: "main", }) @@ -314,6 +322,7 @@ func (h *Handler) ResolveVaultIdentifierOwnerForAuth(secretsAuth string) (string // EncryptSecrets encrypts secrets for the given workflow owner address. // TDH2 label is the workflow owner address left-padded to 32 bytes; SecretIdentifier.Owner is the same hex address string. +// Each item's Value slice is zeroed in place as it is encrypted; callers must not reuse rawSecrets afterward. func (h *Handler) EncryptSecrets(rawSecrets UpsertSecretsInputs, owner string) ([]*vault.EncryptedSecret, error) { pubKeyHex, err := h.fetchVaultMasterPublicKeyHex() if err != nil { @@ -321,8 +330,10 @@ func (h *Handler) EncryptSecrets(rawSecrets UpsertSecretsInputs, owner string) ( } encryptedSecrets := make([]*vault.EncryptedSecret, 0, len(rawSecrets)) - for _, item := range rawSecrets { + for i := range rawSecrets { + item := &rawSecrets[i] cipherHex, err := EncryptSecret(item.Value, pubKeyHex, owner) + clear(item.Value) if err != nil { return nil, fmt.Errorf("failed to encrypt secret (key=%s ns=%s): %w", item.ID, item.Namespace, err) } @@ -340,7 +351,7 @@ func (h *Handler) EncryptSecrets(rawSecrets UpsertSecretsInputs, owner string) ( } // encryptSecretWithLabel encrypts a secret using the vault master public key and the given label. -func encryptSecretWithLabel(secret, masterPublicKeyHex string, label [32]byte) (string, error) { +func encryptSecretWithLabel(secret []byte, masterPublicKeyHex string, label [32]byte) (string, error) { masterPublicKey := tdh2easy.PublicKey{} masterPublicKeyBytes, err := hex.DecodeString(masterPublicKeyHex) if err != nil { @@ -350,7 +361,7 @@ func encryptSecretWithLabel(secret, masterPublicKeyHex string, label [32]byte) ( return "", fmt.Errorf("failed to unmarshal master public key: %w", err) } - cipher, err := tdh2easy.EncryptWithLabel(&masterPublicKey, []byte(secret), label) + cipher, err := tdh2easy.EncryptWithLabel(&masterPublicKey, secret, label) if err != nil { return "", fmt.Errorf("failed to encrypt secret: %w", err) } @@ -362,7 +373,7 @@ func encryptSecretWithLabel(secret, masterPublicKeyHex string, label [32]byte) ( } // EncryptSecret encrypts for the owner-key / web3 flow using a 32-byte label derived from the EOA (12 zero bytes + 20-byte address). -func EncryptSecret(secret, masterPublicKeyHex string, ownerAddress string) (string, error) { +func EncryptSecret(secret []byte, masterPublicKeyHex string, ownerAddress string) (string, error) { addr := common.HexToAddress(ownerAddress) // canonical 20-byte address var label [32]byte copy(label[12:], addr.Bytes()) // left-pad with 12 zero bytes @@ -413,6 +424,8 @@ func (h *Handler) Execute( duration time.Duration, secretsAuth string, ) error { + defer ZeroUpsertSecretValues(inputs) + h.execCtx = ctx if IsBrowserFlow(secretsAuth) { return h.executeBrowserUpsert(ctx, inputs, method) diff --git a/cmd/secrets/common/handler_test.go b/cmd/secrets/common/handler_test.go index 30a236b8..e1400980 100644 --- a/cmd/secrets/common/handler_test.go +++ b/cmd/secrets/common/handler_test.go @@ -43,9 +43,29 @@ func (m *mockGatewayClient) PostWithBearer(b []byte, _ string) ([]byte, int, err return m.post(b) } +func requireZeroedBytes(t *testing.T, b []byte) { + t.Helper() + for i, v := range b { + require.Zero(t, v, "byte at index %d should be zero", i) + } +} + // It represents a hex-encoded tdh2easy.PublicKey blob. const vaultPublicKeyHex = "7b2247726f7570223a2250323536222c22475f626172223a22424d704759487a2b33333432596436582f2b6d4971396d5468556c6d2f317355716b51783333343564303373472b2f2f307257494d39795a70454b44566c6c2b616f36586c513743366546452b665472356568785a4f343d222c2248223a22424257546f7638394b546b41505a7566474454504e35626f456d6453305368697975696e3847336e58517774454931536333394453314b41306a595a6576546155476775444d694431746e6e4d686575373177574b57593d222c22484172726179223a5b22424937726649364c646f7654413948676a684b5955516a4744456a5a66374f30774378466c432f2f384e394733464c796247436d6e54734236632b50324c34596a39477548555a4936386d54342b4e77786f794b6261513d222c22424736634369395574317a65433753786b4c442b6247354751505473717463324a7a544b4c726b784d496e4c36484e7658376541324b6167423243447a4b6a6f76783570414c6a74523734537a6c7146543366746662513d222c224245576f7147546d6b47314c31565a53655874345147446a684d4d2b656e7a6b426b7842782b484f72386e39336b51543963594938486f513630356a65504a732f53575866355a714534564e676b4f672f643530395a6b3d222c22424a31552b6e5344783269567a654177475948624e715242564869626b74466b624f4762376158562f3946744c6876314b4250416c3272696e73714171754459504e2f54667870725a6e655259594a2b2f453162536a673d222c224243675a623770424d777732337138577767736e322b6c4d665259343561347576445345715a7559614e2f356e64744970355a492f4a6f454d372b36304a6338735978682b535365364645683052364f57666855706d453d222c2242465a5942524a336d6647695644312b4f4b4e4f374c54355a6f6574515442624a6b464152757143743268492f52757832756b7166794c6c364d71566e55613557336e49726e71506132566d5345755758546d39456f733d222c22424f716b662f356232636c4d314a78615831446d6a76494c4437334f6734566b42732f4b686b6e4d6867435772552f30574a36734e514a6b425462686b4a5535576b48506342626d45786c6362706a49743349494632303d225d7d" +func TestZeroUpsertSecretValues(t *testing.T) { + inputs := UpsertSecretsInputs{ + {ID: "a", Value: []byte("secret-one"), Namespace: "main"}, + {ID: "b", Value: []byte("secret-two"), Namespace: "main"}, + } + + ZeroUpsertSecretValues(inputs) + + for _, item := range inputs { + requireZeroedBytes(t, item.Value) + } +} + func TestEncryptSecrets(t *testing.T) { h, _, _ := newMockHandler(t) h.OwnerAddress = "0xabc" @@ -71,8 +91,8 @@ func TestEncryptSecrets(t *testing.T) { } raw := UpsertSecretsInputs{ - {ID: "test-secret-1", Value: "value1", Namespace: "ns1"}, - {ID: "test-secret-2", Value: "another-value", Namespace: "ns2"}, + {ID: "test-secret-1", Value: []byte("value1"), Namespace: "ns1"}, + {ID: "test-secret-2", Value: []byte("another-value"), Namespace: "ns2"}, } enc, err := h.EncryptSecrets(raw, "0xabc") @@ -95,6 +115,10 @@ func TestEncryptSecrets(t *testing.T) { _, err = hex.DecodeString(enc[1].EncryptedValue) require.NoError(t, err) require.NotEmpty(t, enc[1].EncryptedValue) + + for i := range raw { + requireZeroedBytes(t, raw[i].Value) + } }) t.Run("failure - gateway POST error", func(t *testing.T) { @@ -104,7 +128,7 @@ func TestEncryptSecrets(t *testing.T) { }, } - enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: "v", Namespace: "n"}}, "0xabc") + enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: []byte("v"), Namespace: "n"}}, "0xabc") require.Error(t, err) require.Nil(t, enc) require.Contains(t, err.Error(), "gateway POST failed") @@ -130,7 +154,7 @@ func TestEncryptSecrets(t *testing.T) { }, } - enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: "v", Namespace: "n"}}, "0xabc") + enc, err := h.EncryptSecrets(UpsertSecretsInputs{{ID: "s", Value: []byte("v"), Namespace: "n"}}, "0xabc") require.Error(t, err) require.Nil(t, enc) require.Contains(t, err.Error(), "vault public key fetch error") @@ -229,7 +253,7 @@ func TestEncryptSecrets_UsesWorkflowOwnerAddress(t *testing.T) { h.OwnerAddress = "0xabc" enc, err := h.EncryptSecrets(UpsertSecretsInputs{ - {ID: "secret-1", Value: "val1", Namespace: "main"}, + {ID: "secret-1", Value: []byte("val1"), Namespace: "main"}, }, "0xabc") require.NoError(t, err) require.Len(t, enc, 1) diff --git a/cmd/secrets/create/create.go b/cmd/secrets/create/create.go index 8209b103..31f1a50d 100644 --- a/cmd/secrets/create/create.go +++ b/cmd/secrets/create/create.go @@ -66,6 +66,7 @@ func New(ctx *runtime.Context) *cobra.Command { if err != nil { return err } + defer common.ZeroUpsertSecretValues(inputs) if err := h.ValidateInputs(inputs); err != nil { return err diff --git a/cmd/secrets/update/update.go b/cmd/secrets/update/update.go index a7bc84ef..de776004 100644 --- a/cmd/secrets/update/update.go +++ b/cmd/secrets/update/update.go @@ -67,6 +67,7 @@ func New(ctx *runtime.Context) *cobra.Command { if err != nil { return err } + defer common.ZeroUpsertSecretValues(inputs) if err := h.ValidateInputs(inputs); err != nil { return err From a0b6f54caa2933287a321e88dc0bab48e795ecce Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:26:21 +0100 Subject: [PATCH 24/29] Fix context propagation in execute operations (#459) * Fix context propagation in execute operations * Fix unit test * Fix second unit test * Refactor of ctx prop * Use test handler to avoid ctx nil panics * lint * Remove test handler --- .tool-versions | 2 +- cmd/common/compile.go | 15 +++---- cmd/common/compile_test.go | 17 ++++---- cmd/common/fetch.go | 10 ++++- cmd/common/fetch_test.go | 7 ++-- cmd/workflow/build/build.go | 7 ++-- cmd/workflow/deploy/artifacts.go | 11 +++-- cmd/workflow/deploy/artifacts_test.go | 17 ++++---- cmd/workflow/deploy/auto_link.go | 37 +++++++++-------- cmd/workflow/deploy/auto_link_test.go | 5 ++- cmd/workflow/deploy/compile.go | 5 ++- cmd/workflow/deploy/compile_test.go | 10 ++--- cmd/workflow/deploy/deploy.go | 32 ++++++++------- cmd/workflow/deploy/private_registry_test.go | 3 +- cmd/workflow/deploy/register.go | 9 +++-- cmd/workflow/deploy/register_test.go | 7 +--- .../deploy/registry_deploy_strategy.go | 11 ++--- .../registry_deploy_strategy_onchain.go | 40 ++++++++++++++----- .../registry_deploy_strategy_private.go | 11 ++--- cmd/workflow/hash/hash.go | 19 ++++----- cmd/workflow/hash/hash_test.go | 17 ++++---- cmd/workflow/simulate/simulate.go | 10 ++--- cmd/workflow/simulate/simulate_test.go | 2 +- .../client/storageclient/storageclient.go | 33 +++++++++------ 24 files changed, 195 insertions(+), 142 deletions(-) diff --git a/.tool-versions b/.tool-versions index 9a45258b..71d117e2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,4 @@ -golang 1.25.5 +golang 1.26.2 golangci-lint 2.11.2 goreleaser 2.0.1 python 3.10.5 diff --git a/cmd/common/compile.go b/cmd/common/compile.go index 2456ec77..d500e6e9 100644 --- a/cmd/common/compile.go +++ b/cmd/common/compile.go @@ -1,6 +1,7 @@ package common import ( + "context" "errors" "fmt" "os" @@ -33,7 +34,7 @@ type WorkflowCompileOptions struct { } // getBuildCmd returns a single step that builds the workflow and returns the WASM bytes. -func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCompileOptions) (func() ([]byte, error), error) { +func getBuildCmd(ctx context.Context, workflowRootFolder, mainFile, language string, opts WorkflowCompileOptions) (func() ([]byte, error), error) { tmpPath := filepath.Join(workflowRootFolder, ".cre_build_tmp.wasm") switch language { case constants.WorkflowLanguageTypeScript: @@ -41,7 +42,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCom if opts.SkipTypeChecks { args = append(args, SkipTypeChecksFlag) } - cmd := exec.Command("bun", args...) + cmd := exec.CommandContext(ctx, "bun", args...) cmd.Dir = workflowRootFolder return func() ([]byte, error) { out, err := cmd.CombinedOutput() @@ -67,7 +68,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCom if opts.StripSymbols { ldflags = "-buildid= -w -s" } - cmd := exec.Command( + cmd := exec.CommandContext(ctx, "go", "build", "-o", tmpPath, "-trimpath", @@ -92,7 +93,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCom if err != nil { return nil, err } - makeCmd := exec.Command("make", "build") + makeCmd := exec.CommandContext(ctx, "make", "build") makeCmd.Dir = makeRoot builtPath := filepath.Join(makeRoot, defaultWasmOutput) return func() ([]byte, error) { @@ -108,7 +109,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCom if opts.StripSymbols { ldflags = "-buildid= -w -s" } - cmd := exec.Command( + cmd := exec.CommandContext(ctx, "go", "build", "-o", tmpPath, "-trimpath", @@ -135,7 +136,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCom // opts.StripSymbols: for Go builds, true strips debug symbols (deploy); false keeps them (simulate). // opts.SkipTypeChecks: for TypeScript, passes SkipTypeChecksFlag to cre-compile. // For custom Makefile WASM builds, StripSymbols and SkipTypeChecks have no effect. -func CompileWorkflowToWasm(workflowPath string, opts WorkflowCompileOptions) ([]byte, error) { +func CompileWorkflowToWasm(ctx context.Context, workflowPath string, opts WorkflowCompileOptions) ([]byte, error) { workflowRootFolder, workflowMainFile, err := WorkflowPathRootAndMain(workflowPath) if err != nil { return nil, fmt.Errorf("workflow path: %w", err) @@ -167,7 +168,7 @@ func CompileWorkflowToWasm(workflowPath string, opts WorkflowCompileOptions) ([] return nil, fmt.Errorf("unsupported workflow language for file %s", workflowMainFile) } - buildStep, err := getBuildCmd(workflowRootFolder, workflowMainFile, language, opts) + buildStep, err := getBuildCmd(ctx, workflowRootFolder, workflowMainFile, language, opts) if err != nil { return nil, err } diff --git a/cmd/common/compile_test.go b/cmd/common/compile_test.go index fdc7dc3d..fa4aa42c 100644 --- a/cmd/common/compile_test.go +++ b/cmd/common/compile_test.go @@ -2,6 +2,7 @@ package common import ( "bytes" + "context" "io" "os" "os/exec" @@ -47,21 +48,21 @@ func TestFindMakefileRoot(t *testing.T) { func TestCompileWorkflowToWasm_Go_Success(t *testing.T) { t.Run("basic_workflow", func(t *testing.T) { path := deployTestdataPath("basic_workflow", "main.go") - wasm, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true}) + wasm, err := CompileWorkflowToWasm(context.Background(), path, WorkflowCompileOptions{StripSymbols: true}) require.NoError(t, err) assert.NotEmpty(t, wasm) }) t.Run("configless_workflow", func(t *testing.T) { path := deployTestdataPath("configless_workflow", "main.go") - wasm, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true}) + wasm, err := CompileWorkflowToWasm(context.Background(), path, WorkflowCompileOptions{StripSymbols: true}) require.NoError(t, err) assert.NotEmpty(t, wasm) }) t.Run("missing_go_mod", func(t *testing.T) { path := deployTestdataPath("missing_go_mod", "main.go") - wasm, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true}) + wasm, err := CompileWorkflowToWasm(context.Background(), path, WorkflowCompileOptions{StripSymbols: true}) require.NoError(t, err) assert.NotEmpty(t, wasm) }) @@ -69,7 +70,7 @@ func TestCompileWorkflowToWasm_Go_Success(t *testing.T) { func TestCompileWorkflowToWasm_Go_Malformed_Fails(t *testing.T) { path := deployTestdataPath("malformed_workflow", "main.go") - _, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true}) + _, err := CompileWorkflowToWasm(context.Background(), path, WorkflowCompileOptions{StripSymbols: true}) require.Error(t, err) assert.Contains(t, err.Error(), "failed to compile workflow") assert.Contains(t, err.Error(), "undefined: sdk.RemovedFunctionThatFailsCompilation") @@ -80,7 +81,7 @@ func TestCompileWorkflowToWasm_Wasm_Success(t *testing.T) { _ = os.Remove(wasmPath) t.Cleanup(func() { _ = os.Remove(wasmPath) }) - wasm, err := CompileWorkflowToWasm(wasmPath, WorkflowCompileOptions{StripSymbols: true}) + wasm, err := CompileWorkflowToWasm(context.Background(), wasmPath, WorkflowCompileOptions{StripSymbols: true}) require.NoError(t, err) assert.NotEmpty(t, wasm) @@ -96,14 +97,14 @@ func TestCompileWorkflowToWasm_Wasm_Fails(t *testing.T) { wasmPath := filepath.Join(wasmDir, "workflow.wasm") require.NoError(t, os.WriteFile(wasmPath, []byte("not really wasm"), 0600)) - _, err := CompileWorkflowToWasm(wasmPath, WorkflowCompileOptions{StripSymbols: true}) + _, err := CompileWorkflowToWasm(context.Background(), wasmPath, WorkflowCompileOptions{StripSymbols: true}) require.Error(t, err) assert.Contains(t, err.Error(), "no Makefile found") }) t.Run("make_build_fails", func(t *testing.T) { path := deployTestdataPath("wasm_make_fails", "wasm", "workflow.wasm") - _, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true}) + _, err := CompileWorkflowToWasm(context.Background(), path, WorkflowCompileOptions{StripSymbols: true}) require.Error(t, err) assert.Contains(t, err.Error(), "failed to compile workflow") assert.Contains(t, err.Error(), "build output:") @@ -138,7 +139,7 @@ func TestCompileWorkflowToWasm_TS_Success(t *testing.T) { "include": ["main.ts"] } `), 0600)) - wasm, err := CompileWorkflowToWasm(mainPath, WorkflowCompileOptions{StripSymbols: true}) + wasm, err := CompileWorkflowToWasm(context.Background(), mainPath, WorkflowCompileOptions{StripSymbols: true}) if err != nil { t.Skipf("TS compile failed (published cre-sdk may lack full layout): %v", err) } diff --git a/cmd/common/fetch.go b/cmd/common/fetch.go index 5f8ee4f4..bc5b69e0 100644 --- a/cmd/common/fetch.go +++ b/cmd/common/fetch.go @@ -1,6 +1,7 @@ package common import ( + "context" "fmt" "io" "net/http" @@ -28,8 +29,13 @@ func IsURL(s string) bool { } // FetchURL performs an HTTP GET and returns the response body bytes. -func FetchURL(url string) ([]byte, error) { - resp, err := http.Get(url) //nolint:gosec,noctx +func FetchURL(ctx context.Context, url string) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("HTTP GET %s: %w", url, err) + } + + resp, err := http.DefaultClient.Do(req) //nolint:gosec if err != nil { return nil, fmt.Errorf("HTTP GET %s: %w", url, err) } diff --git a/cmd/common/fetch_test.go b/cmd/common/fetch_test.go index 10a6ade6..0291f3dd 100644 --- a/cmd/common/fetch_test.go +++ b/cmd/common/fetch_test.go @@ -1,6 +1,7 @@ package common import ( + "context" "net/http" "net/http/httptest" "testing" @@ -42,7 +43,7 @@ func TestFetchURL(t *testing.T) { })) defer srv.Close() - data, err := FetchURL(srv.URL) + data, err := FetchURL(context.Background(), srv.URL) require.NoError(t, err) assert.Equal(t, body, data) }) @@ -53,13 +54,13 @@ func TestFetchURL(t *testing.T) { })) defer srv.Close() - _, err := FetchURL(srv.URL) + _, err := FetchURL(context.Background(), srv.URL) require.Error(t, err) assert.Contains(t, err.Error(), "returned status 404") }) t.Run("unreachable host", func(t *testing.T) { - _, err := FetchURL("http://127.0.0.1:1") + _, err := FetchURL(context.Background(), "http://127.0.0.1:1") require.Error(t, err) }) } diff --git a/cmd/workflow/build/build.go b/cmd/workflow/build/build.go index f92f6973..63b79edd 100644 --- a/cmd/workflow/build/build.go +++ b/cmd/workflow/build/build.go @@ -1,6 +1,7 @@ package build import ( + "context" "fmt" "os" "path/filepath" @@ -26,7 +27,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { outputPath, _ := cmd.Flags().GetString("output") skipTypeChecks, _ := cmd.Flags().GetBool(cmdcommon.SkipTypeChecksCLIFlag) - return execute(args[0], outputPath, skipTypeChecks) + return execute(cmd.Context(), args[0], outputPath, skipTypeChecks) }, } buildCmd.Flags().StringP("output", "o", "", "Output file path for the compiled WASM binary (default: /binary.wasm)") @@ -34,7 +35,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { return buildCmd } -func execute(workflowFolder, outputPath string, skipTypeChecks bool) error { +func execute(ctx context.Context, workflowFolder, outputPath string, skipTypeChecks bool) error { workflowDir, err := filepath.Abs(workflowFolder) if err != nil { return fmt.Errorf("resolve workflow folder: %w", err) @@ -60,7 +61,7 @@ func execute(workflowFolder, outputPath string, skipTypeChecks bool) error { outputPath = cmdcommon.EnsureWasmExtension(outputPath) ui.Dim("Compiling workflow...") - wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedPath, cmdcommon.WorkflowCompileOptions{ + wasmBytes, err := cmdcommon.CompileWorkflowToWasm(ctx, resolvedPath, cmdcommon.WorkflowCompileOptions{ StripSymbols: true, SkipTypeChecks: skipTypeChecks, }) diff --git a/cmd/workflow/deploy/artifacts.go b/cmd/workflow/deploy/artifacts.go index 070b1421..1bcb6467 100644 --- a/cmd/workflow/deploy/artifacts.go +++ b/cmd/workflow/deploy/artifacts.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "fmt" "github.com/smartcontractkit/cre-cli/internal/client/graphqlclient" @@ -8,7 +9,11 @@ import ( "github.com/smartcontractkit/cre-cli/internal/ui" ) -func (h *handler) uploadArtifacts() error { +func (h *handler) uploadArtifacts(ctx context.Context) error { + if err := ctx.Err(); err != nil { + return err + } + if h.workflowArtifact == nil { return fmt.Errorf("workflowArtifact is nil") } @@ -46,7 +51,7 @@ func (h *handler) uploadArtifacts() error { if !binaryFromURL { ui.Success(fmt.Sprintf("Loaded binary from: %s", h.inputs.OutputPath)) binaryResp, err := storageClient.UploadArtifactWithRetriesAndGetURL( - workflowID, storageclient.ArtifactTypeBinary, binaryData, "application/octet-stream") + ctx, workflowID, storageclient.ArtifactTypeBinary, binaryData, "application/octet-stream") if err != nil { return fmt.Errorf("uploading binary artifact: %w", err) } @@ -59,7 +64,7 @@ func (h *handler) uploadArtifacts() error { ui.Success(fmt.Sprintf("Loaded config from: %s", h.inputs.ConfigPath)) var err error configURL, err = storageClient.UploadArtifactWithRetriesAndGetURL( - workflowID, storageclient.ArtifactTypeConfig, configData, "text/plain") + ctx, workflowID, storageclient.ArtifactTypeConfig, configData, "text/plain") if err != nil { return fmt.Errorf("uploading config artifact: %w", err) } diff --git a/cmd/workflow/deploy/artifacts_test.go b/cmd/workflow/deploy/artifacts_test.go index 24833d9c..a70e6405 100644 --- a/cmd/workflow/deploy/artifacts_test.go +++ b/cmd/workflow/deploy/artifacts_test.go @@ -2,6 +2,7 @@ package deploy import ( //nolint:gosec + "context" "encoding/json" "errors" "io" @@ -99,7 +100,7 @@ func TestUpload_SuccessAndErrorCases(t *testing.T) { ConfigData: []byte("configdata"), WorkflowID: "workflow-id", } - err := h.uploadArtifacts() + err := h.uploadArtifacts(context.Background()) require.NoError(t, err) require.Equal(t, "http://origin/get", h.inputs.BinaryURL) require.Equal(t, "http://origin/get", *h.inputs.ConfigURL) @@ -110,12 +111,12 @@ func TestUpload_SuccessAndErrorCases(t *testing.T) { ConfigData: nil, WorkflowID: "workflow-id", } - err = h.uploadArtifacts() + err = h.uploadArtifacts(context.Background()) require.NoError(t, err) // Error: workflowArtifact is nil h.workflowArtifact = nil - err = h.uploadArtifacts() + err = h.uploadArtifacts(context.Background()) require.ErrorContains(t, err, "workflowArtifact is nil") // Error: empty BinaryData @@ -124,7 +125,7 @@ func TestUpload_SuccessAndErrorCases(t *testing.T) { ConfigData: []byte("configdata"), WorkflowID: "workflow-id", } - err = h.uploadArtifacts() + err = h.uploadArtifacts(context.Background()) require.ErrorContains(t, err, "uploading binary artifact: content is empty for artifactType BINARY") // Error: workflowID is empty @@ -133,7 +134,7 @@ func TestUpload_SuccessAndErrorCases(t *testing.T) { ConfigData: []byte("configdata"), WorkflowID: "", } - err = h.uploadArtifacts() + err = h.uploadArtifacts(context.Background()) require.ErrorContains(t, err, "workflowID is empty") } @@ -174,7 +175,7 @@ func TestUploadArtifactToStorageService_OriginError(t *testing.T) { ConfigData: []byte("configdata"), WorkflowID: "workflow-id", } - err := h.uploadArtifacts() + err := h.uploadArtifacts(context.Background()) require.ErrorContains(t, err, "upload to origin") } @@ -240,7 +241,7 @@ func TestUploadArtifactToStorageService_AlreadyExistsError(t *testing.T) { ConfigData: []byte("configdata"), WorkflowID: "workflow-id", } - err := h.uploadArtifacts() + err := h.uploadArtifacts(context.Background()) require.NoError(t, err) require.Equal(t, "http://origin/get", h.inputs.BinaryURL) require.Equal(t, "http://origin/get", *h.inputs.ConfigURL) @@ -291,7 +292,7 @@ func TestUpload_UsesResolvedWorkflowOwnerForPresignedUrls(t *testing.T) { WorkflowID: "workflow-id", } - err := h.uploadArtifacts() + err := h.uploadArtifacts(context.Background()) require.NoError(t, err) require.NotEmpty(t, ownersUsed) for _, owner := range ownersUsed { diff --git a/cmd/workflow/deploy/auto_link.go b/cmd/workflow/deploy/auto_link.go index 7e393dc3..e350033f 100644 --- a/cmd/workflow/deploy/auto_link.go +++ b/cmd/workflow/deploy/auto_link.go @@ -22,10 +22,10 @@ const ( ) // ensureOwnerLinkedOrFail checks if the owner is linked and attempts auto-link if needed -func (h *handler) ensureOwnerLinkedOrFail(onChain *settings.OnChainRegistry) error { +func (h *handler) ensureOwnerLinkedOrFail(ctx context.Context, onChain *settings.OnChainRegistry) error { ownerAddr := common.HexToAddress(h.inputs.WorkflowOwner) - linked, err := h.wrc.IsOwnerLinked(h.execCtx, ownerAddr) + linked, err := h.wrc.IsOwnerLinked(ctx, ownerAddr) if err != nil { return fmt.Errorf("failed to check owner link status: %w", err) } @@ -34,7 +34,7 @@ func (h *handler) ensureOwnerLinkedOrFail(onChain *settings.OnChainRegistry) err if linked { // Owner is linked on contract, now verify it's linked to the current user's account - linkedToCurrentUser, err := h.checkLinkStatusViaGraphQL(ownerAddr) + linkedToCurrentUser, err := h.checkLinkStatusViaGraphQL(ctx, ownerAddr) if err != nil { return fmt.Errorf("failed to validate key ownership: %w", err) } @@ -48,14 +48,14 @@ func (h *handler) ensureOwnerLinkedOrFail(onChain *settings.OnChainRegistry) err } ui.Dim(fmt.Sprintf("Owner not linked. Attempting auto-link: owner=%s", ownerAddr.Hex())) - if err := h.tryAutoLink(onChain); err != nil { + if err := h.tryAutoLink(ctx, onChain); err != nil { return fmt.Errorf("auto-link attempt failed: %w", err) } ui.Success(fmt.Sprintf("Auto-link successful: owner=%s", ownerAddr.Hex())) // Wait for linking process to complete - if err := h.waitForBackendLinkProcessing(ownerAddr); err != nil { + if err := h.waitForBackendLinkProcessing(ctx, ownerAddr); err != nil { return fmt.Errorf("linking process failed: %w", err) } @@ -63,17 +63,17 @@ func (h *handler) ensureOwnerLinkedOrFail(onChain *settings.OnChainRegistry) err } // autoLinkMSIGAndExit handles MSIG auto-link and exits if manual intervention is needed -func (h *handler) autoLinkMSIGAndExit(onChain *settings.OnChainRegistry) (halt bool, err error) { +func (h *handler) autoLinkMSIGAndExit(ctx context.Context, onChain *settings.OnChainRegistry) (halt bool, err error) { ownerAddr := common.HexToAddress(h.inputs.WorkflowOwner) - linked, err := h.wrc.IsOwnerLinked(h.execCtx, ownerAddr) + linked, err := h.wrc.IsOwnerLinked(ctx, ownerAddr) if err != nil { return false, fmt.Errorf("failed to check owner link status: %w", err) } if linked { // Owner is linked on contract, now verify it's linked to the current user's account - linkedToCurrentUser, err := h.checkLinkStatusViaGraphQL(ownerAddr) + linkedToCurrentUser, err := h.checkLinkStatusViaGraphQL(ctx, ownerAddr) if err != nil { return false, fmt.Errorf("failed to validate MSIG key ownership: %w", err) } @@ -89,7 +89,7 @@ func (h *handler) autoLinkMSIGAndExit(onChain *settings.OnChainRegistry) (halt b ui.Dim(fmt.Sprintf("MSIG workflow owner link status: owner=%s, linked=%v", ownerAddr.Hex(), linked)) ui.Dim(fmt.Sprintf("MSIG owner: attempting auto-link... owner=%s", ownerAddr.Hex())) - if err := h.tryAutoLink(onChain); err != nil { + if err := h.tryAutoLink(ctx, onChain); err != nil { return false, fmt.Errorf("MSIG auto-link attempt failed: %w", err) } @@ -98,7 +98,7 @@ func (h *handler) autoLinkMSIGAndExit(onChain *settings.OnChainRegistry) (halt b } // tryAutoLink executes the auto-link process using the link-key command -func (h *handler) tryAutoLink(onChain *settings.OnChainRegistry) error { +func (h *handler) tryAutoLink(ctx context.Context, onChain *settings.OnChainRegistry) error { rtx := &runtime.Context{ Settings: h.settings, Credentials: h.credentials, @@ -107,7 +107,7 @@ func (h *handler) tryAutoLink(onChain *settings.OnChainRegistry) error { EnvironmentSet: h.environmentSet, } - return linkkey.Exec(h.execCtx, rtx, linkkey.Inputs{ + return linkkey.Exec(ctx, rtx, linkkey.Inputs{ WorkflowOwner: h.inputs.WorkflowOwner, WorkflowRegistryContractAddress: onChain.Address(), WorkflowOwnerLabel: h.inputs.OwnerLabel, @@ -115,7 +115,7 @@ func (h *handler) tryAutoLink(onChain *settings.OnChainRegistry) error { } // checkLinkStatusViaGraphQL checks if the owner is linked and verified by querying the service -func (h *handler) checkLinkStatusViaGraphQL(ownerAddr common.Address) (bool, error) { +func (h *handler) checkLinkStatusViaGraphQL(ctx context.Context, ownerAddr common.Address) (bool, error) { const query = ` query { listWorkflowOwners(filters: { linkStatus: LINKED_ONLY }) { @@ -137,7 +137,7 @@ func (h *handler) checkLinkStatusViaGraphQL(ownerAddr common.Address) (bool, err } gql := graphqlclient.New(h.credentials, h.environmentSet, h.log) - if err := gql.Execute(context.Background(), req, &resp); err != nil { + if err := gql.Execute(ctx, req, &resp); err != nil { return false, fmt.Errorf("GraphQL query failed: %w", err) } @@ -169,7 +169,7 @@ func (h *handler) checkLinkStatusViaGraphQL(ownerAddr common.Address) (bool, err } // waitForBackendLinkProcessing polls the service until the link is processed -func (h *handler) waitForBackendLinkProcessing(ownerAddr common.Address) error { +func (h *handler) waitForBackendLinkProcessing(ctx context.Context, ownerAddr common.Address) error { const maxAttempts = 5 const retryDelay = 3 * time.Second const initialBlockWait = 36 * time.Second // Wait for 3 block confirmations (~12s per block) @@ -181,11 +181,15 @@ func (h *handler) waitForBackendLinkProcessing(ownerAddr common.Address) error { ui.Line() // Wait for 3 block confirmations before polling - time.Sleep(initialBlockWait) + select { + case <-time.After(initialBlockWait): + case <-ctx.Done(): + return ctx.Err() + } err := retry.Do( func() error { - linked, err := h.checkLinkStatusViaGraphQL(ownerAddr) + linked, err := h.checkLinkStatusViaGraphQL(ctx, ownerAddr) if err != nil { h.log.Warn().Err(err).Msg("Failed to check link status") return err // Return error to trigger retry @@ -199,6 +203,7 @@ func (h *handler) waitForBackendLinkProcessing(ownerAddr common.Address) error { retry.Delay(retryDelay), retry.DelayType(retry.FixedDelay), // Use fixed 3s delay between retries retry.LastErrorOnly(true), + retry.Context(ctx), retry.OnRetry(func(n uint, err error) { h.log.Debug().Uint("attempt", n+1).Uint("maxAttempts", maxAttempts).Err(err).Msg("Retrying link status check") ui.Dim(fmt.Sprintf(" Waiting for verification... (attempt %d/%d)", n+1, maxAttempts)) diff --git a/cmd/workflow/deploy/auto_link_test.go b/cmd/workflow/deploy/auto_link_test.go index e192ccfa..f12d767b 100644 --- a/cmd/workflow/deploy/auto_link_test.go +++ b/cmd/workflow/deploy/auto_link_test.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "encoding/json" "net/http" "net/http/httptest" @@ -164,7 +165,7 @@ func TestCheckLinkStatusViaGraphQL(t *testing.T) { // Test the function ownerAddr := common.HexToAddress(tt.ownerAddress) - result, err := h.checkLinkStatusViaGraphQL(ownerAddr) + result, err := h.checkLinkStatusViaGraphQL(context.Background(), ownerAddr) if tt.expectError { assert.Error(t, err) @@ -335,7 +336,7 @@ func TestWaitForBackendLinkProcessing(t *testing.T) { // Test the function ownerAddr := common.HexToAddress(tt.ownerAddress) - err := h.waitForBackendLinkProcessing(ownerAddr) + err := h.waitForBackendLinkProcessing(context.Background(), ownerAddr) if tt.expectError { assert.Error(t, err) diff --git a/cmd/workflow/deploy/compile.go b/cmd/workflow/deploy/compile.go index ecb3c064..ffa42bdf 100644 --- a/cmd/workflow/deploy/compile.go +++ b/cmd/workflow/deploy/compile.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "fmt" "os" @@ -9,7 +10,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/ui" ) -func (h *handler) Compile() error { +func (h *handler) Compile(ctx context.Context) error { if !h.validated { return fmt.Errorf("handler h.inputs not validated") } @@ -67,7 +68,7 @@ func (h *handler) Compile() error { h.runtimeContext.Workflow.Language = cmdcommon.GetWorkflowLanguage(workflowMainFile) } - wasmFile, err = cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{ + wasmFile, err = cmdcommon.CompileWorkflowToWasm(ctx, resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{ StripSymbols: true, SkipTypeChecks: h.inputs.SkipTypeChecks, }) diff --git a/cmd/workflow/deploy/compile_test.go b/cmd/workflow/deploy/compile_test.go index 149ac19a..b8abe6ee 100644 --- a/cmd/workflow/deploy/compile_test.go +++ b/cmd/workflow/deploy/compile_test.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "encoding/base64" "errors" "io" @@ -266,12 +267,11 @@ func runCompile(simulatedEnvironment *chainsim.SimulatedEnvironment, inputs Inpu handler.settings = ctx.Settings handler.inputs = inputs - err := handler.ValidateInputs() - if err != nil { + if err := handler.ValidateInputs(); err != nil { return err } - return handler.Compile() + return handler.Compile(context.Background()) } // outputPathWithExtensions returns the path with .wasm.br.b64 appended as in Compile(). @@ -286,7 +286,7 @@ func outputPathWithExtensions(path string) string { // file content equals CompileWorkflowToWasm(workflowPath) + brotli + base64. func assertCompileOutputMatchesUnderlying(t *testing.T, simulatedEnvironment *chainsim.SimulatedEnvironment, inputs Inputs, ownerType string) { t.Helper() - wasm, err := cmdcommon.CompileWorkflowToWasm(inputs.WorkflowPath, cmdcommon.WorkflowCompileOptions{ + wasm, err := cmdcommon.CompileWorkflowToWasm(context.Background(), inputs.WorkflowPath, cmdcommon.WorkflowCompileOptions{ StripSymbols: true, SkipTypeChecks: inputs.SkipTypeChecks, }) @@ -435,7 +435,7 @@ func TestCompileWithWasmPath(t *testing.T) { handler.validated = true // Compile() with URL wasm should return nil (skips compile entirely). - err := handler.Compile() + err := handler.Compile(context.Background()) require.NoError(t, err) }) diff --git a/cmd/workflow/deploy/deploy.go b/cmd/workflow/deploy/deploy.go index 34f68f9b..11952c21 100644 --- a/cmd/workflow/deploy/deploy.go +++ b/cmd/workflow/deploy/deploy.go @@ -73,8 +73,6 @@ type handler struct { // existingWorkflowStatus stores the status of an existing workflow when updating. // nil means this is a new workflow, otherwise it contains the current status (0=active, 1=paused). existingWorkflowStatus *uint8 - - execCtx context.Context } var defaultOutputPath = "./binary.wasm.br.b64" @@ -211,8 +209,6 @@ func (h *handler) Execute(ctx context.Context) error { return fmt.Errorf("handler inputs not validated") } - h.execCtx = ctx - deployAccess, err := h.credentials.GetDeploymentAccessStatus() if err != nil { return fmt.Errorf("failed to check deployment access: %w", err) @@ -222,23 +218,27 @@ func (h *handler) Execute(ctx context.Context) error { return h.accessRequester.PromptAndSubmitRequest(ctx) } - adapter, err := newRegistryDeployStrategy(h.runtimeContext.ResolvedRegistry, h) + adapter, err := newRegistryDeployStrategy(ctx, h.runtimeContext.ResolvedRegistry, h) if err != nil { return err } - if err := h.prepareArtifacts(); err != nil { + if err := h.prepareArtifacts(ctx); err != nil { + return err + } + + if err := ctx.Err(); err != nil { return err } - if err := adapter.RunPreDeployChecks(); err != nil { + if err := adapter.RunPreDeployChecks(ctx); err != nil { if errors.Is(err, errDeployHalted) { return nil } return err } - exists, existingStatus, err := adapter.CheckWorkflowExists( + exists, existingStatus, err := adapter.CheckWorkflowExists(ctx, h.inputs.WorkflowOwner, h.inputs.WorkflowName, h.inputs.WorkflowTag, @@ -259,11 +259,11 @@ func (h *handler) Execute(ctx context.Context) error { ui.Line() ui.Dim("Uploading files...") - if err := h.uploadArtifacts(); err != nil { + if err := h.uploadArtifacts(ctx); err != nil { return fmt.Errorf("failed to upload workflow: %w", err) } - err = adapter.Upsert() + err = adapter.Upsert(ctx) if err == nil { warnIfPausedWorkflowUpdate(h.existingWorkflowStatus) } @@ -273,7 +273,11 @@ func (h *handler) Execute(ctx context.Context) error { // prepareArtifacts handles compile/fetch, artifact preparation, and hashing. // Artifact upload is deferred to the deploy service so it runs after any // existing-workflow update confirmation. -func (h *handler) prepareArtifacts() error { +func (h *handler) prepareArtifacts(ctx context.Context) error { + if err := ctx.Err(); err != nil { + return err + } + workflowcommon.DisplayWorkflowDetails( h.settings, h.runtimeContext, @@ -285,14 +289,14 @@ func (h *handler) prepareArtifacts() error { if cmdcommon.IsURL(h.inputs.WasmPath) { h.inputs.BinaryURL = h.inputs.WasmPath ui.Dim("Fetching binary from URL for workflow ID computation...") - fetched, err := cmdcommon.FetchURL(h.inputs.WasmPath) + fetched, err := cmdcommon.FetchURL(ctx, h.inputs.WasmPath) if err != nil { return fmt.Errorf("failed to fetch binary from URL: %w", err) } h.urlBinaryData = fetched ui.Success(fmt.Sprintf("Using binary URL: %s", h.inputs.WasmPath)) } else { - if err := h.Compile(); err != nil { + if err := h.Compile(ctx); err != nil { return fmt.Errorf("failed to compile workflow: %w", err) } } @@ -302,7 +306,7 @@ func (h *handler) prepareArtifacts() error { h.inputs.ConfigURL = &url h.inputs.ConfigPath = "" ui.Dim("Fetching config from URL for workflow ID computation...") - fetched, err := cmdcommon.FetchURL(url) + fetched, err := cmdcommon.FetchURL(ctx, url) if err != nil { return fmt.Errorf("failed to fetch config from URL: %w", err) } diff --git a/cmd/workflow/deploy/private_registry_test.go b/cmd/workflow/deploy/private_registry_test.go index db0ed61b..098b669b 100644 --- a/cmd/workflow/deploy/private_registry_test.go +++ b/cmd/workflow/deploy/private_registry_test.go @@ -313,10 +313,9 @@ func TestCheckWorkflowExists_PrivateRegistry(t *testing.T) { defer gqlServer.Close() h.environmentSet.GraphQLURL = gqlServer.URL - h.execCtx = context.Background() strategy := newPrivateRegistryDeployStrategy(h) - exists, status, err := strategy.CheckWorkflowExists("", "jnowak-workflow-test-v5", "", tt.workflowID) + exists, status, err := strategy.CheckWorkflowExists(context.Background(), "", "jnowak-workflow-test-v5", "", tt.workflowID) if tt.wantErr { require.Error(t, err) if tt.errMsg != "" { diff --git a/cmd/workflow/deploy/register.go b/cmd/workflow/deploy/register.go index 29c6ac67..1d884bde 100644 --- a/cmd/workflow/deploy/register.go +++ b/cmd/workflow/deploy/register.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "encoding/hex" "fmt" "time" @@ -14,7 +15,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/ui" ) -func (h *handler) upsert(onChain *settings.OnChainRegistry) error { +func (h *handler) upsert(ctx context.Context, onChain *settings.OnChainRegistry) error { if !h.validated { return fmt.Errorf("handler inputs not validated") } @@ -23,7 +24,7 @@ func (h *handler) upsert(onChain *settings.OnChainRegistry) error { if err != nil { return err } - return h.handleUpsert(params, onChain) + return h.handleUpsert(ctx, params, onChain) } func (h *handler) prepareUpsertParams() (client.RegisterWorkflowV2Parameters, error) { @@ -53,11 +54,11 @@ func (h *handler) prepareUpsertParams() (client.RegisterWorkflowV2Parameters, er }, nil } -func (h *handler) handleUpsert(params client.RegisterWorkflowV2Parameters, onChain *settings.OnChainRegistry) error { +func (h *handler) handleUpsert(ctx context.Context, params client.RegisterWorkflowV2Parameters, onChain *settings.OnChainRegistry) error { workflowName := h.inputs.WorkflowName workflowTag := h.inputs.WorkflowTag h.log.Debug().Interface("Workflow parameters", params).Msg("Registering workflow...") - txOut, err := h.wrc.UpsertWorkflow(h.execCtx, params) + txOut, err := h.wrc.UpsertWorkflow(ctx, params) if err != nil { return fmt.Errorf("failed to register workflow: %w", err) } diff --git a/cmd/workflow/deploy/register_test.go b/cmd/workflow/deploy/register_test.go index b039aaf5..60e296ea 100644 --- a/cmd/workflow/deploy/register_test.go +++ b/cmd/workflow/deploy/register_test.go @@ -56,18 +56,15 @@ func TestWorkflowUpsert(t *testing.T) { err = handler.ValidateInputs() require.NoError(t, err) - wfArt := workflowArtifact{ + handler.workflowArtifact = &workflowArtifact{ BinaryData: []byte("0x1234"), ConfigData: []byte("config"), WorkflowID: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", } - handler.workflowArtifact = &wfArt - handler.execCtx = context.Background() - onChain, err := settings.AsOnChain(ctx.ResolvedRegistry, "test") require.NoError(t, err) - err = handler.upsert(onChain) + err = handler.upsert(context.Background(), onChain) require.NoError(t, err) }) } diff --git a/cmd/workflow/deploy/registry_deploy_strategy.go b/cmd/workflow/deploy/registry_deploy_strategy.go index 34a5c431..419d4c41 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy.go +++ b/cmd/workflow/deploy/registry_deploy_strategy.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "errors" "github.com/smartcontractkit/cre-cli/internal/settings" @@ -22,23 +23,23 @@ type registryDeployStrategy interface { // RunPreDeployChecks validates readiness and runs registry-specific // prechecks (ownership linking, duplicate detection, etc.). // Return errDeployHalted to stop the deploy without returning an error. - RunPreDeployChecks() error + RunPreDeployChecks(ctx context.Context) error // CheckWorkflowExists returns whether a same-name workflow exists for this // registry target and includes the existing workflow status for updates. // When the existing workflow ID matches workflowID, exists is true and // errWorkflowUnchanged is returned to block redeployment of identical artifacts. - CheckWorkflowExists(workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) + CheckWorkflowExists(ctx context.Context, workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) // Upsert registers or updates the workflow in the target registry // and displays the result. - Upsert() error + Upsert(ctx context.Context) error } // newRegistryDeployStrategy returns the appropriate strategy for the given target. -func newRegistryDeployStrategy(resolvedRegistry settings.ResolvedRegistry, h *handler) (registryDeployStrategy, error) { +func newRegistryDeployStrategy(ctx context.Context, resolvedRegistry settings.ResolvedRegistry, h *handler) (registryDeployStrategy, error) { if resolvedRegistry.Type() == settings.RegistryTypeOffChain { return newPrivateRegistryDeployStrategy(h), nil } - return newOnchainRegistryDeployStrategy(h) + return newOnchainRegistryDeployStrategy(ctx, h) } diff --git a/cmd/workflow/deploy/registry_deploy_strategy_onchain.go b/cmd/workflow/deploy/registry_deploy_strategy_onchain.go index 032de838..cd1eafc8 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy_onchain.go +++ b/cmd/workflow/deploy/registry_deploy_strategy_onchain.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "fmt" "sync" @@ -23,7 +24,7 @@ type onchainRegistryDeployStrategy struct { initErr error } -func newOnchainRegistryDeployStrategy(h *handler) (*onchainRegistryDeployStrategy, error) { +func newOnchainRegistryDeployStrategy(ctx context.Context, h *handler) (*onchainRegistryDeployStrategy, error) { onChain, err := settings.AsOnChain(h.runtimeContext.ResolvedRegistry, "deploy") if err != nil { return nil, err @@ -33,7 +34,7 @@ func newOnchainRegistryDeployStrategy(h *handler) (*onchainRegistryDeployStrateg a.wg.Add(1) go func() { defer a.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(h.execCtx) + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client(ctx) if err != nil { a.initErr = fmt.Errorf("failed to create workflow registry client: %w", err) return @@ -44,10 +45,27 @@ func newOnchainRegistryDeployStrategy(h *handler) (*onchainRegistryDeployStrateg return a, nil } -func (a *onchainRegistryDeployStrategy) RunPreDeployChecks() error { +func waitWithContext(ctx context.Context, wg *sync.WaitGroup) error { + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (a *onchainRegistryDeployStrategy) RunPreDeployChecks(ctx context.Context) error { h := a.h - a.wg.Wait() + if err := waitWithContext(ctx, &a.wg); err != nil { + return err + } if a.initErr != nil { return a.initErr } @@ -55,7 +73,7 @@ func (a *onchainRegistryDeployStrategy) RunPreDeployChecks() error { ui.Line() ui.Dim("Verifying ownership...") if h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerType == constants.WorkflowOwnerTypeMSIG { - halt, err := h.autoLinkMSIGAndExit(a.onChain) + halt, err := h.autoLinkMSIGAndExit(ctx, a.onChain) if err != nil { return fmt.Errorf("failed to check/handle MSIG owner link status: %w", err) } @@ -63,7 +81,7 @@ func (a *onchainRegistryDeployStrategy) RunPreDeployChecks() error { return errDeployHalted } } else { - if err := h.ensureOwnerLinkedOrFail(a.onChain); err != nil { + if err := h.ensureOwnerLinkedOrFail(ctx, a.onChain); err != nil { return err } } @@ -71,8 +89,8 @@ func (a *onchainRegistryDeployStrategy) RunPreDeployChecks() error { return nil } -func (a *onchainRegistryDeployStrategy) CheckWorkflowExists(workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) { - workflow, err := a.wrc.GetWorkflow(a.h.execCtx, common.HexToAddress(workflowOwner), workflowName, workflowTag) +func (a *onchainRegistryDeployStrategy) CheckWorkflowExists(ctx context.Context, workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) { + workflow, err := a.wrc.GetWorkflow(ctx, common.HexToAddress(workflowOwner), workflowName, workflowTag) if err != nil { return false, nil, err } @@ -88,11 +106,11 @@ func (a *onchainRegistryDeployStrategy) CheckWorkflowExists(workflowOwner, workf return false, nil, nil } -func (a *onchainRegistryDeployStrategy) Upsert() error { +func (a *onchainRegistryDeployStrategy) Upsert(ctx context.Context) error { h := a.h if err := checkUserDonLimitBeforeDeploy( - h.execCtx, + ctx, a.wrc, a.wrc, common.HexToAddress(h.inputs.WorkflowOwner), @@ -106,7 +124,7 @@ func (a *onchainRegistryDeployStrategy) Upsert() error { ui.Line() ui.Dim("Preparing deployment transaction...") - if err := h.upsert(a.onChain); err != nil { + if err := h.upsert(ctx, a.onChain); err != nil { return fmt.Errorf("failed to register workflow: %w", err) } return nil diff --git a/cmd/workflow/deploy/registry_deploy_strategy_private.go b/cmd/workflow/deploy/registry_deploy_strategy_private.go index 9909800e..1369fd8a 100644 --- a/cmd/workflow/deploy/registry_deploy_strategy_private.go +++ b/cmd/workflow/deploy/registry_deploy_strategy_private.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "fmt" "strings" @@ -27,14 +28,14 @@ func (a *privateRegistryDeployStrategy) ensureClient() { } } -func (a *privateRegistryDeployStrategy) RunPreDeployChecks() error { +func (a *privateRegistryDeployStrategy) RunPreDeployChecks(_ context.Context) error { return nil } -func (a *privateRegistryDeployStrategy) CheckWorkflowExists(_, workflowName, _, workflowID string) (bool, *uint8, error) { +func (a *privateRegistryDeployStrategy) CheckWorkflowExists(ctx context.Context, _, workflowName, _, workflowID string) (bool, *uint8, error) { a.ensureClient() - workflow, err := a.prc.GetWorkflowByName(a.h.execCtx, workflowName) + workflow, err := a.prc.GetWorkflowByName(ctx, workflowName) if err == nil { if workflow.WorkflowID == workflowID { return true, offchainStatusToUint8(workflow.Status), fmt.Errorf("workflow with id %s is already registered and unchanged; re-deployment skipped: %w", workflowID, errWorkflowUnchanged) @@ -48,7 +49,7 @@ func (a *privateRegistryDeployStrategy) CheckWorkflowExists(_, workflowName, _, return false, nil, err } -func (a *privateRegistryDeployStrategy) Upsert() error { +func (a *privateRegistryDeployStrategy) Upsert(ctx context.Context) error { a.ensureClient() h := a.h @@ -57,7 +58,7 @@ func (a *privateRegistryDeployStrategy) Upsert() error { ui.Line() ui.Dim(fmt.Sprintf("Registering workflow in private registry (workflowID: %s)...", input.WorkflowID)) - result, err := a.prc.UpsertWorkflowInRegistry(a.h.execCtx, input) + result, err := a.prc.UpsertWorkflowInRegistry(ctx, input) if err != nil { return fmt.Errorf("failed to register workflow in private registry: %w", err) } diff --git a/cmd/workflow/hash/hash.go b/cmd/workflow/hash/hash.go index 49533870..7efdb434 100644 --- a/cmd/workflow/hash/hash.go +++ b/cmd/workflow/hash/hash.go @@ -1,6 +1,7 @@ package hash import ( + "context" "fmt" "os" "strings" @@ -62,7 +63,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { DerivedOwner: runtimeContext.DerivedWorkflowOwner, } - return Execute(inputs) + return Execute(cmd.Context(), inputs) }, } @@ -81,8 +82,8 @@ func New(runtimeContext *runtime.Context) *cobra.Command { return hashCmd } -func Execute(inputs Inputs) error { - rawBinary, err := loadBinary(inputs.WasmPath, inputs.WorkflowPath, inputs.SkipTypeChecks) +func Execute(ctx context.Context, inputs Inputs) error { + rawBinary, err := loadBinary(ctx, inputs.WasmPath, inputs.WorkflowPath, inputs.SkipTypeChecks) if err != nil { return err } @@ -92,7 +93,7 @@ func Execute(inputs Inputs) error { return fmt.Errorf("failed to compress binary: %w", err) } - config, err := loadConfig(inputs.ConfigPath) + config, err := loadConfig(ctx, inputs.ConfigPath) if err != nil { return err } @@ -190,11 +191,11 @@ func isPrivateRegistryID(deploymentRegistry string) bool { return strings.EqualFold(deploymentRegistry, "private") } -func loadBinary(wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) ([]byte, error) { +func loadBinary(ctx context.Context, wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) ([]byte, error) { if wasmFlag != "" { if cmdcommon.IsURL(wasmFlag) { ui.Dim("Fetching WASM binary from URL...") - data, err := cmdcommon.FetchURL(wasmFlag) + data, err := cmdcommon.FetchURL(ctx, wasmFlag) if err != nil { return nil, fmt.Errorf("failed to fetch WASM from URL: %w", err) } @@ -221,7 +222,7 @@ func loadBinary(wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) spinner := ui.NewSpinner() spinner.Start("Compiling workflow...") - wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{ + wasmBytes, err := cmdcommon.CompileWorkflowToWasm(ctx, resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{ StripSymbols: true, SkipTypeChecks: skipTypeChecks, }) @@ -235,13 +236,13 @@ func loadBinary(wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) return wasmBytes, nil } -func loadConfig(configPath string) ([]byte, error) { +func loadConfig(ctx context.Context, configPath string) ([]byte, error) { if configPath == "" { return nil, nil } if cmdcommon.IsURL(configPath) { ui.Dim("Fetching config from URL...") - data, err := cmdcommon.FetchURL(configPath) + data, err := cmdcommon.FetchURL(ctx, configPath) if err != nil { return nil, fmt.Errorf("failed to fetch config from URL: %w", err) } diff --git a/cmd/workflow/hash/hash_test.go b/cmd/workflow/hash/hash_test.go index 0ed08a82..14809402 100644 --- a/cmd/workflow/hash/hash_test.go +++ b/cmd/workflow/hash/hash_test.go @@ -1,6 +1,7 @@ package hash import ( + "context" "crypto/sha256" "encoding/hex" "io" @@ -80,7 +81,7 @@ func TestExecute_WithForUser(t *testing.T) { WorkflowName: "test-workflow", } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } @@ -94,7 +95,7 @@ func TestExecute_WithoutForUser_UsesPrivateKey(t *testing.T) { PrivateKey: testPrivateKey, } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } @@ -107,7 +108,7 @@ func TestExecute_WithoutForUser_NoKey_Errors(t *testing.T) { WorkflowName: "test-workflow", } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.Error(t, err) assert.Contains(t, err.Error(), "--public_key") } @@ -173,7 +174,7 @@ func TestExecute_HashesAreDeterministic(t *testing.T) { "workflow ID should start with version byte 00") // Running Execute should succeed (hashes are printed via ui, verified above) - err = Execute(inputs) + err = Execute(context.Background(), inputs) require.NoError(t, err) } @@ -187,7 +188,7 @@ func TestExecute_EmptyConfig(t *testing.T) { WorkflowName: "test-workflow", } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } @@ -201,7 +202,7 @@ func TestExecute_OffChainRequiresPublicKey(t *testing.T) { RegistryType: settings.RegistryTypeOffChain, } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.Error(t, err) assert.Contains(t, err.Error(), "--public_key") } @@ -218,7 +219,7 @@ func TestExecute_OffChainUsesPublicKey(t *testing.T) { DerivedOwner: testDerivedOwner, } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } @@ -233,7 +234,7 @@ func TestExecute_OffChainUsesDerivedOwner(t *testing.T) { DerivedOwner: testDerivedOwner, } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } diff --git a/cmd/workflow/simulate/simulate.go b/cmd/workflow/simulate/simulate.go index 7d0ca445..fe95ed2b 100644 --- a/cmd/workflow/simulate/simulate.go +++ b/cmd/workflow/simulate/simulate.go @@ -90,7 +90,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { if err != nil { return err } - return handler.Execute(inputs) + return handler.Execute(cmd.Context(), inputs) }, } @@ -252,14 +252,14 @@ func (h *handler) ValidateInputs(inputs Inputs) error { return nil } -func (h *handler) Execute(inputs Inputs) error { +func (h *handler) Execute(ctx context.Context, inputs Inputs) error { var wasmFileBinary []byte var err error if inputs.WasmPath != "" { if cmdcommon.IsURL(inputs.WasmPath) { ui.Dim("Fetching WASM binary from URL...") - wasmFileBinary, err = cmdcommon.FetchURL(inputs.WasmPath) + wasmFileBinary, err = cmdcommon.FetchURL(ctx, inputs.WasmPath) if err != nil { return fmt.Errorf("failed to fetch WASM from URL: %w", err) } @@ -298,7 +298,7 @@ func (h *handler) Execute(inputs Inputs) error { spinner := ui.NewSpinner() spinner.Start("Compiling workflow...") - wasmFileBinary, err = cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{ + wasmFileBinary, err = cmdcommon.CompileWorkflowToWasm(ctx, resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{ StripSymbols: false, SkipTypeChecks: inputs.SkipTypeChecks, }) @@ -343,7 +343,7 @@ func (h *handler) Execute(inputs Inputs) error { var config []byte if cmdcommon.IsURL(inputs.ConfigPath) { ui.Dim("Fetching config from URL...") - config, err = cmdcommon.FetchURL(inputs.ConfigPath) + config, err = cmdcommon.FetchURL(ctx, inputs.ConfigPath) if err != nil { return fmt.Errorf("failed to fetch config from URL: %w", err) } diff --git a/cmd/workflow/simulate/simulate_test.go b/cmd/workflow/simulate/simulate_test.go index 847d7ae2..4879b8f3 100644 --- a/cmd/workflow/simulate/simulate_test.go +++ b/cmd/workflow/simulate/simulate_test.go @@ -98,7 +98,7 @@ func TestBlankWorkflowSimulation(t *testing.T) { require.NoError(t, err) // Execute the simulation. We expect this to compile the workflow and run the simulator successfully. - err = handler.Execute(inputs) + err = handler.Execute(context.Background(), inputs) require.NoError(t, err, "Execute should not return an error") } diff --git a/internal/client/storageclient/storageclient.go b/internal/client/storageclient/storageclient.go index de16d790..9a798684 100644 --- a/internal/client/storageclient/storageclient.go +++ b/internal/client/storageclient/storageclient.go @@ -69,15 +69,15 @@ func (c *Client) SetHTTPTimeout(timeout time.Duration) { c.httpTimeout = timeout } -func (c *Client) CreateServiceContextWithTimeout() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), c.serviceTimeout) //nolint:gosec // G118 -- cancel is deferred by all callers +func (c *Client) CreateServiceContextWithTimeout(parent context.Context) (context.Context, context.CancelFunc) { + return context.WithTimeout(parent, c.serviceTimeout) //nolint:gosec // G118 -- cancel is deferred by all callers } -func (c *Client) CreateHttpContextWithTimeout() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), c.httpTimeout) //nolint:gosec // G118 -- cancel is deferred by all callers +func (c *Client) CreateHttpContextWithTimeout(parent context.Context) (context.Context, context.CancelFunc) { + return context.WithTimeout(parent, c.httpTimeout) //nolint:gosec // G118 -- cancel is deferred by all callers } -func (c *Client) GeneratePostUrlForArtifact(workflowId string, artifactType ArtifactType, content []byte) (GeneratePresignedPostUrlForArtifactResponse, error) { +func (c *Client) GeneratePostUrlForArtifact(ctx context.Context, workflowId string, artifactType ArtifactType, content []byte) (GeneratePresignedPostUrlForArtifactResponse, error) { const mutation = ` mutation GeneratePresignedPostUrlForArtifact($artifact: GeneratePresignedPostUrlRequest!) { generatePresignedPostUrlForArtifact(artifact: $artifact) { @@ -102,7 +102,7 @@ mutation GeneratePresignedPostUrlForArtifact($artifact: GeneratePresignedPostUrl GeneratePresignedPostUrlForArtifact GeneratePresignedPostUrlForArtifactResponse `json:"generatePresignedPostUrlForArtifact"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + ctx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() if err := c.graphql. @@ -116,7 +116,7 @@ mutation GeneratePresignedPostUrlForArtifact($artifact: GeneratePresignedPostUrl return container.GeneratePresignedPostUrlForArtifact, nil } -func (c *Client) GenerateUnsignedGetUrlForArtifact(workflowId string, artifactType ArtifactType) (GenerateUnsignedGetUrlForArtifactResponse, error) { +func (c *Client) GenerateUnsignedGetUrlForArtifact(ctx context.Context, workflowId string, artifactType ArtifactType) (GenerateUnsignedGetUrlForArtifactResponse, error) { const mutation = ` mutation GenerateUnsignedGetUrlForArtifact($artifact: GenerateUnsignedGetUrlRequest!) { generateUnsignedGetUrlForArtifact(artifact: $artifact) { @@ -134,7 +134,7 @@ mutation GenerateUnsignedGetUrlForArtifact($artifact: GenerateUnsignedGetUrlRequ GenerateUnsignedGetUrlForArtifact GenerateUnsignedGetUrlForArtifactResponse `json:"generateUnsignedGetUrlForArtifact"` } - ctx, cancel := c.CreateServiceContextWithTimeout() + ctx, cancel := c.CreateServiceContextWithTimeout(ctx) defer cancel() if err := c.graphql. @@ -154,7 +154,7 @@ func calculateContentHash(content []byte) string { return contentHash } -func (c *Client) UploadToOrigin(g GeneratePresignedPostUrlForArtifactResponse, content []byte, contentType string) error { +func (c *Client) UploadToOrigin(ctx context.Context, g GeneratePresignedPostUrlForArtifactResponse, content []byte, contentType string) error { c.log.Debug().Str("URL", g.PresignedPostURL).Msg("Uploading content to origin") var b bytes.Buffer @@ -197,7 +197,7 @@ func (c *Client) UploadToOrigin(g GeneratePresignedPostUrlForArtifactResponse, c return err } - ctx, cancel := c.CreateHttpContextWithTimeout() + ctx, cancel := c.CreateHttpContextWithTimeout(ctx) defer cancel() httpReq, err := http.NewRequestWithContext(ctx, "POST", g.PresignedPostURL, &b) @@ -231,10 +231,14 @@ func (c *Client) UploadToOrigin(g GeneratePresignedPostUrlForArtifactResponse, c } func (c *Client) UploadArtifactWithRetriesAndGetURL( + ctx context.Context, workflowID string, artifactType ArtifactType, content []byte, contentType string) (GenerateUnsignedGetUrlForArtifactResponse, error) { + if err := ctx.Err(); err != nil { + return GenerateUnsignedGetUrlForArtifactResponse{}, err + } if len(workflowID) == 0 { return GenerateUnsignedGetUrlForArtifactResponse{}, fmt.Errorf("workflowID is empty") } @@ -251,7 +255,7 @@ func (c *Client) UploadArtifactWithRetriesAndGetURL( err := retry.Do( func() error { var err error - g, err = c.GeneratePostUrlForArtifact(workflowID, artifactType, content) + g, err = c.GeneratePostUrlForArtifact(ctx, workflowID, artifactType, content) if err != nil { if strings.Contains(err.Error(), "already exists") { shouldUpload = false @@ -264,6 +268,7 @@ func (c *Client) UploadArtifactWithRetriesAndGetURL( }, retry.Attempts(3), retry.LastErrorOnly(true), + retry.Context(ctx), ) if err != nil { c.log.Error().Err(err).Msg("Failed to generate presigned post URL for artifact") @@ -276,10 +281,11 @@ func (c *Client) UploadArtifactWithRetriesAndGetURL( if shouldUpload { err = retry.Do( func() error { - return c.UploadToOrigin(g, content, contentType) + return c.UploadToOrigin(ctx, g, content, contentType) }, retry.Attempts(3), retry.LastErrorOnly(true), + retry.Context(ctx), ) if err != nil { c.log.Error().Err(err).Msg("Failed to upload content to origin") @@ -290,7 +296,7 @@ func (c *Client) UploadArtifactWithRetriesAndGetURL( var g2 GenerateUnsignedGetUrlForArtifactResponse err = retry.Do( func() error { - g2, err = c.GenerateUnsignedGetUrlForArtifact(workflowID, artifactType) + g2, err = c.GenerateUnsignedGetUrlForArtifact(ctx, workflowID, artifactType) if err != nil { return fmt.Errorf("generate unsigned get url: %w", err) } @@ -298,6 +304,7 @@ func (c *Client) UploadArtifactWithRetriesAndGetURL( }, retry.Attempts(3), retry.LastErrorOnly(true), + retry.Context(ctx), ) if err != nil { c.log.Error().Err(err).Msg("Failed to generate unsigned get URL for artifact") From f8a4b9a1fd0e962a19dd0654da319cd5113bad3f Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:18:26 +0100 Subject: [PATCH 25/29] Make the user context a hard requirement (#462) * Make the user context a hard requirement * e2e tests --- cmd/login/login.go | 3 +- cmd/login/login_test.go | 44 +++++++++++++ cmd/root.go | 19 +++++- internal/testutil/graphql_mock.go | 63 ++++++++++++++++--- .../multi_command_flows/account_happy_path.go | 13 ++-- .../multi_command_flows/secrets_happy_path.go | 27 ++++---- .../workflow_happy_path_1.go | 14 ++--- .../workflow_happy_path_2.go | 27 ++++---- .../workflow_happy_path_3.go | 40 +++++------- 9 files changed, 166 insertions(+), 84 deletions(-) diff --git a/cmd/login/login.go b/cmd/login/login.go index 5baa5ea8..1c0ce616 100644 --- a/cmd/login/login.go +++ b/cmd/login/login.go @@ -127,7 +127,8 @@ func (h *handler) execute(ctx context.Context) error { h.spinner.Update("Fetching user context...") if err := h.fetchTenantConfig(ctx, tokenSet); err != nil { - h.log.Debug().Err(err).Msgf("failed to fetch user context — %s not written", tenantctx.ContextFile) + h.spinner.StopAll() + return fmt.Errorf("failed to fetch user context: %w", err) } // Stop spinner before final output diff --git a/cmd/login/login_test.go b/cmd/login/login_test.go index 33bdb2fb..a5abfedb 100644 --- a/cmd/login/login_test.go +++ b/cmd/login/login_test.go @@ -1,6 +1,8 @@ package login import ( + "context" + "encoding/json" "io" "net/http" "net/http/httptest" @@ -16,9 +18,51 @@ import ( "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/oauth" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" "github.com/smartcontractkit/cre-cli/internal/ui" ) +func TestFetchTenantConfig_GQLError_ReturnsError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "errors": []map[string]string{{"message": "unauthorized"}}, + }) + })) + defer srv.Close() + + tmp := t.TempDir() + t.Setenv("HOME", tmp) + log := zerolog.Nop() + h := &handler{ + log: &log, + environmentSet: &environments.EnvironmentSet{ + EnvName: "STAGING", + GraphQLURL: srv.URL, + }, + } + + tokenSet := &credentials.CreLoginTokenSet{ + AccessToken: "test-access-token", + IDToken: "test-id-token", + ExpiresIn: 3600, + TokenType: "Bearer", + } + + err := h.fetchTenantConfig(context.Background(), tokenSet) + if err == nil { + t.Fatal("expected error when GQL fetch fails") + } + if !strings.Contains(err.Error(), "fetch user context") { + t.Errorf("expected fetch user context error, got: %v", err) + } + + contextPath := filepath.Join(tmp, credentials.ConfigDir, tenantctx.ContextFile) + if _, statErr := os.Stat(contextPath); statErr == nil { + t.Errorf("expected %s not to be written on fetch failure", tenantctx.ContextFile) + } +} + func TestLogin_NonInteractive_ReturnsError(t *testing.T) { // Create a parent command with the global --non-interactive persistent flag, // since in production this flag is defined on the root command. diff --git a/cmd/root.go b/cmd/root.go index aa149602..88191efe 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,6 +34,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/telemetry" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" "github.com/smartcontractkit/cre-cli/internal/ui" intupdate "github.com/smartcontractkit/cre-cli/internal/update" ) @@ -236,7 +237,14 @@ func newRootCommand() *cobra.Command { spinner.Update("Loading user context...") } if err := runtimeContext.AttachTenantContext(cmd.Context()); err != nil { - ui.Warning("Failed to load user context") + if showSpinner { + spinner.Stop() + } + ui.ErrorWithSuggestions("Failed to load user context", []string{ + "Run `cre login` to fetch your user context", + fmt.Sprintf("Ensure ~/.cre/%s exists and is readable", tenantctx.ContextFile), + }) + return fmt.Errorf("user context required: %w", err) } // Check if organization is ungated for commands that require it @@ -295,7 +303,14 @@ func newRootCommand() *cobra.Command { spinner.Update("Loading user context...") } if err := runtimeContext.AttachTenantContext(cmd.Context()); err != nil { - ui.Warning("Failed to load user context") + if showSpinner { + spinner.Stop() + } + ui.ErrorWithSuggestions("Failed to load user context", []string{ + "Run `cre login` to fetch your user context", + fmt.Sprintf("Ensure ~/.cre/%s exists and is readable", tenantctx.ContextFile), + }) + return fmt.Errorf("user context required: %w", err) } } } diff --git a/internal/testutil/graphql_mock.go b/internal/testutil/graphql_mock.go index 9f5ff73f..888f6f7e 100644 --- a/internal/testutil/graphql_mock.go +++ b/internal/testutil/graphql_mock.go @@ -10,8 +10,56 @@ import ( "github.com/smartcontractkit/cre-cli/internal/environments" ) +// MockGetCreOrganizationInfoGraphQLPayload returns a GraphQL response for getCreOrganizationInfo. +func MockGetCreOrganizationInfoGraphQLPayload() map[string]any { + return map[string]any{ + "data": map[string]any{ + "getCreOrganizationInfo": map[string]any{ + "orgId": "test-org-id", + "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, + }, + }, + } +} + +// QueryIsGetTenantConfig reports whether q is a getTenantConfig GraphQL operation. +func QueryIsGetTenantConfig(q string) bool { + return strings.Contains(q, "GetTenantConfig") || strings.Contains(q, "getTenantConfig") +} + +// MockGetTenantConfigGraphQLPayload returns a GraphQL response for getTenantConfig +// suitable for E2E tests using the anvil-devnet workflow registry defaults. +func MockGetTenantConfigGraphQLPayload() map[string]any { + return map[string]any{ + "data": map[string]any{ + "getTenantConfig": map[string]any{ + "tenantId": "test-tenant-id", + "defaultDonFamily": "test-don", + "vaultGatewayUrl": "https://vault.example.test", + "registries": []map[string]any{ + { + "id": "anvil-devnet", + "label": "anvil-devnet", + "type": "ON_CHAIN", + "chainSelector": "6433500567565415381", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "secretsAuthFlows": []string{"OWNER_KEY_SIGNING"}, + }, + { + "id": "private", + "label": "Private (Chainlink-hosted)", + "type": "OFF_CHAIN", + "secretsAuthFlows": []string{"BROWSER"}, + }, + }, + "forwarders": []any{}, + }, + }, + } +} + // NewGraphQLMockServerGetOrganization starts an httptest.Server that responds to -// getCreOrganizationInfo with a fixed orgId and derivedWorkflowOwners. +// getCreOrganizationInfo and getTenantConfig with fixed test payloads. // It sets EnvVarGraphQLURL so CLI commands use this server. Caller must defer srv.Close(). func NewGraphQLMockServerGetOrganization(t *testing.T) *httptest.Server { t.Helper() @@ -24,14 +72,11 @@ func NewGraphQLMockServerGetOrganization(t *testing.T) *httptest.Server { _ = json.NewDecoder(r.Body).Decode(&req) w.Header().Set("Content-Type", "application/json") if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(MockGetTenantConfigGraphQLPayload()) return } w.WriteHeader(http.StatusBadRequest) diff --git a/test/multi_command_flows/account_happy_path.go b/test/multi_command_flows/account_happy_path.go index e769d834..c31aba3b 100644 --- a/test/multi_command_flows/account_happy_path.go +++ b/test/multi_command_flows/account_happy_path.go @@ -58,14 +58,11 @@ func RunAccountHappyPath(t *testing.T, tc TestConfig, testEthURL, chainName stri // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } diff --git a/test/multi_command_flows/secrets_happy_path.go b/test/multi_command_flows/secrets_happy_path.go index f37fbfdf..71974060 100644 --- a/test/multi_command_flows/secrets_happy_path.go +++ b/test/multi_command_flows/secrets_happy_path.go @@ -24,6 +24,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/testutil" ) // Hex-encoded tdh2easy.PublicKey blob returned by the gateway @@ -63,14 +64,11 @@ func RunSecretsHappyPath(t *testing.T, tc TestConfig, chainName string) { // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } @@ -258,14 +256,11 @@ func RunSecretsListMsig(t *testing.T, tc TestConfig, chainName string) { // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } diff --git a/test/multi_command_flows/workflow_happy_path_1.go b/test/multi_command_flows/workflow_happy_path_1.go index 4433ac62..236cb652 100644 --- a/test/multi_command_flows/workflow_happy_path_1.go +++ b/test/multi_command_flows/workflow_happy_path_1.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/testutil" ) // TestConfig represents test configuration @@ -62,14 +63,11 @@ func workflowDeployEoaWithMockStorage(t *testing.T, tc TestConfig) (output strin // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } diff --git a/test/multi_command_flows/workflow_happy_path_2.go b/test/multi_command_flows/workflow_happy_path_2.go index 42c52bb8..eed85044 100644 --- a/test/multi_command_flows/workflow_happy_path_2.go +++ b/test/multi_command_flows/workflow_happy_path_2.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/constants" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/testutil" ) // workflowDeployEoa deploys a workflow via CLI, mocking GraphQL + Origin. @@ -35,14 +36,11 @@ func workflowDeployEoa(t *testing.T, tc TestConfig) string { // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } @@ -156,14 +154,11 @@ func workflowDeployUpdateWithConfig(t *testing.T, tc TestConfig) string { // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } diff --git a/test/multi_command_flows/workflow_happy_path_3.go b/test/multi_command_flows/workflow_happy_path_3.go index 5b890207..5e125e74 100644 --- a/test/multi_command_flows/workflow_happy_path_3.go +++ b/test/multi_command_flows/workflow_happy_path_3.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/testutil" ) // workflowInit runs cre init to initialize a new workflow project from scratch @@ -32,14 +33,11 @@ func workflowInit(t *testing.T, projectRootFlag, projectName, workflowName strin // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } @@ -101,14 +99,11 @@ func workflowDeployUnsigned(t *testing.T, tc TestConfig, projectRootFlag, workfl // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } @@ -220,14 +215,11 @@ func workflowDeployWithConfigAndLinkedKey(t *testing.T, tc TestConfig, projectRo // Handle authentication validation query if strings.Contains(req.Query, "getCreOrganizationInfo") { - _ = json.NewEncoder(w).Encode(map[string]any{ - "data": map[string]any{ - "getCreOrganizationInfo": map[string]any{ - "orgId": "test-org-id", - "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, - }, - }, - }) + _ = json.NewEncoder(w).Encode(testutil.MockGetCreOrganizationInfoGraphQLPayload()) + return + } + if testutil.QueryIsGetTenantConfig(req.Query) { + _ = json.NewEncoder(w).Encode(testutil.MockGetTenantConfigGraphQLPayload()) return } From 402731ec8ffb0eb6c82c66f8149a4dd1b9e51357 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 2 Jun 2026 11:20:43 -0400 Subject: [PATCH 26/29] Solana bindings write report only (#455) * starting on solana binding generator * added write methods * adding changes * adding anchor fork * Adding anchor required * changes * adding jen * moving anchor go into repo and adding gen command * only keep data storage contract * fake protos and add tests * Adding support for generate-bindings-solana * Cleaning up code and moving to a cre file * remove some files * docs * using real sdk * removing most mocks on cre-cli side * temp adding flakes * rename evm and solana * polish * clean * clean * comments * removing flakes * doc * tidy * lint * lint * lint * lint * lint * clean up * lint * tidy * lint * tidy * generate-bindings * docs * revert * adding back handler * lint * bump capabilities development branch * fix generator to accept ComuteConfig * fix field names collision (#341) * add handle complex enum logic (#343) * add handle complex enum logic * add negatie test * use untransformed type name and add nil check (#344) * fix out flag (#345) * Convert package level map into instance field (#346) * Convert package level map into instance field * rm sol generated files * Ozep/fix u256 dispatch (#456) * changing signing/hashing algo * add u256/i256 explicit handlers --------- Co-authored-by: yashnevatia * Ozep/fix silent rounding (#457) * changing signing/hashing algo * use ParseUint for 64 bit values * Ozep/fix u256 dispatch (#347) add u256/i256 explicit handlers --------- Co-authored-by: yashnevatia * Ozep/check vec name (#458) * changing signing/hashing algo * add vec len prevalidation * Ozep/fix u256 dispatch (#347) add u256/i256 explicit handlers * use ParseUint for 64 bit values (#348) --------- Co-authored-by: yashnevatia * Ozep/nil complex enum fix (#350) * changing signing/hashing algo * add case nil to unregistered variant types * Ozep/fix u256 dispatch (#347) add u256/i256 explicit handlers * use ParseUint for 64 bit values (#348) * add vec len prevalidation (#349) --------- Co-authored-by: yashnevatia * switch from uint8() to qual (#351) * Ozep/add package validation (#352) * add package name validation * fix options assigment before declaration * document forwarder prefix (#355) * fix strconv const handlers (#356) * fix enum idl types produce incompilable bindings (#357) * changing signing/hashing algo * fix enum idl types produce incompilable bindings --------- Co-authored-by: yashnevatia * apply to upperCase to raw idl references (#358) * add descriminator for zero arg instructions (#354) * rm unused constants (#353) * tidy * fix file permissions * make gendoc * bump tagged sdk-go * reference tagged capability version * replace print with dim * restore contracts --------- Co-authored-by: yashnevatia --- .../{bindings => evm}/README.md | 2 +- .../{bindings => evm}/abigen/FORK_METADATA.md | 0 .../{bindings => evm}/abigen/bind.go | 0 .../{bindings => evm}/abigen/bindv2.go | 0 .../{bindings => evm}/abigen/source.go.tpl | 0 .../{bindings => evm}/abigen/source2.go.tpl | 0 .../{bindings => evm}/abigen/template.go | 0 .../{bindings => evm}/bindgen.go | 4 +- .../{bindings => evm}/bindings_test.go | 4 +- cmd/generate-bindings/evm/evm.go | 469 +++++++ .../evm_test.go} | 143 +- .../{bindings => evm}/gen.go | 2 +- cmd/generate-bindings/evm/gen_test.go | 19 + .../{bindings => evm}/mockcontract.go.tpl | 0 .../{bindings => evm}/mockcontract.ts.tpl | 0 .../{bindings => evm}/sourcecre.go.tpl | 0 .../{bindings => evm}/sourcecre.ts.tpl | 0 .../testdata/DataStorage.sol | 0 .../testdata/DataStorage_combined.json | 0 .../{bindings => evm}/testdata/bindings.go | 0 .../testdata/bindings_mock.go | 0 .../testdata/emptybindings/EmptyContract.sol | 0 .../emptybindings/EmptyContract_combined.json | 0 .../testdata/emptybindings/emptybindings.go | 0 .../emptybindings/emptybindings_mock.go | 0 .../{bindings => evm}/testdata/gen/main.go | 6 +- cmd/generate-bindings/generate-bindings.go | 490 +------ .../solana/anchor-go/generator/accounts.go | 409 ++++++ .../generator/collision_field_names_test.go | 140 ++ .../anchor-go/generator/complex-enums.go | 48 + .../generator/complex_enum_encode_test.go | 86 ++ .../anchor-go/generator/complex_enums_test.go | 114 ++ .../solana/anchor-go/generator/constants.go | 408 ++++++ .../anchor-go/generator/constants_test.go | 1183 ++++++++++++++++ .../solana/anchor-go/generator/cre.go | 413 ++++++ .../anchor-go/generator/discriminator.go | 118 ++ .../solana/anchor-go/generator/doc.go | 34 + .../solana/anchor-go/generator/errors.go | 101 ++ .../solana/anchor-go/generator/events.go | 113 ++ .../solana/anchor-go/generator/fetchers.go | 18 + .../solana/anchor-go/generator/generator.go | 170 +++ .../solana/anchor-go/generator/gomod.go | 27 + .../solana/anchor-go/generator/id.go | 26 + .../anchor-go/generator/idl_validate.go | 215 +++ .../anchor-go/generator/idl_validate_test.go | 49 + .../anchor-go/generator/instructions.go | 797 +++++++++++ .../anchor-go/generator/instructions_test.go | 48 + .../solana/anchor-go/generator/is.go | 101 ++ .../solana/anchor-go/generator/marshal.go | 444 ++++++ .../solana/anchor-go/generator/tests.go | 18 + .../solana/anchor-go/generator/tools.go | 69 + .../solana/anchor-go/generator/types.go | 418 ++++++ .../solana/anchor-go/generator/types_test.go | 103 ++ .../solana/anchor-go/generator/u256_test.go | 148 ++ .../solana/anchor-go/generator/unmarshal.go | 432 ++++++ cmd/generate-bindings/solana/bindgen.go | 155 +++ cmd/generate-bindings/solana/bindings_test.go | 179 +++ cmd/generate-bindings/solana/gen.go | 2 + cmd/generate-bindings/solana/gen_test.go | 17 + cmd/generate-bindings/solana/solana.go | 259 ++++ cmd/generate-bindings/solana/solana_test.go | 285 ++++ .../testdata/contracts/idl/data_storage.json | 511 +++++++ .../testdata/contracts/source/data_storage.rs | 251 ++++ .../solana/testdata/data_storage/accounts.go | 50 + .../solana/testdata/data_storage/constants.go | 4 + .../testdata/data_storage/constructor.go | 88 ++ .../testdata/data_storage/discriminators.go | 30 + .../solana/testdata/data_storage/errors.go | 4 + .../solana/testdata/data_storage/events.go | 93 ++ .../solana/testdata/data_storage/fetchers.go | 4 + .../testdata/data_storage/instructions.go | 1204 +++++++++++++++++ .../testdata/data_storage/program_id.go | 8 + .../testdata/data_storage/tests_test.go | 4 + .../solana/testdata/data_storage/types.go | 763 +++++++++++ .../solana/testdata/gen/main.go | 17 + cmd/root.go | 48 +- docs/cre.md | 2 +- docs/cre_generate-bindings.md | 31 +- docs/cre_generate-bindings_evm.md | 51 + docs/cre_generate-bindings_solana.md | 46 + go.mod | 14 +- go.sum | 13 +- internal/constants/constants.go | 9 +- 83 files changed, 10857 insertions(+), 674 deletions(-) rename cmd/generate-bindings/{bindings => evm}/README.md (99%) rename cmd/generate-bindings/{bindings => evm}/abigen/FORK_METADATA.md (100%) rename cmd/generate-bindings/{bindings => evm}/abigen/bind.go (100%) rename cmd/generate-bindings/{bindings => evm}/abigen/bindv2.go (100%) rename cmd/generate-bindings/{bindings => evm}/abigen/source.go.tpl (100%) rename cmd/generate-bindings/{bindings => evm}/abigen/source2.go.tpl (100%) rename cmd/generate-bindings/{bindings => evm}/abigen/template.go (100%) rename cmd/generate-bindings/{bindings => evm}/bindgen.go (98%) rename cmd/generate-bindings/{bindings => evm}/bindings_test.go (99%) create mode 100644 cmd/generate-bindings/evm/evm.go rename cmd/generate-bindings/{generate-bindings_test.go => evm/evm_test.go} (85%) rename cmd/generate-bindings/{bindings => evm}/gen.go (67%) create mode 100644 cmd/generate-bindings/evm/gen_test.go rename cmd/generate-bindings/{bindings => evm}/mockcontract.go.tpl (100%) rename cmd/generate-bindings/{bindings => evm}/mockcontract.ts.tpl (100%) rename cmd/generate-bindings/{bindings => evm}/sourcecre.go.tpl (100%) rename cmd/generate-bindings/{bindings => evm}/sourcecre.ts.tpl (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/DataStorage.sol (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/DataStorage_combined.json (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/bindings.go (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/bindings_mock.go (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/emptybindings/EmptyContract.sol (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/emptybindings/EmptyContract_combined.json (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/emptybindings/emptybindings.go (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/emptybindings/emptybindings_mock.go (100%) rename cmd/generate-bindings/{bindings => evm}/testdata/gen/main.go (70%) create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/accounts.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/collision_field_names_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/complex-enums.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/complex_enum_encode_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/complex_enums_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/constants.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/constants_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/cre.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/discriminator.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/doc.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/errors.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/events.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/fetchers.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/generator.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/gomod.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/id.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/idl_validate.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/idl_validate_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/instructions.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/instructions_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/is.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/marshal.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/tests.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/tools.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/types.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/types_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/u256_test.go create mode 100644 cmd/generate-bindings/solana/anchor-go/generator/unmarshal.go create mode 100644 cmd/generate-bindings/solana/bindgen.go create mode 100644 cmd/generate-bindings/solana/bindings_test.go create mode 100644 cmd/generate-bindings/solana/gen.go create mode 100644 cmd/generate-bindings/solana/gen_test.go create mode 100644 cmd/generate-bindings/solana/solana.go create mode 100644 cmd/generate-bindings/solana/solana_test.go create mode 100644 cmd/generate-bindings/solana/testdata/contracts/idl/data_storage.json create mode 100644 cmd/generate-bindings/solana/testdata/contracts/source/data_storage.rs create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/accounts.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/constants.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/constructor.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/discriminators.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/errors.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/events.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/fetchers.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/instructions.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/program_id.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/tests_test.go create mode 100644 cmd/generate-bindings/solana/testdata/data_storage/types.go create mode 100644 cmd/generate-bindings/solana/testdata/gen/main.go create mode 100644 docs/cre_generate-bindings_evm.md create mode 100644 docs/cre_generate-bindings_solana.md diff --git a/cmd/generate-bindings/bindings/README.md b/cmd/generate-bindings/evm/README.md similarity index 99% rename from cmd/generate-bindings/bindings/README.md rename to cmd/generate-bindings/evm/README.md index 6513b27a..da346939 100644 --- a/cmd/generate-bindings/bindings/README.md +++ b/cmd/generate-bindings/evm/README.md @@ -32,7 +32,7 @@ that lets you generate Go bindings for your smart contracts using a custom templ ### Programmatic API ```go -import "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings" +import "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm" func main() { err := bindings.GenerateBindings( diff --git a/cmd/generate-bindings/bindings/abigen/FORK_METADATA.md b/cmd/generate-bindings/evm/abigen/FORK_METADATA.md similarity index 100% rename from cmd/generate-bindings/bindings/abigen/FORK_METADATA.md rename to cmd/generate-bindings/evm/abigen/FORK_METADATA.md diff --git a/cmd/generate-bindings/bindings/abigen/bind.go b/cmd/generate-bindings/evm/abigen/bind.go similarity index 100% rename from cmd/generate-bindings/bindings/abigen/bind.go rename to cmd/generate-bindings/evm/abigen/bind.go diff --git a/cmd/generate-bindings/bindings/abigen/bindv2.go b/cmd/generate-bindings/evm/abigen/bindv2.go similarity index 100% rename from cmd/generate-bindings/bindings/abigen/bindv2.go rename to cmd/generate-bindings/evm/abigen/bindv2.go diff --git a/cmd/generate-bindings/bindings/abigen/source.go.tpl b/cmd/generate-bindings/evm/abigen/source.go.tpl similarity index 100% rename from cmd/generate-bindings/bindings/abigen/source.go.tpl rename to cmd/generate-bindings/evm/abigen/source.go.tpl diff --git a/cmd/generate-bindings/bindings/abigen/source2.go.tpl b/cmd/generate-bindings/evm/abigen/source2.go.tpl similarity index 100% rename from cmd/generate-bindings/bindings/abigen/source2.go.tpl rename to cmd/generate-bindings/evm/abigen/source2.go.tpl diff --git a/cmd/generate-bindings/bindings/abigen/template.go b/cmd/generate-bindings/evm/abigen/template.go similarity index 100% rename from cmd/generate-bindings/bindings/abigen/template.go rename to cmd/generate-bindings/evm/abigen/template.go diff --git a/cmd/generate-bindings/bindings/bindgen.go b/cmd/generate-bindings/evm/bindgen.go similarity index 98% rename from cmd/generate-bindings/bindings/bindgen.go rename to cmd/generate-bindings/evm/bindgen.go index 7b7478b4..f79e3717 100644 --- a/cmd/generate-bindings/bindings/bindgen.go +++ b/cmd/generate-bindings/evm/bindgen.go @@ -1,4 +1,4 @@ -package bindings +package evm import ( _ "embed" @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/crypto" - "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings/abigen" + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm/abigen" ) //go:embed sourcecre.go.tpl diff --git a/cmd/generate-bindings/bindings/bindings_test.go b/cmd/generate-bindings/evm/bindings_test.go similarity index 99% rename from cmd/generate-bindings/bindings/bindings_test.go rename to cmd/generate-bindings/evm/bindings_test.go index de225b36..ab558459 100644 --- a/cmd/generate-bindings/bindings/bindings_test.go +++ b/cmd/generate-bindings/evm/bindings_test.go @@ -1,4 +1,4 @@ -package bindings_test +package evm_test import ( "context" @@ -20,7 +20,7 @@ import ( "github.com/smartcontractkit/cre-sdk-go/cre/testutils" consensusmock "github.com/smartcontractkit/cre-sdk-go/internal_testing/capabilities/consensus/mock" - datastorage "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings/testdata" + datastorage "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm/testdata" ) const anyChainSelector = uint64(1337) diff --git a/cmd/generate-bindings/evm/evm.go b/cmd/generate-bindings/evm/evm.go new file mode 100644 index 00000000..f0850b40 --- /dev/null +++ b/cmd/generate-bindings/evm/evm.go @@ -0,0 +1,469 @@ +package evm + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/smartcontractkit/cre-cli/internal/constants" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/ui" + "github.com/smartcontractkit/cre-cli/internal/validation" +) + +type Inputs struct { + ProjectRoot string `validate:"required,dir" cli:"--project-root"` + GoLang bool + TypeScript bool + AbiPath string `validate:"required,path_read" cli:"--abi"` + PkgName string `validate:"required" cli:"--pkg"` + GoOutPath string // contracts/evm/src/generated — set when GoLang is true + TSOutPath string // contracts/evm/ts/generated — set when TypeScript is true +} + +func New(runtimeContext *runtime.Context) *cobra.Command { + generateBindingsCmd := &cobra.Command{ + Use: "evm", + Short: "Generate bindings from contract ABI", + Long: `This command generates bindings from contract ABI files. +Supports EVM chain family with Go and TypeScript languages. +The target language is auto-detected from project files, or can be +specified explicitly with --language. +Each contract gets its own package subdirectory to avoid naming conflicts. +For example, IERC20.abi generates bindings in generated/ierc20/ package. + +Both raw ABI files (*.abi) and JSON artifact files (*.json) are supported. +For JSON files the ABI is read from the top-level "abi" field.`, + Example: " cre generate-bindings evm", + RunE: func(cmd *cobra.Command, args []string) error { + handler := newHandler(runtimeContext) + + inputs, err := handler.ResolveInputs(runtimeContext.Viper) + if err != nil { + return err + } + if err := handler.ValidateInputs(inputs); err != nil { + return err + } + return handler.Execute(inputs) + }, + } + + generateBindingsCmd.Flags().StringP("project-root", "p", "", "Path to project root directory (defaults to current directory)") + generateBindingsCmd.Flags().StringP("language", "l", "", "Target language: go, typescript (auto-detected from project files when omitted)") + generateBindingsCmd.Flags().StringP("abi", "a", "", "Path to ABI directory (defaults to contracts/evm/src/abi/). Supports *.abi and *.json files") + generateBindingsCmd.Flags().StringP("pkg", "k", "bindings", "Base package name (each contract gets its own subdirectory)") + + return generateBindingsCmd +} + +type handler struct { + log *zerolog.Logger + validated bool +} + +func newHandler(ctx *runtime.Context) *handler { + return &handler{ + log: ctx.Logger, + validated: false, + } +} + +func detectLanguages(projectRoot string) (goLang, typescript bool) { + _ = filepath.WalkDir(projectRoot, func(path string, d os.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + if d.Name() == "node_modules" || d.Name() == ".git" { + return filepath.SkipDir + } + return nil + } + base := filepath.Base(path) + if strings.HasSuffix(base, ".go") { + goLang = true + } + if strings.HasSuffix(base, ".ts") && !strings.HasSuffix(base, ".d.ts") { + typescript = true + } + return nil + }) + return goLang, typescript +} + +func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { + currentDir, err := os.Getwd() + if err != nil { + return Inputs{}, fmt.Errorf("failed to get current working directory: %w", err) + } + + projectRoot := v.GetString("project-root") + if projectRoot == "" { + projectRoot = currentDir + } + + contractsPath := filepath.Join(projectRoot, "contracts") + if _, err := os.Stat(contractsPath); err != nil { + return Inputs{}, fmt.Errorf("contracts folder not found in project root: %s", contractsPath) + } + + // Resolve languages: --language flag takes precedence, else auto-detect + var goLang, typescript bool + langFlag := strings.ToLower(strings.TrimSpace(v.GetString("language"))) + switch langFlag { + case "": + goLang, typescript = detectLanguages(projectRoot) + if !goLang && !typescript { + return Inputs{}, fmt.Errorf("no target language detected (use --language go or --language typescript, or ensure project contains .go or .ts files)") + } + case constants.WorkflowLanguageGolang: + goLang = true + case constants.WorkflowLanguageTypeScript: + typescript = true + default: + return Inputs{}, fmt.Errorf("unsupported language %q (supported: go, typescript)", langFlag) + } + + // Unified ABI path for both languages: contracts/evm/src/abi + abiPath := v.GetString("abi") + if abiPath == "" { + abiPath = filepath.Join(projectRoot, "contracts", "evm", "src", "abi") + } + + pkgName := v.GetString("pkg") + + // Separate output paths: Go uses src/, TS uses ts/ (typescript convention) + var goOutPath, tsOutPath string + if goLang { + goOutPath = filepath.Join(projectRoot, "contracts", "evm", "src", "generated") + } + if typescript { + tsOutPath = filepath.Join(projectRoot, "contracts", "evm", "ts", "generated") + } + + return Inputs{ + ProjectRoot: projectRoot, + GoLang: goLang, + TypeScript: typescript, + AbiPath: abiPath, + PkgName: pkgName, + GoOutPath: goOutPath, + TSOutPath: tsOutPath, + }, nil +} + +// findAbiFiles returns all supported ABI files (*.abi and *.json) found in dir. +func findAbiFiles(dir string) ([]string, error) { + abiFiles, err := filepath.Glob(filepath.Join(dir, "*.abi")) + if err != nil { + return nil, err + } + jsonFiles, err := filepath.Glob(filepath.Join(dir, "*.json")) + if err != nil { + return nil, err + } + all := append(abiFiles, jsonFiles...) + sort.Strings(all) + return all, nil +} + +// contractNameFromFile returns the contract name by stripping the .abi or .json +// extension from the base filename. +func contractNameFromFile(path string) string { + name := filepath.Base(path) + ext := filepath.Ext(name) + if ext != "" { + name = name[:len(name)-len(ext)] + } + return name +} + +func (h *handler) ValidateInputs(inputs Inputs) error { + validate, err := validation.NewValidator() + if err != nil { + return fmt.Errorf("failed to initialize validator: %w", err) + } + + if err = validate.Struct(inputs); err != nil { + return validate.ParseValidationErrors(err) + } + + if _, err := os.Stat(inputs.AbiPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("ABI path does not exist: %s", inputs.AbiPath) + } + return fmt.Errorf("failed to access ABI path: %w", err) + } + + if info, err := os.Stat(inputs.AbiPath); err == nil && info.IsDir() { + files, err := findAbiFiles(inputs.AbiPath) + if err != nil { + return fmt.Errorf("failed to check for ABI files in directory: %w", err) + } + if len(files) == 0 { + return fmt.Errorf("no *.abi or *.json files found in directory: %s", inputs.AbiPath) + } + } + + if inputs.GoLang && inputs.GoOutPath == "" { + return fmt.Errorf("go output path is required when language is go") + } + if inputs.TypeScript && inputs.TSOutPath == "" { + return fmt.Errorf("typescript output path is required when language is typescript") + } + + h.validated = true + return nil +} + +// contractNameToPackage converts contract names to valid Go package names +// Examples: IERC20 -> ierc20, ReserveManager -> reserve_manager, IReserveManager -> ireserve_manager +func contractNameToPackage(contractName string) string { + if contractName == "" { + return "" + } + + var result []rune + runes := []rune(contractName) + + for i, r := range runes { + if r >= 'A' && r <= 'Z' { + lower := r - 'A' + 'a' + + // Add underscore before uppercase letters, but not: + // - At the beginning (i == 0) + // - If the previous character was also uppercase and this is followed by lowercase (e.g., "ERC" in "ERC20") + // - If this is part of a sequence of uppercase letters at the beginning (e.g., "IERC20" -> "ierc20") + if i > 0 { + prevIsUpper := runes[i-1] >= 'A' && runes[i-1] <= 'Z' + nextIsLower := i+1 < len(runes) && runes[i+1] >= 'a' && runes[i+1] <= 'z' + + if !prevIsUpper || (prevIsUpper && nextIsLower && i > 1) { + result = append(result, '_') + } + } + + result = append(result, lower) + } else { + result = append(result, r) + } + } + + return string(result) +} + +func (h *handler) processAbiDirectory(inputs Inputs) error { + files, err := findAbiFiles(inputs.AbiPath) + if err != nil { + return fmt.Errorf("failed to find ABI files: %w", err) + } + + if len(files) == 0 { + return fmt.Errorf("no *.abi or *.json files found in directory: %s", inputs.AbiPath) + } + + // Detect duplicate contract names across extensions (e.g. Foo.abi and Foo.json) + contractNames := make(map[string]string) // contract name -> originating file + for _, f := range files { + name := contractNameFromFile(f) + if prev, exists := contractNames[name]; exists { + return fmt.Errorf("duplicate contract name %q: found in both %s and %s", name, filepath.Base(prev), filepath.Base(f)) + } + contractNames[name] = f + } + + if inputs.GoLang { + packageNames := make(map[string]bool) + for _, abiFile := range files { + contractName := contractNameFromFile(abiFile) + packageName := contractNameToPackage(contractName) + if _, exists := packageNames[packageName]; exists { + return fmt.Errorf("package name collision: multiple contracts would generate the same package name '%s' (contracts are converted to snake_case for package names). Please rename one of your contract files to avoid this conflict", packageName) + } + packageNames[packageName] = true + } + } + + var generatedContracts []string + + for _, abiFile := range files { + contractName := contractNameFromFile(abiFile) + + if inputs.TypeScript { + outputFile := filepath.Join(inputs.TSOutPath, contractName+".ts") + ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) + + err = GenerateBindingsTS( + abiFile, + contractName, + outputFile, + ) + if err != nil { + return fmt.Errorf("failed to generate TypeScript bindings for %s: %w", contractName, err) + } + generatedContracts = append(generatedContracts, contractName) + } + + if inputs.GoLang { + packageName := contractNameToPackage(contractName) + + contractOutDir := filepath.Join(inputs.GoOutPath, packageName) + if err := os.MkdirAll(contractOutDir, 0o755); err != nil { + return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err) + } + + outputFile := filepath.Join(contractOutDir, contractName+".go") + ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) + + err = GenerateBindings( + "", + abiFile, + packageName, + contractName, + outputFile, + ) + if err != nil { + return fmt.Errorf("failed to generate bindings for %s: %w", contractName, err) + } + } + } + + // Generate barrel index.ts for TypeScript + if inputs.TypeScript && len(generatedContracts) > 0 { + indexPath := filepath.Join(inputs.TSOutPath, "index.ts") + var indexContent string + indexContent += "// Code generated — DO NOT EDIT.\n" + for _, name := range generatedContracts { + indexContent += fmt.Sprintf("export * from './%s'\n", name) + indexContent += fmt.Sprintf("export * from './%s_mock'\n", name) + } + if err := os.WriteFile(indexPath, []byte(indexContent), 0o600); err != nil { + return fmt.Errorf("failed to write index.ts: %w", err) + } + } + + return nil +} + +func (h *handler) processSingleAbi(inputs Inputs) error { + contractName := contractNameFromFile(inputs.AbiPath) + + if inputs.TypeScript { + outputFile := filepath.Join(inputs.TSOutPath, contractName+".ts") + ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) + + if err := GenerateBindingsTS( + inputs.AbiPath, + contractName, + outputFile, + ); err != nil { + return err + } + } + + if inputs.GoLang { + packageName := contractNameToPackage(contractName) + + contractOutDir := filepath.Join(inputs.GoOutPath, packageName) + if err := os.MkdirAll(contractOutDir, 0o755); err != nil { + return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err) + } + + outputFile := filepath.Join(contractOutDir, contractName+".go") + ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) + + if err := GenerateBindings( + "", + inputs.AbiPath, + packageName, + contractName, + outputFile, + ); err != nil { + return err + } + } + + return nil +} + +func (h *handler) Execute(inputs Inputs) error { + langs := []string{} + if inputs.GoLang { + langs = append(langs, "go") + } + if inputs.TypeScript { + langs = append(langs, "typescript") + } + ui.Dim(fmt.Sprintf("Project: %s, Chain: evm, Languages: %v", inputs.ProjectRoot, langs)) + + if inputs.GoLang { + if err := os.MkdirAll(inputs.GoOutPath, 0o755); err != nil { + return fmt.Errorf("failed to create Go output directory: %w", err) + } + } + if inputs.TypeScript { + if err := os.MkdirAll(inputs.TSOutPath, 0o755); err != nil { + return fmt.Errorf("failed to create TypeScript output directory: %w", err) + } + } + + info, err := os.Stat(inputs.AbiPath) + if err != nil { + return fmt.Errorf("failed to access ABI path: %w", err) + } + + if info.IsDir() { + if err := h.processAbiDirectory(inputs); err != nil { + return err + } + } else { + if err := h.processSingleAbi(inputs); err != nil { + return err + } + } + + if inputs.GoLang { + spinner := ui.NewSpinner() + spinner.Start("Installing dependencies...") + + err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go@"+constants.SdkVersion) + if err != nil { + spinner.Stop() + return err + } + err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm@"+constants.EVMCapabilitiesVersion) + if err != nil { + spinner.Stop() + return err + } + if err = runCommand(inputs.ProjectRoot, "go", "mod", "tidy"); err != nil { + spinner.Stop() + return err + } + + spinner.Stop() + } + + ui.Success("Bindings generated successfully") + return nil +} + +func runCommand(dir string, command string, args ...string) error { + cmd := exec.Command(command, args...) + cmd.Dir = dir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run %s: %w", command, err) + } + + return nil +} diff --git a/cmd/generate-bindings/generate-bindings_test.go b/cmd/generate-bindings/evm/evm_test.go similarity index 85% rename from cmd/generate-bindings/generate-bindings_test.go rename to cmd/generate-bindings/evm/evm_test.go index e7411fb4..559b1c49 100644 --- a/cmd/generate-bindings/generate-bindings_test.go +++ b/cmd/generate-bindings/evm/evm_test.go @@ -1,4 +1,4 @@ -package generatebindings +package evm import ( "fmt" @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings" "github.com/smartcontractkit/cre-cli/internal/runtime" ) @@ -43,12 +42,10 @@ func TestContractNameToPackage(t *testing.T) { } func TestResolveInputs_DefaultFallbacks(t *testing.T) { - // Create a temporary directory for testing tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) - // Create required contracts directory and go.mod contractsDir := filepath.Join(tempDir, "contracts") err = os.MkdirAll(contractsDir, 0755) require.NoError(t, err) @@ -57,7 +54,6 @@ func TestResolveInputs_DefaultFallbacks(t *testing.T) { err = os.WriteFile(goModPath, []byte("module test/contracts\n\ngo 1.20\n"), 0600) require.NoError(t, err) - // Change to temp directory originalDir, err := os.Getwd() require.NoError(t, err) defer func() { @@ -76,14 +72,12 @@ func TestResolveInputs_DefaultFallbacks(t *testing.T) { v.Set("language", "go") v.Set("pkg", "bindings") - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) - // Use filepath.EvalSymlinks to handle macOS /var vs /private/var symlink issues expectedRoot, _ := filepath.EvalSymlinks(tempDir) actualRoot, _ := filepath.EvalSymlinks(inputs.ProjectRoot) assert.Equal(t, expectedRoot, actualRoot) - assert.Equal(t, "evm", inputs.ChainFamily) assert.True(t, inputs.GoLang) expectedAbi, _ := filepath.EvalSymlinks(filepath.Join(tempDir, "contracts", "evm", "src", "abi")) actualAbi, _ := filepath.EvalSymlinks(inputs.AbiPath) @@ -117,7 +111,7 @@ func TestResolveInputs_TypeScriptDefaults(t *testing.T) { v.Set("language", "typescript") v.Set("pkg", "bindings") - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) expectedRoot, _ := filepath.EvalSymlinks(tempDir) @@ -125,12 +119,10 @@ func TestResolveInputs_TypeScriptDefaults(t *testing.T) { assert.Equal(t, expectedRoot, actualRoot) assert.True(t, inputs.TypeScript) - // ABI path: contracts/evm/src/abi expectedAbi, _ := filepath.EvalSymlinks(filepath.Join(tempDir, "contracts", "evm", "src", "abi")) actualAbi, _ := filepath.EvalSymlinks(inputs.AbiPath) assert.Equal(t, expectedAbi, actualAbi) - // TS output path: contracts/evm/ts/generated expectedTSOut, _ := filepath.EvalSymlinks(filepath.Join(tempDir, "contracts", "evm", "ts", "generated")) actualTSOut, _ := filepath.EvalSymlinks(inputs.TSOutPath) assert.Equal(t, expectedTSOut, actualTSOut) @@ -157,7 +149,7 @@ func TestAutoDetect_GoOnly(t *testing.T) { handler := newHandler(runtimeCtx) v := viper.New() - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) assert.True(t, inputs.GoLang, "Go should be auto-detected") @@ -186,7 +178,7 @@ func TestAutoDetect_TypeScriptOnly(t *testing.T) { handler := newHandler(runtimeCtx) v := viper.New() - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) assert.False(t, inputs.GoLang, "Go should not be detected") @@ -220,7 +212,7 @@ func TestAutoDetect_Both(t *testing.T) { handler := newHandler(runtimeCtx) v := viper.New() - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) assert.True(t, inputs.GoLang, "Go should be auto-detected") @@ -247,7 +239,7 @@ func TestExplicitGoFlag(t *testing.T) { v := viper.New() v.Set("language", "go") - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) assert.True(t, inputs.GoLang) @@ -277,7 +269,7 @@ func TestExplicitTypeScriptFlag(t *testing.T) { v := viper.New() v.Set("language", "typescript") - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) assert.False(t, inputs.GoLang) @@ -310,7 +302,7 @@ func TestAutoDetectBothLanguages(t *testing.T) { handler := newHandler(runtimeCtx) v := viper.New() - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) assert.True(t, inputs.GoLang) @@ -340,18 +332,15 @@ func TestOutputPathsSeparation(t *testing.T) { handler := newHandler(runtimeCtx) v := viper.New() - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) - // Go path must contain src/generated assert.Contains(t, inputs.GoOutPath, "src", "Go output path should contain src") assert.Contains(t, inputs.GoOutPath, "generated", "Go output path should contain generated") - // TS path must contain ts/generated assert.Contains(t, inputs.TSOutPath, "ts", "TS output path should contain ts") assert.Contains(t, inputs.TSOutPath, "generated", "TS output path should contain generated") - // Paths must be different assert.NotEqual(t, inputs.GoOutPath, inputs.TSOutPath, "Go and TS output paths must be different") } @@ -384,7 +373,7 @@ func TestEndToEnd_TypeScriptGeneration(t *testing.T) { v := viper.New() v.Set("language", "typescript") v.Set("pkg", "bindings") - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) require.NoError(t, handler.ValidateInputs(inputs)) require.NoError(t, handler.Execute(inputs)) @@ -397,9 +386,7 @@ func TestEndToEnd_TypeScriptGeneration(t *testing.T) { require.FileExists(t, filepath.Join(tsOutDir, "index.ts")) } -// command should run in projectRoot which contains contracts directory -func TestResolveInputs_CustomProjectRoot(t *testing.T) { - // Create a temporary directory for testing +func TestResolveEvmInputs_CustomProjectRoot(t *testing.T) { tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) @@ -407,27 +394,23 @@ func TestResolveInputs_CustomProjectRoot(t *testing.T) { runtimeCtx := &runtime.Context{} handler := newHandler(runtimeCtx) - // Test with custom project root v := viper.New() v.Set("project-root", tempDir) v.Set("language", "go") v.Set("pkg", "bindings") - _, err = handler.ResolveInputs([]string{"evm"}, v) + _, err = handler.ResolveInputs(v) require.Error(t, err) expectedErrMsg := fmt.Sprintf("contracts folder not found in project root: %s", tempDir) require.Contains(t, err.Error(), expectedErrMsg) } -// Empty project root should default to current directory, and this should contain contracts and go.mod -func TestResolveInputs_EmptyProjectRoot(t *testing.T) { - // Create a temporary directory for testing +func TestResolveEvmInputs_EmptyProjectRoot(t *testing.T) { tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) - // Create required contracts directory and go.mod contractsDir := filepath.Join(tempDir, "contracts") err = os.MkdirAll(contractsDir, 0755) require.NoError(t, err) @@ -436,7 +419,6 @@ func TestResolveInputs_EmptyProjectRoot(t *testing.T) { err = os.WriteFile(goModPath, []byte("module test/contracts\n\ngo 1.20\n"), 0600) require.NoError(t, err) - // Change to temp directory originalDir, err := os.Getwd() require.NoError(t, err) defer func() { @@ -451,20 +433,17 @@ func TestResolveInputs_EmptyProjectRoot(t *testing.T) { runtimeCtx := &runtime.Context{} handler := newHandler(runtimeCtx) - // Test with empty project root (should use current directory) v := viper.New() v.Set("project-root", "") v.Set("language", "go") v.Set("pkg", "bindings") - inputs, err := handler.ResolveInputs([]string{"evm"}, v) + inputs, err := handler.ResolveInputs(v) require.NoError(t, err) - // Use filepath.EvalSymlinks to handle macOS /var vs /private/var symlink issues expectedRoot, _ := filepath.EvalSymlinks(tempDir) actualRoot, _ := filepath.EvalSymlinks(inputs.ProjectRoot) assert.Equal(t, expectedRoot, actualRoot) - assert.Equal(t, "evm", inputs.ChainFamily) assert.True(t, inputs.GoLang) expectedAbi, _ := filepath.EvalSymlinks(filepath.Join(tempDir, "contracts", "evm", "src", "abi")) actualAbi, _ := filepath.EvalSymlinks(inputs.AbiPath) @@ -475,32 +454,11 @@ func TestResolveInputs_EmptyProjectRoot(t *testing.T) { assert.Equal(t, expectedGoOut, actualGoOut) } -func TestValidateInputs_RequiredChainFamily(t *testing.T) { - runtimeCtx := &runtime.Context{} - handler := newHandler(runtimeCtx) - - // Test validation with missing chain family - inputs := Inputs{ - ProjectRoot: "/tmp", - ChainFamily: "", // Missing required field - GoLang: true, - AbiPath: "/tmp/abi", - PkgName: "bindings", - GoOutPath: "/tmp/out", - } - - err := handler.ValidateInputs(inputs) - require.Error(t, err) - assert.Contains(t, err.Error(), "chain-family") -} - func TestValidateInputs_ValidInputs(t *testing.T) { - // Create a temporary directory for testing tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) - // Create a valid ABI file abiContent := `[{"type":"function","name":"test","inputs":[],"outputs":[]}]` abiFile := filepath.Join(tempDir, "test.abi") err = os.WriteFile(abiFile, []byte(abiContent), 0600) @@ -509,10 +467,8 @@ func TestValidateInputs_ValidInputs(t *testing.T) { runtimeCtx := &runtime.Context{} handler := newHandler(runtimeCtx) - // Test validation with valid inputs (using single file) inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiFile, PkgName: "bindings", @@ -523,7 +479,6 @@ func TestValidateInputs_ValidInputs(t *testing.T) { require.NoError(t, err) assert.True(t, handler.validated) - // Test validation with directory containing .abi files abiDir := filepath.Join(tempDir, "abi") err = os.MkdirAll(abiDir, 0755) require.NoError(t, err) @@ -535,7 +490,6 @@ func TestValidateInputs_ValidInputs(t *testing.T) { require.NoError(t, err) assert.True(t, handler.validated) - // Test validation with directory containing .abi files for TypeScript (unified extension) abiDir2 := filepath.Join(tempDir, "abi_ts") err = os.MkdirAll(abiDir2, 0755) require.NoError(t, err) @@ -544,7 +498,6 @@ func TestValidateInputs_ValidInputs(t *testing.T) { tsInputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", TypeScript: true, AbiPath: abiDir2, PkgName: "bindings", @@ -555,7 +508,6 @@ func TestValidateInputs_ValidInputs(t *testing.T) { require.NoError(t, err) assert.True(t, handler2.validated) - // Test validation with directory containing only .json files abiDir3 := filepath.Join(tempDir, "abi_json") err = os.MkdirAll(abiDir3, 0755) require.NoError(t, err) @@ -565,7 +517,6 @@ func TestValidateInputs_ValidInputs(t *testing.T) { jsonInputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiDir3, PkgName: "bindings", @@ -577,36 +528,11 @@ func TestValidateInputs_ValidInputs(t *testing.T) { assert.True(t, handler3.validated) } -func TestValidateInputs_InvalidChainFamily(t *testing.T) { - // Create a temporary directory for testing - tempDir, err := os.MkdirTemp("", "generate-bindings-test") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - runtimeCtx := &runtime.Context{} - handler := newHandler(runtimeCtx) - - // Test validation with invalid chain family - inputs := Inputs{ - ProjectRoot: tempDir, - ChainFamily: "solana", // No longer supported - GoLang: true, - AbiPath: tempDir, - PkgName: "bindings", - GoOutPath: tempDir, - } - - err = handler.ValidateInputs(inputs) - require.Error(t, err) - assert.Contains(t, err.Error(), "chain-family") -} - func TestValidateInputs_NoLanguageSpecified(t *testing.T) { tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) - // Create contracts dir but no .go or .ts files for auto-detect contractsDir := filepath.Join(tempDir, "contracts") err = os.MkdirAll(contractsDir, 0755) require.NoError(t, err) @@ -619,21 +545,18 @@ func TestValidateInputs_NoLanguageSpecified(t *testing.T) { runtimeCtx := &runtime.Context{} handler := newHandler(runtimeCtx) - // ResolveInputs should error when no --language and nothing detected v := viper.New() - _, err = handler.ResolveInputs([]string{"evm"}, v) + _, err = handler.ResolveInputs(v) require.Error(t, err) assert.Contains(t, err.Error(), "no target language") } -func TestValidateInputs_NonExistentDirectory(t *testing.T) { +func TestValidateEvmInputs_NonExistentDirectory(t *testing.T) { runtimeCtx := &runtime.Context{} handler := newHandler(runtimeCtx) - // Test validation with non-existent directory inputs := Inputs{ ProjectRoot: "/non/existent/path", - ChainFamily: "evm", GoLang: true, AbiPath: "/non/existent/abi", PkgName: "bindings", @@ -646,7 +569,6 @@ func TestValidateInputs_NonExistentDirectory(t *testing.T) { } func TestProcessAbiDirectory_MultipleFiles(t *testing.T) { - // Create a temporary directory structure tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) @@ -657,7 +579,6 @@ func TestProcessAbiDirectory_MultipleFiles(t *testing.T) { err = os.MkdirAll(abiDir, 0755) require.NoError(t, err) - // Create mock ABI files (both .abi and .json formats) abiContent := `[{"type":"function","name":"test","inputs":[],"outputs":[]}]` jsonContent := `{"abi":[{"type":"function","name":"test","inputs":[],"outputs":[]}]}` err = os.WriteFile(filepath.Join(abiDir, "Contract1.abi"), []byte(abiContent), 0600) @@ -667,7 +588,6 @@ func TestProcessAbiDirectory_MultipleFiles(t *testing.T) { err = os.WriteFile(filepath.Join(abiDir, "Contract3.json"), []byte(jsonContent), 0600) require.NoError(t, err) - // Create a mock logger to prevent nil pointer dereference logger := zerolog.New(os.Stderr).With().Timestamp().Logger() runtimeCtx := &runtime.Context{ Logger: &logger, @@ -676,25 +596,19 @@ func TestProcessAbiDirectory_MultipleFiles(t *testing.T) { inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiDir, PkgName: "bindings", GoOutPath: outDir, } - // This test will fail because it tries to call the actual bindings.GenerateBindings - // but it tests the directory processing logic err = handler.processAbiDirectory(inputs) - // We expect an error because the bindings package requires actual ABI format - // but we can check that it created the expected directory structure if err == nil { t.Log("Unexpectedly succeeded - bindings generation worked with mock ABI") } else { assert.Contains(t, err.Error(), "Contract1") } - // Verify that per-contract directories were created contract1Dir := filepath.Join(outDir, "contract1") contract2Dir := filepath.Join(outDir, "contract2") contract3Dir := filepath.Join(outDir, "contract3") @@ -704,7 +618,6 @@ func TestProcessAbiDirectory_MultipleFiles(t *testing.T) { } func TestProcessAbiDirectory_CreatesPerContractDirectories(t *testing.T) { - // Create a temporary directory structure tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) @@ -715,7 +628,6 @@ func TestProcessAbiDirectory_CreatesPerContractDirectories(t *testing.T) { err = os.MkdirAll(abiDir, 0755) require.NoError(t, err) - // Create mock ABI files with different naming patterns (both .abi and .json) abiContent := `[{"type":"function","name":"test","inputs":[],"outputs":[]}]` jsonContent := `{"abi":[{"type":"function","name":"test","inputs":[],"outputs":[]}]}` testCases := []struct { @@ -737,7 +649,6 @@ func TestProcessAbiDirectory_CreatesPerContractDirectories(t *testing.T) { require.NoError(t, err) } - // Create a mock logger logger := zerolog.New(os.Stderr).With().Timestamp().Logger() runtimeCtx := &runtime.Context{ Logger: &logger, @@ -746,20 +657,17 @@ func TestProcessAbiDirectory_CreatesPerContractDirectories(t *testing.T) { inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiDir, PkgName: "bindings", GoOutPath: outDir, } - // Try to process - the mock ABI content might actually work err = handler.processAbiDirectory(inputs) if err != nil { t.Logf("Expected error occurred: %v", err) } - // Verify that per-contract directories were created with correct names for _, tc := range testCases { contractDir := filepath.Join(outDir, tc.expectedPackage) assert.DirExists(t, contractDir, "Expected directory %s to be created", contractDir) @@ -767,7 +675,6 @@ func TestProcessAbiDirectory_CreatesPerContractDirectories(t *testing.T) { } func TestProcessAbiDirectory_NoAbiFiles(t *testing.T) { - // Create a temporary directory structure tempDir, err := os.MkdirTemp("", "generate-bindings-test") require.NoError(t, err) defer os.RemoveAll(tempDir) @@ -786,7 +693,6 @@ func TestProcessAbiDirectory_NoAbiFiles(t *testing.T) { inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiDir, PkgName: "bindings", @@ -814,7 +720,6 @@ func TestProcessAbiDirectory_NoAbiFiles_TypeScript(t *testing.T) { inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", TypeScript: true, AbiPath: abiDir, PkgName: "bindings", @@ -839,8 +744,6 @@ func TestProcessAbiDirectory_PackageNameCollision(t *testing.T) { abiContent := `[{"type":"function","name":"test","inputs":[],"outputs":[]}]` - // "TestContract" -> "test_contract" - // "test_contract" -> "test_contract" err = os.WriteFile(filepath.Join(abiDir, "TestContract.abi"), []byte(abiContent), 0600) require.NoError(t, err) err = os.WriteFile(filepath.Join(abiDir, "test_contract.abi"), []byte(abiContent), 0600) @@ -854,7 +757,6 @@ func TestProcessAbiDirectory_PackageNameCollision(t *testing.T) { inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiDir, PkgName: "bindings", @@ -862,7 +764,6 @@ func TestProcessAbiDirectory_PackageNameCollision(t *testing.T) { } err = handler.processAbiDirectory(inputs) - fmt.Println(err.Error()) require.Error(t, err) require.Equal(t, err.Error(), "package name collision: multiple contracts would generate the same package name 'test_contract' (contracts are converted to snake_case for package names). Please rename one of your contract files to avoid this conflict") } @@ -890,7 +791,6 @@ func TestProcessAbiDirectory_DuplicateContractNameAcrossExtensions(t *testing.T) inputs := Inputs{ ProjectRoot: tempDir, - ChainFamily: "evm", GoLang: true, AbiPath: abiDir, PkgName: "bindings", @@ -912,7 +812,6 @@ func TestProcessAbiDirectory_NonExistentDirectory(t *testing.T) { inputs := Inputs{ ProjectRoot: "/tmp", - ChainFamily: "evm", GoLang: true, AbiPath: "/non/existent/abi", PkgName: "bindings", @@ -930,7 +829,7 @@ func TestProcessAbiDirectory_NonExistentDirectory(t *testing.T) { func TestGenerateBindings_UnconventionalNaming(t *testing.T) { tests := []struct { name string - contractABI string // raw ABI JSON array + contractABI string pkgName string typeName string shouldFail bool @@ -1044,7 +943,7 @@ func TestGenerateBindings_UnconventionalNaming(t *testing.T) { require.NoError(t, err) outFile := filepath.Join(tempDir, "bindings.go") - err = bindings.GenerateBindings("", abiFile, tc.pkgName, tc.typeName, outFile) + err = GenerateBindings("", abiFile, tc.pkgName, tc.typeName, outFile) if tc.shouldFail { require.Error(t, err, "Expected binding generation to fail for %s", tc.name) @@ -1106,20 +1005,18 @@ func TestGenerateBindings_StructNamePrefixStripping(t *testing.T) { require.NoError(t, err) outFile := filepath.Join(tempDir, "bindings.go") - err = bindings.GenerateBindings("", abiFile, "mycontract", "MyContract", outFile) + err = GenerateBindings("", abiFile, "mycontract", "MyContract", outFile) require.NoError(t, err) content, err := os.ReadFile(outFile) require.NoError(t, err) src := string(content) - // Struct declarations should have the prefix stripped. assert.Contains(t, src, "type DONInfo struct") assert.Contains(t, src, "type CapabilityConfiguration struct") assert.NotContains(t, src, "type MyContractDONInfo struct") assert.NotContains(t, src, "type MyContractCapabilityConfiguration struct") - // Field type references inside structs should also be stripped. assert.Contains(t, src, "[]CapabilityConfiguration") assert.NotContains(t, src, "[]MyContractCapabilityConfiguration") } diff --git a/cmd/generate-bindings/bindings/gen.go b/cmd/generate-bindings/evm/gen.go similarity index 67% rename from cmd/generate-bindings/bindings/gen.go rename to cmd/generate-bindings/evm/gen.go index febfa9dc..34410db7 100644 --- a/cmd/generate-bindings/bindings/gen.go +++ b/cmd/generate-bindings/evm/gen.go @@ -1,2 +1,2 @@ //go:generate go run ./testdata/gen -package bindings +package evm diff --git a/cmd/generate-bindings/evm/gen_test.go b/cmd/generate-bindings/evm/gen_test.go new file mode 100644 index 00000000..a2fa4688 --- /dev/null +++ b/cmd/generate-bindings/evm/gen_test.go @@ -0,0 +1,19 @@ +package evm_test + +import ( + "testing" + + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm" +) + +func TestGenerateBindings(t *testing.T) { + if err := evm.GenerateBindings( + "./testdata/DataStorage_combined.json", + "", + "bindings", + "", + "./testdata/bindings.go", + ); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/generate-bindings/bindings/mockcontract.go.tpl b/cmd/generate-bindings/evm/mockcontract.go.tpl similarity index 100% rename from cmd/generate-bindings/bindings/mockcontract.go.tpl rename to cmd/generate-bindings/evm/mockcontract.go.tpl diff --git a/cmd/generate-bindings/bindings/mockcontract.ts.tpl b/cmd/generate-bindings/evm/mockcontract.ts.tpl similarity index 100% rename from cmd/generate-bindings/bindings/mockcontract.ts.tpl rename to cmd/generate-bindings/evm/mockcontract.ts.tpl diff --git a/cmd/generate-bindings/bindings/sourcecre.go.tpl b/cmd/generate-bindings/evm/sourcecre.go.tpl similarity index 100% rename from cmd/generate-bindings/bindings/sourcecre.go.tpl rename to cmd/generate-bindings/evm/sourcecre.go.tpl diff --git a/cmd/generate-bindings/bindings/sourcecre.ts.tpl b/cmd/generate-bindings/evm/sourcecre.ts.tpl similarity index 100% rename from cmd/generate-bindings/bindings/sourcecre.ts.tpl rename to cmd/generate-bindings/evm/sourcecre.ts.tpl diff --git a/cmd/generate-bindings/bindings/testdata/DataStorage.sol b/cmd/generate-bindings/evm/testdata/DataStorage.sol similarity index 100% rename from cmd/generate-bindings/bindings/testdata/DataStorage.sol rename to cmd/generate-bindings/evm/testdata/DataStorage.sol diff --git a/cmd/generate-bindings/bindings/testdata/DataStorage_combined.json b/cmd/generate-bindings/evm/testdata/DataStorage_combined.json similarity index 100% rename from cmd/generate-bindings/bindings/testdata/DataStorage_combined.json rename to cmd/generate-bindings/evm/testdata/DataStorage_combined.json diff --git a/cmd/generate-bindings/bindings/testdata/bindings.go b/cmd/generate-bindings/evm/testdata/bindings.go similarity index 100% rename from cmd/generate-bindings/bindings/testdata/bindings.go rename to cmd/generate-bindings/evm/testdata/bindings.go diff --git a/cmd/generate-bindings/bindings/testdata/bindings_mock.go b/cmd/generate-bindings/evm/testdata/bindings_mock.go similarity index 100% rename from cmd/generate-bindings/bindings/testdata/bindings_mock.go rename to cmd/generate-bindings/evm/testdata/bindings_mock.go diff --git a/cmd/generate-bindings/bindings/testdata/emptybindings/EmptyContract.sol b/cmd/generate-bindings/evm/testdata/emptybindings/EmptyContract.sol similarity index 100% rename from cmd/generate-bindings/bindings/testdata/emptybindings/EmptyContract.sol rename to cmd/generate-bindings/evm/testdata/emptybindings/EmptyContract.sol diff --git a/cmd/generate-bindings/bindings/testdata/emptybindings/EmptyContract_combined.json b/cmd/generate-bindings/evm/testdata/emptybindings/EmptyContract_combined.json similarity index 100% rename from cmd/generate-bindings/bindings/testdata/emptybindings/EmptyContract_combined.json rename to cmd/generate-bindings/evm/testdata/emptybindings/EmptyContract_combined.json diff --git a/cmd/generate-bindings/bindings/testdata/emptybindings/emptybindings.go b/cmd/generate-bindings/evm/testdata/emptybindings/emptybindings.go similarity index 100% rename from cmd/generate-bindings/bindings/testdata/emptybindings/emptybindings.go rename to cmd/generate-bindings/evm/testdata/emptybindings/emptybindings.go diff --git a/cmd/generate-bindings/bindings/testdata/emptybindings/emptybindings_mock.go b/cmd/generate-bindings/evm/testdata/emptybindings/emptybindings_mock.go similarity index 100% rename from cmd/generate-bindings/bindings/testdata/emptybindings/emptybindings_mock.go rename to cmd/generate-bindings/evm/testdata/emptybindings/emptybindings_mock.go diff --git a/cmd/generate-bindings/bindings/testdata/gen/main.go b/cmd/generate-bindings/evm/testdata/gen/main.go similarity index 70% rename from cmd/generate-bindings/bindings/testdata/gen/main.go rename to cmd/generate-bindings/evm/testdata/gen/main.go index 2eda5a71..44836dd4 100644 --- a/cmd/generate-bindings/bindings/testdata/gen/main.go +++ b/cmd/generate-bindings/evm/testdata/gen/main.go @@ -1,11 +1,11 @@ package main import ( - "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings" + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm" ) func main() { - if err := bindings.GenerateBindings( + if err := evm.GenerateBindings( "./testdata/DataStorage_combined.json", "", "bindings", @@ -15,7 +15,7 @@ func main() { panic(err) } - if err := bindings.GenerateBindings( + if err := evm.GenerateBindings( "./testdata/emptybindings/EmptyContract_combined.json", "", "emptybindings", diff --git a/cmd/generate-bindings/generate-bindings.go b/cmd/generate-bindings/generate-bindings.go index 63691e80..0411122f 100644 --- a/cmd/generate-bindings/generate-bindings.go +++ b/cmd/generate-bindings/generate-bindings.go @@ -1,498 +1,22 @@ package generatebindings import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "sort" - "strings" - - "github.com/rs/zerolog" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings" - "github.com/smartcontractkit/cre-cli/internal/constants" + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm" + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/solana" "github.com/smartcontractkit/cre-cli/internal/runtime" - "github.com/smartcontractkit/cre-cli/internal/ui" - "github.com/smartcontractkit/cre-cli/internal/validation" ) -type Inputs struct { - ProjectRoot string `validate:"required,dir" cli:"--project-root"` - ChainFamily string `validate:"required,oneof=evm" cli:"--chain-family"` - GoLang bool - TypeScript bool - AbiPath string `validate:"required,path_read" cli:"--abi"` - PkgName string `validate:"required" cli:"--pkg"` - GoOutPath string // contracts/{chain}/src/generated — set when GoLang is true - TSOutPath string // contracts/{chain}/ts/generated — set when TypeScript is true -} - func New(runtimeContext *runtime.Context) *cobra.Command { generateBindingsCmd := &cobra.Command{ - Use: "generate-bindings ", - Short: "Generate bindings from contract ABI", - Long: `This command generates bindings from contract ABI files. -Supports EVM chain family with Go and TypeScript languages. -The target language is auto-detected from project files, or can be -specified explicitly with --language. -Each contract gets its own package subdirectory to avoid naming conflicts. -For example, IERC20.abi generates bindings in generated/ierc20/ package. - -Both raw ABI files (*.abi) and JSON artifact files (*.json) are supported. -For JSON files the ABI is read from the top-level "abi" field.`, - Example: " cre generate-bindings evm", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - handler := newHandler(runtimeContext) - - inputs, err := handler.ResolveInputs(args, runtimeContext.Viper) - if err != nil { - return err - } - err = handler.ValidateInputs(inputs) - if err != nil { - return err - } - return handler.Execute(inputs) - }, + Use: "generate-bindings", + Short: "Generate bindings for contracts", + Long: `The generate-bindings command allows you to generate bindings for contracts.`, } - generateBindingsCmd.Flags().StringP("project-root", "p", "", "Path to project root directory (defaults to current directory)") - generateBindingsCmd.Flags().StringP("language", "l", "", "Target language: go, typescript (auto-detected from project files when omitted)") - generateBindingsCmd.Flags().StringP("abi", "a", "", "Path to ABI directory (defaults to contracts/{chain-family}/src/abi/). Supports *.abi and *.json files") - generateBindingsCmd.Flags().StringP("pkg", "k", "bindings", "Base package name (each contract gets its own subdirectory)") + generateBindingsCmd.AddCommand(evm.New(runtimeContext)) + generateBindingsCmd.AddCommand(solana.New(runtimeContext)) return generateBindingsCmd } - -type handler struct { - log *zerolog.Logger - validated bool -} - -func newHandler(ctx *runtime.Context) *handler { - return &handler{ - log: ctx.Logger, - validated: false, - } -} - -func detectLanguages(projectRoot string) (goLang, typescript bool) { - _ = filepath.WalkDir(projectRoot, func(path string, d os.DirEntry, err error) error { - if err != nil { - return nil - } - if d.IsDir() { - // Skip node_modules and other dependency directories - if d.Name() == "node_modules" || d.Name() == ".git" { - return filepath.SkipDir - } - return nil - } - base := filepath.Base(path) - if strings.HasSuffix(base, ".go") { - goLang = true - } - if strings.HasSuffix(base, ".ts") && !strings.HasSuffix(base, ".d.ts") { - typescript = true - } - return nil - }) - return goLang, typescript -} - -func (h *handler) ResolveInputs(args []string, v *viper.Viper) (Inputs, error) { - // Get current working directory as default project root - currentDir, err := os.Getwd() - if err != nil { - return Inputs{}, fmt.Errorf("failed to get current working directory: %w", err) - } - - // Resolve project root with fallback to current directory - projectRoot := v.GetString("project-root") - if projectRoot == "" { - projectRoot = currentDir - } - - contractsPath := filepath.Join(projectRoot, "contracts") - if _, err := os.Stat(contractsPath); err != nil { - return Inputs{}, fmt.Errorf("contracts folder not found in project root: %s", contractsPath) - } - - // Chain family is now a positional argument - chainFamily := args[0] - - // Resolve languages: --language flag takes precedence, else auto-detect - var goLang, typescript bool - langFlag := strings.ToLower(strings.TrimSpace(v.GetString("language"))) - switch langFlag { - case "": - goLang, typescript = detectLanguages(projectRoot) - if !goLang && !typescript { - return Inputs{}, fmt.Errorf("no target language detected (use --language go or --language typescript, or ensure project contains .go or .ts files)") - } - case constants.WorkflowLanguageGolang: - goLang = true - case constants.WorkflowLanguageTypeScript: - typescript = true - default: - return Inputs{}, fmt.Errorf("unsupported language %q (supported: go, typescript)", langFlag) - } - - // Unified ABI path for both languages: contracts/{chain}/src/abi - abiPath := v.GetString("abi") - if abiPath == "" { - abiPath = filepath.Join(projectRoot, "contracts", chainFamily, "src", "abi") - } - - // Package name defaults are handled by StringP - pkgName := v.GetString("pkg") - - // Separate output paths: Go uses src/, TS uses ts/ (typescript convention) - var goOutPath, tsOutPath string - if goLang { - goOutPath = filepath.Join(projectRoot, "contracts", chainFamily, "src", "generated") - } - if typescript { - tsOutPath = filepath.Join(projectRoot, "contracts", chainFamily, "ts", "generated") - } - - return Inputs{ - ProjectRoot: projectRoot, - ChainFamily: chainFamily, - GoLang: goLang, - TypeScript: typescript, - AbiPath: abiPath, - PkgName: pkgName, - GoOutPath: goOutPath, - TSOutPath: tsOutPath, - }, nil -} - -// findAbiFiles returns all supported ABI files (*.abi and *.json) found in dir. -func findAbiFiles(dir string) ([]string, error) { - abiFiles, err := filepath.Glob(filepath.Join(dir, "*.abi")) - if err != nil { - return nil, err - } - jsonFiles, err := filepath.Glob(filepath.Join(dir, "*.json")) - if err != nil { - return nil, err - } - all := append(abiFiles, jsonFiles...) - sort.Strings(all) - return all, nil -} - -// contractNameFromFile returns the contract name by stripping the .abi or .json -// extension from the base filename. -func contractNameFromFile(path string) string { - name := filepath.Base(path) - ext := filepath.Ext(name) - if ext != "" { - name = name[:len(name)-len(ext)] - } - return name -} - -func (h *handler) ValidateInputs(inputs Inputs) error { - validate, err := validation.NewValidator() - if err != nil { - return fmt.Errorf("failed to initialize validator: %w", err) - } - - if err = validate.Struct(inputs); err != nil { - return validate.ParseValidationErrors(err) - } - - // Additional validation for ABI path - if _, err := os.Stat(inputs.AbiPath); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("ABI path does not exist: %s", inputs.AbiPath) - } - return fmt.Errorf("failed to access ABI path: %w", err) - } - - // Validate that if AbiPath is a directory, it contains ABI files (*.abi or *.json) - if info, err := os.Stat(inputs.AbiPath); err == nil && info.IsDir() { - files, err := findAbiFiles(inputs.AbiPath) - if err != nil { - return fmt.Errorf("failed to check for ABI files in directory: %w", err) - } - if len(files) == 0 { - return fmt.Errorf("no *.abi or *.json files found in directory: %s", inputs.AbiPath) - } - } - - // Ensure at least one output path is set for the active language(s) - if inputs.GoLang && inputs.GoOutPath == "" { - return fmt.Errorf("go output path is required when language is go") - } - if inputs.TypeScript && inputs.TSOutPath == "" { - return fmt.Errorf("typescript output path is required when language is typescript") - } - - h.validated = true - return nil -} - -// contractNameToPackage converts contract names to valid Go package names -// Examples: IERC20 -> ierc20, ReserveManager -> reserve_manager, IReserveManager -> ireserve_manager -func contractNameToPackage(contractName string) string { - if contractName == "" { - return "" - } - - var result []rune - runes := []rune(contractName) - - for i, r := range runes { - // Convert to lowercase - if r >= 'A' && r <= 'Z' { - lower := r - 'A' + 'a' - - // Add underscore before uppercase letters, but not: - // - At the beginning (i == 0) - // - If the previous character was also uppercase and this is followed by lowercase (e.g., "ERC" in "ERC20") - // - If this is part of a sequence of uppercase letters at the beginning (e.g., "IERC20" -> "ierc20") - if i > 0 { - prevIsUpper := runes[i-1] >= 'A' && runes[i-1] <= 'Z' - nextIsLower := i+1 < len(runes) && runes[i+1] >= 'a' && runes[i+1] <= 'z' - - // Add underscore if: - // - Previous char was lowercase (CamelCase boundary) - // - Previous char was uppercase but this char is followed by lowercase (end of acronym) - if !prevIsUpper || (prevIsUpper && nextIsLower && i > 1) { - result = append(result, '_') - } - } - - result = append(result, lower) - } else { - result = append(result, r) - } - } - - return string(result) -} - -func (h *handler) processAbiDirectory(inputs Inputs) error { - files, err := findAbiFiles(inputs.AbiPath) - if err != nil { - return fmt.Errorf("failed to find ABI files: %w", err) - } - - if len(files) == 0 { - return fmt.Errorf("no *.abi or *.json files found in directory: %s", inputs.AbiPath) - } - - // Detect duplicate contract names across extensions (e.g. Foo.abi and Foo.json) - contractNames := make(map[string]string) // contract name -> originating file - for _, f := range files { - name := contractNameFromFile(f) - if prev, exists := contractNames[name]; exists { - return fmt.Errorf("duplicate contract name %q: found in both %s and %s", name, filepath.Base(prev), filepath.Base(f)) - } - contractNames[name] = f - } - - if inputs.GoLang { - packageNames := make(map[string]bool) - for _, abiFile := range files { - contractName := contractNameFromFile(abiFile) - packageName := contractNameToPackage(contractName) - if _, exists := packageNames[packageName]; exists { - return fmt.Errorf("package name collision: multiple contracts would generate the same package name '%s' (contracts are converted to snake_case for package names). Please rename one of your contract files to avoid this conflict", packageName) - } - packageNames[packageName] = true - } - } - - // Track generated files for TypeScript barrel export - var generatedContracts []string - - // Process each ABI file - for _, abiFile := range files { - contractName := contractNameFromFile(abiFile) - - if inputs.TypeScript { - outputFile := filepath.Join(inputs.TSOutPath, contractName+".ts") - ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) - - err = bindings.GenerateBindingsTS( - abiFile, - contractName, - outputFile, - ) - if err != nil { - return fmt.Errorf("failed to generate TypeScript bindings for %s: %w", contractName, err) - } - generatedContracts = append(generatedContracts, contractName) - } - - if inputs.GoLang { - packageName := contractNameToPackage(contractName) - - contractOutDir := filepath.Join(inputs.GoOutPath, packageName) - if err := os.MkdirAll(contractOutDir, 0o755); err != nil { - return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err) - } - - outputFile := filepath.Join(contractOutDir, contractName+".go") - ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) - - err = bindings.GenerateBindings( - "", - abiFile, - packageName, - contractName, - outputFile, - ) - if err != nil { - return fmt.Errorf("failed to generate bindings for %s: %w", contractName, err) - } - } - } - - // Generate barrel index.ts for TypeScript - if inputs.TypeScript && len(generatedContracts) > 0 { - indexPath := filepath.Join(inputs.TSOutPath, "index.ts") - var indexContent string - indexContent += "// Code generated — DO NOT EDIT.\n" - for _, name := range generatedContracts { - indexContent += fmt.Sprintf("export * from './%s'\n", name) - indexContent += fmt.Sprintf("export * from './%s_mock'\n", name) - } - if err := os.WriteFile(indexPath, []byte(indexContent), 0o600); err != nil { - return fmt.Errorf("failed to write index.ts: %w", err) - } - } - - return nil -} - -func (h *handler) processSingleAbi(inputs Inputs) error { - contractName := contractNameFromFile(inputs.AbiPath) - - if inputs.TypeScript { - outputFile := filepath.Join(inputs.TSOutPath, contractName+".ts") - ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) - - if err := bindings.GenerateBindingsTS( - inputs.AbiPath, - contractName, - outputFile, - ); err != nil { - return err - } - } - - if inputs.GoLang { - packageName := contractNameToPackage(contractName) - - contractOutDir := filepath.Join(inputs.GoOutPath, packageName) - if err := os.MkdirAll(contractOutDir, 0o755); err != nil { - return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err) - } - - outputFile := filepath.Join(contractOutDir, contractName+".go") - ui.Dim(fmt.Sprintf("Processing: %s -> %s", contractName, outputFile)) - - if err := bindings.GenerateBindings( - "", - inputs.AbiPath, - packageName, - contractName, - outputFile, - ); err != nil { - return err - } - } - - return nil -} - -func (h *handler) Execute(inputs Inputs) error { - langs := []string{} - if inputs.GoLang { - langs = append(langs, "go") - } - if inputs.TypeScript { - langs = append(langs, "typescript") - } - ui.Dim(fmt.Sprintf("Project: %s, Chain: %s, Languages: %v", inputs.ProjectRoot, inputs.ChainFamily, langs)) - - // Validate chain family and handle accordingly - switch inputs.ChainFamily { - case "evm": - // Create output directories for active language(s) - if inputs.GoLang { - if err := os.MkdirAll(inputs.GoOutPath, 0o755); err != nil { - return fmt.Errorf("failed to create Go output directory: %w", err) - } - } - if inputs.TypeScript { - if err := os.MkdirAll(inputs.TSOutPath, 0o755); err != nil { - return fmt.Errorf("failed to create TypeScript output directory: %w", err) - } - } - - // Check if ABI path is a directory or file - info, err := os.Stat(inputs.AbiPath) - if err != nil { - return fmt.Errorf("failed to access ABI path: %w", err) - } - - if info.IsDir() { - if err := h.processAbiDirectory(inputs); err != nil { - return err - } - } else { - if err := h.processSingleAbi(inputs); err != nil { - return err - } - } - - if inputs.GoLang { - spinner := ui.NewSpinner() - spinner.Start("Installing dependencies...") - - err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go@"+constants.SdkVersion) - if err != nil { - spinner.Stop() - return err - } - err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm@"+constants.EVMCapabilitiesVersion) - if err != nil { - spinner.Stop() - return err - } - if err = runCommand(inputs.ProjectRoot, "go", "mod", "tidy"); err != nil { - spinner.Stop() - return err - } - - spinner.Stop() - } - - ui.Success("Bindings generated successfully") - return nil - default: - return fmt.Errorf("unsupported chain family: %s", inputs.ChainFamily) - } -} - -func runCommand(dir string, command string, args ...string) error { - cmd := exec.Command(command, args...) - cmd.Dir = dir - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to run %s: %w", command, err) - } - - return nil -} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/accounts.go b/cmd/generate-bindings/solana/anchor-go/generator/accounts.go new file mode 100644 index 00000000..ea7d16fc --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/accounts.go @@ -0,0 +1,409 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + "strconv" + + . "github.com/dave/jennifer/jen" + "github.com/davecgh/go-spew/spew" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/gagliardetto/anchor-go/tools" +) + +func (g *Generator) genfile_accounts() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains parsers for the accounts defined in the IDL.") + file.HeaderComment("Code generated by https://github.com/smartcontractkit/cre-cli. DO NOT EDIT.") + + names := []string{} + { + for _, acc := range g.idl.Accounts { + names = append(names, tools.ToCamelUpper(acc.Name)) + } + } + { + code, err := g.gen_accountParser(names) + if err != nil { + return nil, fmt.Errorf("error generating account parser: %w", err) + } + file.Add(code) + } + + return &OutputFile{ + Name: "accounts.go", + File: file, + }, nil +} + +func (g *Generator) gen_accountParser(accountNames []string) (Code, error) { + code := Empty() + { + code.Func().Id("ParseAnyAccount"). + Params(Id("accountData").Index().Byte()). + Params(Any(), Error()). + BlockFunc(func(block *Group) { + block.Id("decoder").Op(":=").Qual(PkgBinary, "NewBorshDecoder").Call(Id("accountData")) + block.List(Id("discriminator"), Err()).Op(":=").Id("decoder").Dot("ReadDiscriminator").Call() + + block.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to peek account discriminator: %w"), Err()), + ), + ) + + block.Switch(Id("discriminator")).BlockFunc(func(switchBlock *Group) { + for _, name := range accountNames { + switchBlock.Case(Id(FormatAccountDiscriminatorName(name))).Block( + Id("value").Op(":=").New(Id(name)), + Err().Op(":=").Id("value").Dot("UnmarshalWithDecoder").Call(Id("decoder")), + If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to unmarshal account as "+name+": %w"), Err()), + ), + ), + Return(Id("value"), Nil()), + ) + } + switchBlock.Default().Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("unknown discriminator: %s"), Qual(PkgBinary, "FormatDiscriminator").Call(Id("discriminator")))), + ) + }) + }) + } + { + code.Line().Line() + // for each account, generate a function to parse it: + for _, name := range accountNames { + discriminatorName := FormatAccountDiscriminatorName(name) + + code.Func().Id("ParseAccount_"+name). + Params(Id("accountData").Index().Byte()). + Params(Op("*").Id(name), Error()). + BlockFunc(func(block *Group) { + block.Id("decoder").Op(":=").Qual(PkgBinary, "NewBorshDecoder").Call(Id("accountData")) + block.List(Id("discriminator"), Err()).Op(":=").Id("decoder").Dot("ReadDiscriminator").Call() + + block.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to peek discriminator: %w"), Err()), + ), + ) + + block.If(Id("discriminator").Op("!=").Id(discriminatorName)).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("expected discriminator %v, got %s"), Id(discriminatorName), Qual(PkgBinary, "FormatDiscriminator").Call(Id("discriminator")))), + ) + + block.Id("acc").Op(":=").New(Id(name)) + block.Err().Op("=").Id("acc").Dot("UnmarshalWithDecoder").Call(Id("decoder")) + + block.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to unmarshal account of type "+name+": %w"), Err()), + ), + ) + + block.Return(Id("acc"), Nil()) + }) + code.Line().Line() + + // DecodeAccount method for the codec + code.Add(creDecodeAccountFn(name)) + code.Line().Line() + } + } + return code, nil +} + +func (g *Generator) gen_IDLTypeDefTyStruct( + name string, + docs []string, + typ idl.IdlTypeDefTyStruct, + withDiscriminator bool, +) (Code, error) { + st := newStatement() + + exportedAccountName := tools.ToCamelUpper(name) + { + // Declare the struct: + code := Empty() + addComments(code, docs) + code.Type().Id(exportedAccountName).StructFunc(func(fieldsGroup *Group) { + switch fields := typ.Fields.(type) { + case idl.IdlDefinedFieldsNamed: + // Generate unique field names to handle duplicates + uniqueFieldNames := generateUniqueFieldNames(fields) + + for fieldIndex, field := range fields { + + // Add docs for the field: + for docIndex, doc := range field.Docs { + if docIndex == 0 && fieldIndex > 0 { + fieldsGroup.Line() + } + fieldsGroup.Comment(doc) + } + // fieldsGroup.Line() + optionality := IsOption(field.Ty) || IsCOption(field.Ty) + + // TODO: optionality for complex enums is a nil interface. + uniqueFieldName := uniqueFieldNames[field.Name] + fieldsGroup.Add(g.genFieldWithName(field, uniqueFieldName, optionality)). + Add(func() Code { + tagMap := map[string]string{} + if IsOption(field.Ty) { + tagMap["bin"] = "optional" + } + if IsCOption(field.Ty) { + tagMap["bin"] = "coption" + } + // add json tag: use original field name to avoid duplicates + tagMap["json"] = field.Name + func() string { + if optionality { + return ",omitempty" + } + return "" + }() + return Tag(tagMap) + }()) + } + case idl.IdlDefinedFieldsTuple: + // panic(fmt.Errorf("tuple fields not supported: %s", spew.Sdump(fields))) + for fieldIndex, field := range fields { + + fieldsGroup.Line() + optionality := IsOption(field) || IsCOption(field) + + fieldsGroup.Add(g.genFieldNamed( + FormatTupleItemName(fieldIndex), + field, + optionality, + )). + Add(func() Code { + tagMap := map[string]string{} + if IsOption(field) { + tagMap["bin"] = "optional" + } + if IsCOption(field) { + tagMap["bin"] = "coption" + } + // add json tag: + tagMap["json"] = tools.ToCamelLower(FormatTupleItemName(fieldIndex)) + func() string { + if optionality { + return ",omitempty" + } + return "" + }() + return Tag(tagMap) + }()) + } + + case nil: + // No fields, just an empty struct. + // TODO: should we panic here? + default: + panic(fmt.Errorf("unknown fields type: %T", typ.Fields)) + } + }) + st.Add(code.Line()) + } + { + // Declare the decoder/encoder methods: + code := Empty() + + { + discriminatorName := FormatAccountDiscriminatorName(exportedAccountName) + + // Declare MarshalWithEncoder: + // TODO: + code.Line().Line().Add( + g.gen_MarshalWithEncoder_struct( + g.idl, + withDiscriminator, + exportedAccountName, + discriminatorName, + typ.Fields, + true, + )) + + // Declare UnmarshalWithDecoder + code.Line().Line().Add( + g.gen_UnmarshalWithDecoder_struct( + g.idl, + withDiscriminator, + exportedAccountName, + discriminatorName, + typ.Fields, + )) + } + st.Add(code.Line().Line()) + } + { + code := Empty() + code.Add(creGenerateCodecEncoderForTypes(exportedAccountName)) + st.Add(code.Line().Line()) + } + { + // Declare the WriteReportFrom methods: + // TODO: should i exclude events here ? currently it does accounts/structs/events + code := Empty() + code.Add(creWriteReportFromStructs(exportedAccountName, g)) + code.Line().Line() + code.Add(creWriteReportFromStructsSlice(exportedAccountName, g)) + st.Add(code.Line().Line()) + } + return st, nil +} + +// generateUniqueFieldNames creates unique Go field names from IDL field names, +// handling cases where multiple IDL fields would map to the same Go field name +func generateUniqueFieldNames(fields []idl.IdlField) map[string]string { + fieldNameMap := make(map[string]string) + usedNames := make(map[string]int) + + for _, field := range fields { + baseName := tools.ToCamelUpper(field.Name) + finalName := baseName + + // Check if this name has been used before + if count, exists := usedNames[baseName]; exists { + // Add a numeric suffix to make it unique + finalName = baseName + fmt.Sprintf("%d", count+1) + usedNames[baseName] = count + 1 + } else { + usedNames[baseName] = 0 + } + + fieldNameMap[field.Name] = finalName + } + + return fieldNameMap +} + +func (g *Generator) genField(field idl.IdlField, pointer bool) Code { + return g.genFieldNamed(field.Name, field.Ty, pointer) +} + +func (g *Generator) genFieldWithName(field idl.IdlField, fieldName string, pointer bool) Code { + return g.genFieldNamed(fieldName, field.Ty, pointer) +} + +func (g *Generator) genFieldNamed(name string, typ idltype.IdlType, pointer bool) Code { + st := newStatement() + st.Id(tools.ToCamelUpper(name)). + Add(func() Code { + if g.isComplexEnum(typ) { + return nil + } + if pointer { + return Op("*") + } + return nil + }()). + Add(genTypeName(typ)) + return st +} + +func genTypeName(idlTypeEnv idltype.IdlType) Code { + st := newStatement() + switch { + case IsIDLTypeKind(idlTypeEnv): + { + str := idlTypeEnv + st.Add(IDLTypeKind_ToTypeDeclCode(str)) + } + case IsOption(idlTypeEnv): + { + opt := idlTypeEnv.(*idltype.Option) + // TODO: optional = pointer? or that's determined upstream? + st.Add(genTypeName(opt.Option)) + } + case IsCOption(idlTypeEnv): + { + copt := idlTypeEnv.(*idltype.COption) + st.Add(genTypeName(copt.COption)) + } + case IsVec(idlTypeEnv): + { + vec := idlTypeEnv.(*idltype.Vec) + st.Index().Add(genTypeName(vec.Vec)) + } + case IsDefined(idlTypeEnv): + { + def := idlTypeEnv.(*idltype.Defined) + st.Add(Id(tools.ToCamelUpper(def.Name))) + } + case IsArray(idlTypeEnv): + { + arr := idlTypeEnv.(*idltype.Array) + { + switch size := arr.Size.(type) { + case *idltype.IdlArrayLenGeneric: + panic(fmt.Sprintf("generic array length not supported: %s", spew.Sdump(size))) + case *idltype.IdlArrayLenValue: + if size.Value < 0 { + panic(fmt.Sprintf("expected positive integer, got %d", size.Value)) + } + st.Index(Id(strconv.Itoa(int(size.Value)))).Add(genTypeName(arr.Type)) + } + } + } + default: + panic("unhandled type: " + spew.Sdump(idlTypeEnv)) + } + return st +} + +func IDLTypeKind_ToTypeDeclCode(ts idltype.IdlType) *Statement { + stat := newStatement() + switch ts.(type) { + case *idltype.Bool: + stat.Bool() + case *idltype.U8: + stat.Uint8() + case *idltype.I8: + stat.Int8() + case *idltype.U16: + // TODO: some types have their implementation in github.com/gagliardetto/binary + stat.Uint16() + case *idltype.I16: + stat.Int16() + case *idltype.U32: + stat.Uint32() + case *idltype.I32: + stat.Int32() + case *idltype.F32: + stat.Float32() + case *idltype.U64: + stat.Uint64() + case *idltype.I64: + stat.Int64() + case *idltype.F64: + stat.Float64() + case *idltype.U128: + stat.Qual(PkgBinary, "Uint128") + case *idltype.I128: + stat.Qual(PkgBinary, "Int128") + case *idltype.U256: + stat.Index(Lit(32)).Byte() + case *idltype.I256: + stat.Index(Lit(32)).Byte() + case *idltype.Bytes: + stat.Index().Byte() + case *idltype.String: + stat.String() + case *idltype.Pubkey: + stat.Qual(PkgSolanaGo, "PublicKey") + + default: + panic(fmt.Sprintf("unhandled type: %s", spew.Sdump(ts))) + } + + return stat +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/collision_field_names_test.go b/cmd/generate-bindings/solana/anchor-go/generator/collision_field_names_test.go new file mode 100644 index 00000000..477cefa8 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/collision_field_names_test.go @@ -0,0 +1,140 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "strings" + "testing" + + "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// collidingNamedFields is an IDL shape where two distinct field names normalize to the +// same Go identifier via tools.ToCamelUpper (foo_bar and fooBar -> FooBar). Struct +// generation deconflicts these as FooBar and FooBar1; marshal/unmarshal must use the same names. +func collidingNamedFields() idl.IdlDefinedFieldsNamed { + return idl.IdlDefinedFieldsNamed{ + {Name: "foo_bar", Ty: &idltype.U8{}}, + {Name: "fooBar", Ty: &idltype.U8{}}, + } +} + +func TestGenerateUniqueFieldNames_collidingIDLNames(t *testing.T) { + fields := collidingNamedFields() + m := generateUniqueFieldNames(fields) + require.Len(t, m, 2) + assert.Equal(t, "FooBar", m["foo_bar"]) + assert.Equal(t, "FooBar1", m["fooBar"]) +} + +// TestMarshalUnmarshalCodegen_matchesUniqueStructFieldNames documents the regression where +// gen_MarshalWithEncoder_struct / gen_UnmarshalWithDecoder_struct used tools.ToCamelUpper(field.Name) +// for accessors instead of generateUniqueFieldNames: both fields targeted obj.FooBar, so one +// value was serialized twice and the FooBar1 sibling was never written or read. +// +// The expected assertions describe the correct fixed behavior; they fail until uniquified names +// are threaded through marshal/unmarshal generation. +func TestMarshalUnmarshalCodegen_matchesUniqueStructFieldNames(t *testing.T) { + idlMinimal := &idl.Idl{} + fields := collidingNamedFields() + receiver := "CollideAccount" + + g := &Generator{ + idl: idlMinimal, + options: &GeneratorOptions{Package: "test"}, + complexEnumRegistry: make(map[string]struct{}), + } + + marshalCode := g.gen_MarshalWithEncoder_struct( + idlMinimal, + false, + receiver, + "", + fields, + true, + ) + unmarshalCode := g.gen_UnmarshalWithDecoder_struct( + idlMinimal, + false, + receiver, + "", + fields, + ) + + f := jen.NewFile("fixture") + f.Add(marshalCode) + f.Add(unmarshalCode) + src := f.GoString() + + // Correct codegen must reference both uniquified struct fields. + assert.Contains(t, src, "obj.FooBar1", "marshal/unmarshal must access the deconflicted FooBar1 field") + + // Buggy codegen encodes/decodes the same field twice; reject duplicate bare obj.FooBar + // Encode/Decode when a second distinct IDL field exists. + encodeFooBar := strings.Count(src, "Encode(obj.FooBar)") + decodeFooBar := strings.Count(src, "Decode(&obj.FooBar)") + assert.Equal(t, 1, encodeFooBar, "each IDL field must map to a single Encode(obj.); duplicate Encode(obj.FooBar) indicates silent corruption") + assert.Equal(t, 1, decodeFooBar, "each IDL field must map to a single Decode(&obj.); duplicate Decode(&obj.FooBar) indicates silent corruption") + + assert.Contains(t, src, "Encode(obj.FooBar1)") + assert.Contains(t, src, "Decode(&obj.FooBar1)") +} + +func TestGenerateUniqueParamNames_collidingIDLNames(t *testing.T) { + fields := collidingNamedFields() + m := generateUniqueParamNames(fields) + require.Len(t, m, 2) + assert.NotEqual(t, m[fields[0].Name], m[fields[1].Name]) + b0 := formatParamName(fields[0].Name) + b1 := formatParamName(fields[1].Name) + if b0 == b1 { + assert.Equal(t, b0+"1", m[fields[1].Name]) + } +} + +func TestGenInstructionType_uniquifiesArgFieldsAndDecode(t *testing.T) { + ins := idl.IdlInstruction{ + Name: "do_test", + Args: []idl.IdlField(collidingNamedFields()), + Accounts: []idl.IdlInstructionAccountItem{}, + } + g := &Generator{idl: &idl.Idl{}, options: &GeneratorOptions{Package: "test"}} + code, err := g.gen_instructionType(ins) + require.NoError(t, err) + + f := jen.NewFile("test") + f.Add(code) + src := f.GoString() + + assert.Contains(t, src, "FooBar1") + assert.Equal(t, 1, strings.Count(src, "Decode(&obj.FooBar)")) + assert.Contains(t, src, "Decode(&obj.FooBar1)") +} + +func TestGenInstructions_builderEncodesEachArgOnce(t *testing.T) { + fields := collidingNamedFields() + paramNames := generateUniqueParamNames(fields) + + idlData := &idl.Idl{ + Instructions: []idl.IdlInstruction{ + { + Name: "do_test", + Args: []idl.IdlField(fields), + Accounts: []idl.IdlInstructionAccountItem{}, + }, + }, + } + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + out, err := gen.gen_instructions() + require.NoError(t, err) + s := out.File.GoString() + + p0 := paramNames[fields[0].Name] + p1 := paramNames[fields[1].Name] + assert.Equal(t, 1, strings.Count(s, "Encode("+p0+")"), "each arg must be encoded exactly once") + assert.Equal(t, 1, strings.Count(s, "Encode("+p1+")")) + assert.NotEqual(t, p0, p1) +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/complex-enums.go b/cmd/generate-bindings/solana/anchor-go/generator/complex-enums.go new file mode 100644 index 00000000..62e66642 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/complex-enums.go @@ -0,0 +1,48 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" +) + +func (g *Generator) isComplexEnum(envel idltype.IdlType) bool { + switch vv := envel.(type) { + case *idltype.Defined: + _, ok := g.complexEnumRegistry[vv.Name] + return ok + } + return false +} + +func (g *Generator) registerComplexEnumType(name string) { + if g.complexEnumRegistry == nil { + g.complexEnumRegistry = make(map[string]struct{}) + } + g.complexEnumRegistry[name] = struct{}{} +} + +func (g *Generator) isOptionalComplexEnum(ty idltype.IdlType) bool { + switch v := ty.(type) { + case *idltype.Option: + return g.isComplexEnum(v.Option) + case *idltype.COption: + return g.isComplexEnum(v.COption) + } + return false +} + +func (g *Generator) registerComplexEnums(def idl.IdlTypeDef) { + switch vv := def.Ty.(type) { + case *idl.IdlTypeDefTyEnum: + enumTypeName := def.Name + if !vv.IsAllSimple() { + g.registerComplexEnumType(enumTypeName) + } + case idl.IdlTypeDefTyEnum: + enumTypeName := def.Name + if !vv.IsAllSimple() { + g.registerComplexEnumType(enumTypeName) + } + } +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/complex_enum_encode_test.go b/cmd/generate-bindings/solana/anchor-go/generator/complex_enum_encode_test.go new file mode 100644 index 00000000..b92501e1 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/complex_enum_encode_test.go @@ -0,0 +1,86 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "strings" + "testing" + + "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// complexEnumIDL returns a minimal IDL containing a two-variant complex enum +// ("MyAction") suitable for exercising gen_complexEnum codegen. +func complexEnumIDL() *idl.Idl { + enumType := &idl.IdlTypeDefTyEnum{ + Variants: idl.VariantSlice{ + { + Name: "Transfer", + Fields: idl.Some[idl.IdlDefinedFields](idl.IdlDefinedFieldsNamed{ + {Name: "amount", Ty: &idltype.U64{}}, + }), + }, + { + Name: "Burn", + Fields: idl.Some[idl.IdlDefinedFields](idl.IdlDefinedFieldsNamed{ + {Name: "quantity", Ty: &idltype.U32{}}, + }), + }, + }, + } + return &idl.Idl{ + Types: idl.IdTypeDef_slice{ + { + Name: "MyAction", + Ty: enumType, + }, + }, + } +} + +func genComplexEnumSource(t *testing.T) string { + t.Helper() + idlData := complexEnumIDL() + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + enumType := idlData.Types[0].Ty.(*idl.IdlTypeDefTyEnum) + code, err := gen.gen_complexEnum("MyAction", nil, *enumType) + require.NoError(t, err) + + f := jen.NewFile("test") + f.Add(code) + return f.GoString() +} + +func TestComplexEnumEncode_nilInterfaceReturnsError(t *testing.T) { + src := genComplexEnumSource(t) + + assert.Contains(t, src, "case nil:", "encoder must reject nil interface values") + assert.Contains(t, src, `cannot encode nil value`, "nil case must return a descriptive error") +} + +func TestComplexEnumEncode_defaultArmReturnsError(t *testing.T) { + src := genComplexEnumSource(t) + + assert.Contains(t, src, "default:", "encoder must reject unknown variant types") + assert.Contains(t, src, `unknown variant type`, "default case must return a descriptive error") +} + +func TestComplexEnumEncode_nilPointerGuardPerVariant(t *testing.T) { + src := genComplexEnumSource(t) + + assert.Contains(t, src, "realvalue == nil", "each variant case must guard against typed nil pointers") + assert.Contains(t, src, `cannot encode nil *MyAction_Transfer`, + "Transfer variant must have a nil-pointer error message") + assert.Contains(t, src, `cannot encode nil *MyAction_Burn`, + "Burn variant must have a nil-pointer error message") + + nilGuards := strings.Count(src, "realvalue == nil") + assert.Equal(t, 2, nilGuards, "must have exactly one nil-pointer guard per variant") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/complex_enums_test.go b/cmd/generate-bindings/solana/anchor-go/generator/complex_enums_test.go new file mode 100644 index 00000000..c8049cf1 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/complex_enums_test.go @@ -0,0 +1,114 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "strings" + "testing" + + "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/stretchr/testify/assert" +) + +// complexEnumGuard mirrors the condition used in gen_marshal_DefinedFieldsNamed +// and gen_unmarshal_DefinedFieldsNamed to decide whether a field is routed to +// the specialized enum encoder/parser or falls through to the generic +// Encode/Decode path. +func complexEnumGuard(g *Generator, ty idltype.IdlType) bool { + return g.isComplexEnum(ty) || + (IsArray(ty) && g.isComplexEnum(ty.(*idltype.Array).Type)) || + (IsVec(ty) && g.isComplexEnum(ty.(*idltype.Vec).Vec)) || + g.isOptionalComplexEnum(ty) +} + +func newTestGenerator() *Generator { + return &Generator{ + idl: &idl.Idl{}, + options: &GeneratorOptions{Package: "test"}, + complexEnumRegistry: make(map[string]struct{}), + } +} + +func TestComplexEnumGuard_handlesOptionAndCOption(t *testing.T) { + const name = "Outcome" + g := newTestGenerator() + g.registerComplexEnumType(name) + + defined := &idltype.Defined{Name: name} + + assert.True(t, complexEnumGuard(g, defined), "bare Defined") + assert.True(t, complexEnumGuard(g, &idltype.Option{Option: defined}), "Option") + assert.True(t, complexEnumGuard(g, &idltype.COption{COption: defined}), "COption") +} + +// TestComplexEnumGuard_rejectsNonComplexOptionals ensures the guard does NOT +// fire for Option/COption wrapping a non-complex Defined or a primitive. +// A false positive here would cause the switch to enter the Option/COption case +// where .Option.(*idltype.Defined) would panic on a non-Defined inner type. +func TestComplexEnumGuard_rejectsNonComplexOptionals(t *testing.T) { + const complexName = "Outcome" + g := newTestGenerator() + g.registerComplexEnumType(complexName) + + nonComplex := &idltype.Defined{Name: "PlainStruct"} + + assert.False(t, complexEnumGuard(g, &idltype.Option{Option: nonComplex}), + "Option must not trigger the complex-enum path") + assert.False(t, complexEnumGuard(g, &idltype.COption{COption: nonComplex}), + "COption must not trigger the complex-enum path") + assert.False(t, complexEnumGuard(g, &idltype.Option{Option: &idltype.U64{}}), + "Option must not trigger the complex-enum path") + assert.False(t, complexEnumGuard(g, &idltype.COption{COption: &idltype.U8{}}), + "COption must not trigger the complex-enum path") + assert.False(t, complexEnumGuard(g, &idltype.Option{Option: &idltype.Vec{Vec: &idltype.Defined{Name: complexName}}}), + "Option> — nested containers not supported, must not match") +} + +// TestComplexEnumCodegen_optionalComplexEnum runs the actual marshal/unmarshal +// generator with Option and COption fields and +// verifies the generated Go source uses the specialized enum encoder/parser +// instead of the generic Encode/Decode. +func TestComplexEnumCodegen_optionalComplexEnum(t *testing.T) { + const enumName = "Outcome" + g := newTestGenerator() + g.registerComplexEnumType(enumName) + + fields := idl.IdlDefinedFieldsNamed{ + {Name: "id", Ty: &idltype.U64{}}, + {Name: "verdict", Ty: &idltype.Option{Option: &idltype.Defined{Name: enumName}}}, + {Name: "alt_verdict", Ty: &idltype.COption{COption: &idltype.Defined{Name: enumName}}}, + {Name: "checksum", Ty: &idltype.U64{}}, + } + + marshalCode := g.gen_MarshalWithEncoder_struct( + &idl.Idl{}, false, "Report", "", fields, true, + ) + unmarshalCode := g.gen_UnmarshalWithDecoder_struct( + &idl.Idl{}, false, "Report", "", fields, + ) + + f := jen.NewFile("fixture") + f.Add(marshalCode) + f.Add(unmarshalCode) + src := f.GoString() + + // Specialized enum encoder/parser must appear. + assert.Contains(t, src, "EncodeOutcome", + "Option/COption fields must call the specialized enum encoder") + assert.Contains(t, src, "DecodeOutcome", + "Option/COption fields must call the specialized enum parser") + + // Option flags must still be written/read. + assert.Contains(t, src, "WriteOption") + assert.Contains(t, src, "WriteCOption") + assert.Contains(t, src, "ReadOption") + assert.Contains(t, src, "ReadCOption") + + // Only the two plain U64 fields (Id, Checksum) should use the generic + // encoder/decoder. If the enum fields also fall through, the count is 4. + assert.Equal(t, 2, strings.Count(src, ".Encode("), + "generic Encode must only be used for non-enum fields") + assert.Equal(t, 2, strings.Count(src, ".Decode("), + "generic Decode must only be used for non-enum fields") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/constants.go b/cmd/generate-bindings/solana/anchor-go/generator/constants.go new file mode 100644 index 00000000..ad2d6ad3 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/constants.go @@ -0,0 +1,408 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "encoding/json" + "fmt" + "math/big" + "strconv" + "strings" + + . "github.com/dave/jennifer/jen" + "github.com/davecgh/go-spew/spew" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/gagliardetto/solana-go" +) + +func (g *Generator) gen_constants() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains constants.") + { + if len(g.idl.Constants) > 0 { + file.Comment("Constants defined in the IDL:") + file.Line() + } + code := Empty() + for coi, co := range g.idl.Constants { + if co.Name == "" { + continue // Skip constants without a name. + } + if len(co.Value) == 0 { + continue // Skip constants without a value. + } + + addComments(code, co.Docs) + + switch ty := co.Ty.(type) { + case *idltype.String: + _ = ty + // "value":"\"organism\"" + v, err := strconv.Unquote(co.Value) + if err != nil { + return nil, fmt.Errorf("failed to unquote string constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(v) + code.Line() + case *idltype.Bytes: + _ = ty + // "value":"[102, 101, 101, 95, 118, 97, 117, 108, 116]" + var b []byte + err := json.Unmarshal([]byte(co.Value), &b) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal bytes constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Var().Id(co.Name).Op("=").Index().Byte().Op("{").ListFunc(func(byteGroup *Group) { + for _, byteVal := range b[:] { + byteGroup.Lit(int(byteVal)) + } + }).Op("}") + code.Line() + case *idltype.Pubkey: + _ = ty + // "value":"MiNTdCbWwAu3boEeTL6HzS5VgLb89mhf8VhMLtMrmWL" + pk, err := solana.PublicKeyFromBase58(co.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse pubkey constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Var().Id(co.Name).Op("=").Qual(PkgSolanaGo, "MustPublicKeyFromBase58").Call(Lit(pk.String())) + code.Line() + case *idltype.Bool: + _ = ty + // "value":"true" + v, err := strconv.ParseBool(co.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse bool constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Var().Id(co.Name).Op("=").Lit(v) + code.Line() + case *idltype.U8: + _ = ty + // "value":"42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseUint(cleanValue, 10, 8) + if err != nil { + return nil, fmt.Errorf("failed to parse u8 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(uint8(v)) + code.Line() + case *idltype.I8: + _ = ty + // "value":"-42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseInt(cleanValue, 10, 8) + if err != nil { + return nil, fmt.Errorf("failed to parse i8 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(int8(v)) + code.Line() + case *idltype.U16: + _ = ty + // "value":"42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseUint(cleanValue, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse u16 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(uint16(v)) + code.Line() + case *idltype.I16: + _ = ty + // "value":"-42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseInt(cleanValue, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse i16 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(int16(v)) + code.Line() + case *idltype.U32: + _ = ty + // "value":"42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseUint(cleanValue, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse u32 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(uint32(v)) + code.Line() + case *idltype.I32: + _ = ty + // "value":"-42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseInt(cleanValue, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse i32 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(int32(v)) + code.Line() + case *idltype.U64: + _ = ty + // "value":"42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseUint(cleanValue, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse u64 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(uint64(v)) + code.Line() + case *idltype.I64: + _ = ty + // "value":"-42" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseInt(cleanValue, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse i64 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(int64(v)) + code.Line() + case *idltype.U128: + _ = ty + // "value":"100_000_000" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + bigInt := new(big.Int) + _, ok := bigInt.SetString(cleanValue, 10) + if !ok { + return nil, fmt.Errorf("failed to parse u128 constants[%d] %s: invalid format", coi, spew.Sdump(co)) + } + // Generate code that creates a big.Int from string + code.Var().Id(co.Name).Op("=").Func().Params().Op("*").Qual("math/big", "Int").Block( + Id("val").Op(",").Id("ok").Op(":=").New(Qual("math/big", "Int")).Dot("SetString").Call(Lit(cleanValue), Lit(10)), + If(Op("!").Id("ok")).Block( + Panic(Lit(fmt.Sprintf("invalid u128 constant %s", co.Name))), + ), + Return(Id("val")), + ).Call() + code.Line() + case *idltype.I128: + _ = ty + // "value":"-100_000_000" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + bigInt := new(big.Int) + _, ok := bigInt.SetString(cleanValue, 10) + if !ok { + return nil, fmt.Errorf("failed to parse i128 constants[%d] %s: invalid format", coi, spew.Sdump(co)) + } + // Generate code that creates a big.Int from string + code.Var().Id(co.Name).Op("=").Func().Params().Op("*").Qual("math/big", "Int").Block( + Id("val").Op(",").Id("ok").Op(":=").New(Qual("math/big", "Int")).Dot("SetString").Call(Lit(cleanValue), Lit(10)), + If(Op("!").Id("ok")).Block( + Panic(Lit(fmt.Sprintf("invalid i128 constant %s", co.Name))), + ), + Return(Id("val")), + ).Call() + code.Line() + case *idltype.U256: + _ = ty + cleanValue := strings.ReplaceAll(co.Value, "_", "") + bigInt := new(big.Int) + _, ok := bigInt.SetString(cleanValue, 10) + if !ok { + return nil, fmt.Errorf("failed to parse u256 constants[%d] %s: invalid format", coi, spew.Sdump(co)) + } + code.Var().Id(co.Name).Op("=").Func().Params().Op("*").Qual("math/big", "Int").Block( + Id("val").Op(",").Id("ok").Op(":=").New(Qual("math/big", "Int")).Dot("SetString").Call(Lit(cleanValue), Lit(10)), + If(Op("!").Id("ok")).Block( + Panic(Lit(fmt.Sprintf("invalid u256 constant %s", co.Name))), + ), + Return(Id("val")), + ).Call() + code.Line() + case *idltype.I256: + _ = ty + cleanValue := strings.ReplaceAll(co.Value, "_", "") + bigInt := new(big.Int) + _, ok := bigInt.SetString(cleanValue, 10) + if !ok { + return nil, fmt.Errorf("failed to parse i256 constants[%d] %s: invalid format", coi, spew.Sdump(co)) + } + code.Var().Id(co.Name).Op("=").Func().Params().Op("*").Qual("math/big", "Int").Block( + Id("val").Op(",").Id("ok").Op(":=").New(Qual("math/big", "Int")).Dot("SetString").Call(Lit(cleanValue), Lit(10)), + If(Op("!").Id("ok")).Block( + Panic(Lit(fmt.Sprintf("invalid i256 constant %s", co.Name))), + ), + Return(Id("val")), + ).Call() + code.Line() + case *idltype.F32: + _ = ty + // "value":"3.14" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseFloat(cleanValue, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse f32 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(float32(v)) + code.Line() + case *idltype.F64: + _ = ty + // "value":"3.14" + // "value":"4e-6" + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseFloat(cleanValue, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse f64 constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(v) + code.Line() + case *idltype.Array: + _ = ty + // "type":{"array":["u8",23]},"value":"[115, 101, 110, 100, 95, 119, 105, 116, 104, 95, 115, 119, 97, 112, 95, 100, 101, 108, 101, 103, 97, 116, 101]" + var b []any + dec := json.NewDecoder(strings.NewReader(co.Value)) + dec.UseNumber() + err := dec.Decode(&b) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal array constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + size, ok := ty.Size.(*idltype.IdlArrayLenValue) + if !ok { + return nil, fmt.Errorf("expected IdlArrayLenValue for constants[%d] %s, got %T", coi, spew.Sdump(co), ty.Size) + } + if len(b) != size.Value { + return nil, fmt.Errorf("expected %d elements in array constants[%d] %s, got %d", ty.Size, coi, spew.Sdump(co), len(b)) + } + code.Var().Id(co.Name).Op("=").Index(Lit(size.Value)).Do(func(index *Statement) { + switch ty.Type.(type) { + case *idltype.U8: + index.Byte() + case *idltype.I8: + index.Int8() + case *idltype.U16: + index.Uint16() + case *idltype.I16: + index.Int16() + case *idltype.U32: + index.Uint32() + case *idltype.I32: + index.Int32() + case *idltype.U64: + index.Uint64() + case *idltype.I64: + index.Int64() + case *idltype.F32: + index.Float32() + case *idltype.F64: + index.Float64() + case *idltype.String: + index.String() + case *idltype.Bool: + index.Bool() + default: + panic(fmt.Errorf("unsupported array type for constants[%d] %s: %T", coi, spew.Sdump(co), ty.Type)) + } + }).Op("{").ListFunc(func(byteGroup *Group) { + for _, val := range b[:] { + switch ty.Type.(type) { + case *idltype.U8: + v, err := strconv.ParseUint(val.(json.Number).String(), 10, 8) + if err != nil { + panic(fmt.Errorf("failed to parse u8 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(byte(v)) + case *idltype.I8: + v, err := strconv.ParseInt(val.(json.Number).String(), 10, 8) + if err != nil { + panic(fmt.Errorf("failed to parse i8 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(int8(v)) + case *idltype.U16: + v, err := strconv.ParseUint(val.(json.Number).String(), 10, 16) + if err != nil { + panic(fmt.Errorf("failed to parse u16 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(uint16(v)) + case *idltype.I16: + v, err := strconv.ParseInt(val.(json.Number).String(), 10, 16) + if err != nil { + panic(fmt.Errorf("failed to parse i16 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(int16(v)) + case *idltype.U32: + v, err := strconv.ParseUint(val.(json.Number).String(), 10, 32) + if err != nil { + panic(fmt.Errorf("failed to parse u32 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(uint32(v)) + case *idltype.I32: + v, err := strconv.ParseInt(val.(json.Number).String(), 10, 32) + if err != nil { + panic(fmt.Errorf("failed to parse i32 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(int32(v)) + case *idltype.U64: + v, err := strconv.ParseUint(val.(json.Number).String(), 10, 64) + if err != nil { + panic(fmt.Errorf("failed to parse u64 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(uint64(v)) + case *idltype.I64: + v, err := strconv.ParseInt(val.(json.Number).String(), 10, 64) + if err != nil { + panic(fmt.Errorf("failed to parse i64 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(int64(v)) + case *idltype.F32: + v, err := strconv.ParseFloat(val.(json.Number).String(), 32) + if err != nil { + panic(fmt.Errorf("failed to parse f32 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(float32(v)) + case *idltype.F64: + v, err := strconv.ParseFloat(val.(json.Number).String(), 64) + if err != nil { + panic(fmt.Errorf("failed to parse f64 in constants[%d] %s: %w", coi, spew.Sdump(co), err)) + } + byteGroup.Lit(v) + case *idltype.String: + byteGroup.Lit(val.(string)) + case *idltype.Bool: + byteGroup.Lit(val.(bool)) + default: + panic(fmt.Errorf("unsupported array type for constants[%d] %s: %T", coi, spew.Sdump(co), ty.Type)) + } + } + }).Op("}") + code.Line() + + case *idltype.Defined: + _ = ty + // Handle user-defined types like usize, isize, etc. + switch ty.Name { + case "usize": + // usize is typically a pointer-sized unsigned integer + // In most cases, this is equivalent to u64 on 64-bit systems + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseUint(cleanValue, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse usize constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(uint64(v)) + code.Line() + case "isize": + // isize is typically a pointer-sized signed integer + // In most cases, this is equivalent to i64 on 64-bit systems + cleanValue := strings.ReplaceAll(co.Value, "_", "") + v, err := strconv.ParseInt(cleanValue, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse isize constants[%d] %s: %w", coi, spew.Sdump(co), err) + } + code.Const().Id(co.Name).Op("=").Lit(int64(v)) + code.Line() + default: + // For other defined types, we could try to resolve them, + // but for now, we'll return an error with more specific information + return nil, fmt.Errorf("unsupported defined type '%s' for constants[%d] %s: %T", ty.Name, coi, spew.Sdump(co), ty) + } + + default: + return nil, fmt.Errorf("unsupported constant type for constants[%d] %s: %T", coi, spew.Sdump(co), ty) + } + } + file.Add(code) + } + return &OutputFile{ + Name: "constants.go", + File: file, + }, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/constants_test.go b/cmd/generate-bindings/solana/anchor-go/generator/constants_test.go new file mode 100644 index 00000000..83f298cb --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/constants_test.go @@ -0,0 +1,1183 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + "strings" + "testing" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenConstants(t *testing.T) { + tests := []struct { + name string + constants []idl.IdlConst + expectError bool + expectCode []string // 期望在生成的代码中找到的字符串 + }{ + { + name: "String constant", + constants: []idl.IdlConst{ + { + Name: "TEST_STRING", + Ty: &idltype.String{}, + Value: `"hello world"`, + }, + }, + expectCode: []string{ + "const TEST_STRING = \"hello world\"", + }, + }, + { + name: "Boolean constants", + constants: []idl.IdlConst{ + { + Name: "IS_ENABLED", + Ty: &idltype.Bool{}, + Value: "true", + }, + { + Name: "IS_DISABLED", + Ty: &idltype.Bool{}, + Value: "false", + }, + }, + expectCode: []string{ + "var IS_ENABLED = true", + "var IS_DISABLED = false", + }, + }, + { + name: "Unsigned integer constants", + constants: []idl.IdlConst{ + { + Name: "MAX_U8", + Ty: &idltype.U8{}, + Value: "255", + }, + { + Name: "MAX_U16", + Ty: &idltype.U16{}, + Value: "65535", + }, + { + Name: "MAX_U32", + Ty: &idltype.U32{}, + Value: "4294967295", + }, + { + Name: "MAX_U64", + Ty: &idltype.U64{}, + Value: "18446744073709551615", + }, + }, + expectCode: []string{ + "const MAX_U8 = uint8(0xff)", + "const MAX_U16 = uint16(0xffff)", + "const MAX_U32 = uint32(0xffffffff)", + "const MAX_U64 = uint64(0xffffffffffffffff)", + }, + }, + { + name: "Signed integer constants", + constants: []idl.IdlConst{ + { + Name: "MIN_I8", + Ty: &idltype.I8{}, + Value: "-128", + }, + { + Name: "MIN_I16", + Ty: &idltype.I16{}, + Value: "-32768", + }, + { + Name: "MIN_I32", + Ty: &idltype.I32{}, + Value: "-2147483648", + }, + { + Name: "MIN_I64", + Ty: &idltype.I64{}, + Value: "-9223372036854775808", + }, + }, + expectCode: []string{ + "const MIN_I8 = int8(-128)", + "const MIN_I16 = int16(-32768)", + "const MIN_I32 = int32(-2147483648)", + "const MIN_I64 = int64(-9223372036854775808)", + }, + }, + { + name: "Float constants", + constants: []idl.IdlConst{ + { + Name: "PI_F32", + Ty: &idltype.F32{}, + Value: "3.14159", + }, + { + Name: "E_F64", + Ty: &idltype.F64{}, + Value: "2.718281828459045", + }, + }, + expectCode: []string{ + "const PI_F32 = float32(3.14159)", + "const E_F64 = 2.718281828459045", + }, + }, + { + name: "Numbers with underscores", + constants: []idl.IdlConst{ + { + Name: "LARGE_NUMBER", + Ty: &idltype.U64{}, + Value: "100_000_000", + }, + { + Name: "NEGATIVE_NUMBER", + Ty: &idltype.I32{}, + Value: "-1_000_000", + }, + }, + expectCode: []string{ + "const LARGE_NUMBER = uint64(0x5f5e100)", + "const NEGATIVE_NUMBER = int32(-1000000)", + }, + }, + { + name: "usize constant", + constants: []idl.IdlConst{ + { + Name: "MAX_BIN_PER_ARRAY", + Ty: &idltype.Defined{ + Name: "usize", + }, + Value: "70", + }, + }, + expectCode: []string{ + "const MAX_BIN_PER_ARRAY = uint64(0x46)", + }, + }, + { + name: "isize constant", + constants: []idl.IdlConst{ + { + Name: "MIN_BIN_ID", + Ty: &idltype.Defined{ + Name: "isize", + }, + Value: "-443636", + }, + }, + expectCode: []string{ + "const MIN_BIN_ID = int64(-443636)", + }, + }, + { + name: "u128 constant", + constants: []idl.IdlConst{ + { + Name: "MAX_BASE_FEE", + Ty: &idltype.U128{}, + Value: "100_000_000", + }, + }, + expectCode: []string{ + "var MAX_BASE_FEE = func() *big.Int", + ".SetString(\"100000000\", 10)", + }, + }, + { + name: "i128 constant", + constants: []idl.IdlConst{ + { + Name: "MIN_BALANCE", + Ty: &idltype.I128{}, + Value: "-1_000_000_000_000", + }, + }, + expectCode: []string{ + "var MIN_BALANCE = func() *big.Int", + ".SetString(\"-1000000000000\", 10)", + }, + }, + { + name: "Bytes constant", + constants: []idl.IdlConst{ + { + Name: "SEED_BYTES", + Ty: &idltype.Bytes{}, + Value: "[102, 101, 101, 95, 118, 97, 117, 108, 116]", + }, + }, + expectCode: []string{ + "var SEED_BYTES = []byte{102, 101, 101, 95, 118, 97, 117, 108, 116}", + }, + }, + { + name: "Pubkey constant", + constants: []idl.IdlConst{ + { + Name: "PROGRAM_ID", + Ty: &idltype.Pubkey{}, + Value: "11111111111111111111111111111112", // System Program ID + }, + }, + expectCode: []string{ + "var PROGRAM_ID = solanago.MustPublicKeyFromBase58(\"11111111111111111111111111111112\")", + }, + }, + { + name: "Empty name - should be skipped", + constants: []idl.IdlConst{ + { + Name: "", + Ty: &idltype.U8{}, + Value: "42", + }, + { + Name: "VALID_CONST", + Ty: &idltype.U8{}, + Value: "42", + }, + }, + expectCode: []string{ + "const VALID_CONST = uint8(0x2a)", + }, + }, + { + name: "Empty value - should be skipped", + constants: []idl.IdlConst{ + { + Name: "EMPTY_VALUE", + Ty: &idltype.U8{}, + Value: "", + }, + { + Name: "VALID_CONST", + Ty: &idltype.U8{}, + Value: "42", + }, + }, + expectCode: []string{ + "const VALID_CONST = uint8(0x2a)", + }, + }, + { + name: "Unsupported defined type", + constants: []idl.IdlConst{ + { + Name: "CUSTOM_TYPE", + Ty: &idltype.Defined{ + Name: "CustomType", + }, + Value: "42", + }, + }, + expectError: true, + }, + { + name: "Invalid string format", + constants: []idl.IdlConst{ + { + Name: "INVALID_STRING", + Ty: &idltype.String{}, + Value: "invalid string format", // 应该有引号 + }, + }, + expectError: true, + }, + { + name: "Invalid number format", + constants: []idl.IdlConst{ + { + Name: "INVALID_NUMBER", + Ty: &idltype.U8{}, + Value: "not_a_number", + }, + }, + expectError: true, + }, + { + name: "Invalid u128 format", + constants: []idl.IdlConst{ + { + Name: "INVALID_U128", + Ty: &idltype.U128{}, + Value: "not_a_number", + }, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // 创建一个最小的 IDL 结构 + idlData := &idl.Idl{ + Constants: tt.constants, + } + + // 创建生成器 + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{ + Package: "test", + }, + } + + // 生成常量 + outputFile, err := gen.gen_constants() + + if tt.expectError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + require.NotNil(t, outputFile) + + // 获取生成的代码 + generatedCode := outputFile.File.GoString() + + // 检查期望的代码片段是否存在 + for _, expectedCode := range tt.expectCode { + assert.Contains(t, generatedCode, expectedCode, + "Expected code snippet not found: %s\nGenerated code:\n%s", + expectedCode, generatedCode) + } + + // 基本的结构检查 + assert.Contains(t, generatedCode, "package test") + assert.Contains(t, generatedCode, "Code generated by https://github.com/gagliardetto/anchor-go") + assert.Contains(t, generatedCode, "This file contains constants") + }) + } +} + +func TestGenConstantsWithArrays(t *testing.T) { + // 测试数组常量 + constants := []idl.IdlConst{ + { + Name: "BYTE_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.U8{}, + Size: &idltype.IdlArrayLenValue{Value: 3}, + }, + Value: "[1, 2, 3]", + }, + } + + idlData := &idl.Idl{ + Constants: constants, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{ + Package: "test", + }, + } + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var BYTE_ARRAY = [3]byte{uint8(0x1), uint8(0x2), uint8(0x3)}") +} + +func TestGenConstantsWithF32Array(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "FLOAT_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.F32{}, + Size: &idltype.IdlArrayLenValue{Value: 3}, + }, + Value: "[1.5, 2.5, 3.5]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var FLOAT_ARRAY = [3]float32{float32(1.5), float32(2.5), float32(3.5)}") +} + +func TestGenConstantsWithF64Array(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "DOUBLE_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.F64{}, + Size: &idltype.IdlArrayLenValue{Value: 2}, + }, + Value: "[3.14159, 2.71828]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var DOUBLE_ARRAY = [2]float64{3.14159, 2.71828}") +} + +func TestGenConstantsWithBoolArray(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "BOOL_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.Bool{}, + Size: &idltype.IdlArrayLenValue{Value: 3}, + }, + Value: "[true, false, true]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var BOOL_ARRAY = [3]bool{true, false, true}") +} + +func TestGenConstantsWithStringArray(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "STRING_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.String{}, + Size: &idltype.IdlArrayLenValue{Value: 2}, + }, + Value: `["hello", "world"]`, + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, `var STRING_ARRAY = [2]string{"hello", "world"}`) +} + +func TestGenConstantsEdgeCases(t *testing.T) { + t.Run("No constants", func(t *testing.T) { + idlData := &idl.Idl{ + Constants: []idl.IdlConst{}, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{ + Package: "test", + }, + } + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "package test") + // 不应该包含 "Constants defined in the IDL:" 注释 + assert.NotContains(t, generatedCode, "Constants defined in the IDL:") + }) + + t.Run("Underscore cleaning", func(t *testing.T) { + // 测试下划线清理功能 + testCases := []struct { + value string + expected string + }{ + {"1_000", "1000"}, + {"1_000_000", "1000000"}, + {"1_2_3_4", "1234"}, + {"100", "100"}, // 没有下划线 + } + + for _, tc := range testCases { + constants := []idl.IdlConst{ + { + Name: "TEST_VALUE", + Ty: &idltype.U64{}, + Value: tc.value, + }, + } + + idlData := &idl.Idl{ + Constants: constants, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{ + Package: "test", + }, + } + + outputFile, err := gen.gen_constants() + require.NoError(t, err, "Failed for value: %s", tc.value) + + generatedCode := outputFile.File.GoString() + + // 验证生成的代码不包含原始的下划线值 + if strings.Contains(tc.value, "_") { + assert.NotContains(t, generatedCode, tc.value) + } + } + }) +} + +func TestGenConstantsPerformance(t *testing.T) { + // 测试大量常量的性能 + constants := make([]idl.IdlConst, 1000) + for i := 0; i < 1000; i++ { + constants[i] = idl.IdlConst{ + Name: fmt.Sprintf("CONST_%d", i), + Ty: &idltype.U32{}, + Value: fmt.Sprintf("%d", i), + } + } + + idlData := &idl.Idl{ + Constants: constants, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{ + Package: "test", + }, + } + + // 测试性能(应该在合理时间内完成) + outputFile, err := gen.gen_constants() + require.NoError(t, err) + require.NotNil(t, outputFile) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "CONST_0") + assert.Contains(t, generatedCode, "CONST_999") +} + +// TestGenConstantsSpecialCases 测试特殊情况 +func TestGenConstantsSpecialCases(t *testing.T) { + t.Run("Zero values", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "ZERO_U8", + Ty: &idltype.U8{}, + Value: "0", + }, + { + Name: "ZERO_I32", + Ty: &idltype.I32{}, + Value: "0", + }, + { + Name: "ZERO_F64", + Ty: &idltype.F64{}, + Value: "0.0", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "const ZERO_U8 = uint8(0x0)") + assert.Contains(t, generatedCode, "const ZERO_I32 = int32(0)") + assert.Contains(t, generatedCode, "const ZERO_F64 = 0") + }) + + t.Run("Maximum values", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "MAX_U8_VALUE", + Ty: &idltype.U8{}, + Value: "255", + }, + { + Name: "MAX_I8_VALUE", + Ty: &idltype.I8{}, + Value: "127", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "const MAX_U8_VALUE = uint8(0xff)") + assert.Contains(t, generatedCode, "const MAX_I8_VALUE = int8(127)") + }) + + t.Run("Complex underscores", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "COMPLEX_NUMBER", + Ty: &idltype.U64{}, + Value: "1_000_000_000_000_000_000", + }, + { + Name: "HEX_LIKE_NUMBER", + Ty: &idltype.U32{}, + Value: "0_x_F_F_F_F", // 这不是真正的十六进制,只是包含下划线的数字 + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + // 第二个应该失败,因为它不是有效的数字 + outputFile, err := gen.gen_constants() + assert.Error(t, err) // 应该失败 + _ = outputFile + }) + + t.Run("Scientific notation", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "SCIENTIFIC_F32", + Ty: &idltype.F32{}, + Value: "1.23e-4", + }, + { + Name: "SCIENTIFIC_F64", + Ty: &idltype.F64{}, + Value: "1.23456789e10", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "const SCIENTIFIC_F32 = float32(0.000123)") + assert.Contains(t, generatedCode, "const SCIENTIFIC_F64 = 1.23456789e+10") + }) + + t.Run("Empty bytes array", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "EMPTY_BYTES", + Ty: &idltype.Bytes{}, + Value: "[]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var EMPTY_BYTES = []byte{}") + }) + + t.Run("With docs", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "DOCUMENTED_CONST", + Docs: []string{"This is a test constant", "With multiple lines of documentation"}, + Ty: &idltype.U32{}, + Value: "42", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "// This is a test constant") + assert.Contains(t, generatedCode, "// With multiple lines of documentation") + assert.Contains(t, generatedCode, "const DOCUMENTED_CONST = uint32(0x2a)") + }) +} + +// TestGenConstantsErrorCases 测试各种错误情况 +func TestGenConstantsErrorCases(t *testing.T) { + t.Run("Invalid pubkey", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "INVALID_PUBKEY", + Ty: &idltype.Pubkey{}, + Value: "invalid_pubkey_format", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + _, err := gen.gen_constants() + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse pubkey") + }) + + t.Run("Invalid bytes format", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "INVALID_BYTES", + Ty: &idltype.Bytes{}, + Value: "[1, 2, invalid]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + _, err := gen.gen_constants() + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to unmarshal bytes") + }) + + t.Run("Invalid array format", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "INVALID_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.U8{}, + Size: &idltype.IdlArrayLenValue{Value: 3}, + }, + Value: "[1, 2]", // 只有2个元素,但期望3个 + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + _, err := gen.gen_constants() + assert.Error(t, err) + assert.Contains(t, err.Error(), "got 2") + }) + + t.Run("Number overflow", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "OVERFLOW_U8", + Ty: &idltype.U8{}, + Value: "256", // 超出 u8 范围 + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + _, err := gen.gen_constants() + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse u8") + }) +} + +// TestGenConstantsLargeU64I64ArrayPrecision verifies that u64 and i64 array +// elements above 2^53 are emitted with full 64-bit precision. json.Unmarshal +// into []any decodes numbers as float64, which silently rounds integers larger +// than 2^53. This test catches that: if the generator still uses float64 casts, +// the expected exact values will not appear in the generated code. +func TestGenConstantsLargeU64I64ArrayPrecision(t *testing.T) { + t.Run("u64 array with values above 2^53", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "LARGE_U64_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.U64{}, + Size: &idltype.IdlArrayLenValue{Value: 4}, + }, + // 2^53 = 9007199254740992 is the last integer float64 represents exactly. + // 2^53+1 and 2^53+3 are NOT representable in float64 and will be rounded + // to 2^53 and 2^53+4 respectively if parsed through float64. + Value: "[9007199254740993, 9007199254740995, 18446744073709551615, 9007199254740992]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + + // 2^53+1 = 0x20000000000001 — NOT exactly representable in float64 + assert.Contains(t, generatedCode, "uint64(0x20000000000001)", + "9007199254740993 (2^53+1) was rounded; float64 precision loss in u64 array element") + // 2^53+3 = 0x20000000000003 — NOT exactly representable in float64 + assert.Contains(t, generatedCode, "uint64(0x20000000000003)", + "9007199254740995 (2^53+3) was rounded; float64 precision loss in u64 array element") + // max u64 = 0xffffffffffffffff + assert.Contains(t, generatedCode, "uint64(0xffffffffffffffff)", + "18446744073709551615 (max u64) was rounded; float64 precision loss in u64 array element") + // 2^53 exactly representable — should always work + assert.Contains(t, generatedCode, "uint64(0x20000000000000)", + "9007199254740992 (2^53) should be emitted correctly") + }) + + t.Run("i64 array with values above 2^53", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "LARGE_I64_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.I64{}, + Size: &idltype.IdlArrayLenValue{Value: 4}, + }, + Value: "[9007199254740993, -9007199254740993, 9223372036854775807, -9223372036854775808]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + + // 2^53+1 positive + assert.Contains(t, generatedCode, "int64(9007199254740993)", + "9007199254740993 (2^53+1) was rounded; float64 precision loss in i64 array element") + // 2^53+1 negative + assert.Contains(t, generatedCode, "int64(-9007199254740993)", + "-9007199254740993 was rounded; float64 precision loss in i64 array element") + // max i64 + assert.Contains(t, generatedCode, "int64(9223372036854775807)", + "max i64 was rounded; float64 precision loss in i64 array element") + // min i64 + assert.Contains(t, generatedCode, "int64(-9223372036854775808)", + "min i64 was rounded; float64 precision loss in i64 array element") + }) + + t.Run("u32 array is not affected", func(t *testing.T) { + // u32 max = 4294967295 < 2^53, so float64 is fine + constants := []idl.IdlConst{ + { + Name: "U32_ARRAY", + Ty: &idltype.Array{ + Type: &idltype.U32{}, + Size: &idltype.IdlArrayLenValue{Value: 2}, + }, + Value: "[4294967295, 0]", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "uint32(0xffffffff)") + assert.Contains(t, generatedCode, "uint32(0x0)") + }) +} + +// TestGenConstantsRealWorldExamples 测试真实世界的例子 +func TestGenConstantsRealWorldExamples(t *testing.T) { + t.Run("Solana program constants", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "LAMPORTS_PER_SOL", + Ty: &idltype.U64{}, + Value: "1_000_000_000", + }, + { + Name: "SEED_PREFIX", + Ty: &idltype.String{}, + Value: `"anchor"`, + }, + { + Name: "MAX_SEED_LEN", + Ty: &idltype.U32{}, + Value: "32", + }, + { + Name: "SYSTEM_PROGRAM_ID", + Ty: &idltype.Pubkey{}, + Value: "11111111111111111111111111111112", + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "myprogram"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "package myprogram") + assert.Contains(t, generatedCode, "const LAMPORTS_PER_SOL = uint64(0x3b9aca00)") + assert.Contains(t, generatedCode, "const SEED_PREFIX = \"anchor\"") + assert.Contains(t, generatedCode, "const MAX_SEED_LEN = uint32(0x20)") + assert.Contains(t, generatedCode, "var SYSTEM_PROGRAM_ID = solanago.MustPublicKeyFromBase58") + }) + + t.Run("Mixed types with all supported features", func(t *testing.T) { + constants := []idl.IdlConst{ + { + Name: "FEATURE_ENABLED", + Docs: []string{"Feature flag for new functionality"}, + Ty: &idltype.Bool{}, + Value: "true", + }, + { + Name: "MAX_BIN_COUNT", + Docs: []string{"Maximum number of bins per array"}, + Ty: &idltype.Defined{ + Name: "usize", + }, + Value: "70", + }, + { + Name: "PROTOCOL_FEE", + Docs: []string{"Protocol fee in basis points"}, + Ty: &idltype.U128{}, + Value: "10_000_000_000_000_000_000", + }, + { + Name: "SIGNATURE_SEED", + Ty: &idltype.Array{ + Type: &idltype.U8{}, + Size: &idltype.IdlArrayLenValue{Value: 8}, + }, + Value: "[115, 105, 103, 110, 97, 116, 117, 114]", // "signatur" in ASCII + }, + } + + idlData := &idl.Idl{Constants: constants} + gen := &Generator{idl: idlData, options: &GeneratorOptions{Package: "test"}} + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + + // 检查注释 + assert.Contains(t, generatedCode, "// Feature flag for new functionality") + assert.Contains(t, generatedCode, "// Maximum number of bins per array") + assert.Contains(t, generatedCode, "// Protocol fee in basis points") + + // 检查生成的常量 + assert.Contains(t, generatedCode, "var FEATURE_ENABLED = true") + assert.Contains(t, generatedCode, "const MAX_BIN_COUNT = uint64(0x46)") + assert.Contains(t, generatedCode, "var PROTOCOL_FEE = func() *big.Int") + assert.Contains(t, generatedCode, "var SIGNATURE_SEED = [8]byte{uint8(0x73), uint8(0x69), uint8(0x67), uint8(0x6e), uint8(0x61), uint8(0x74), uint8(0x75), uint8(0x72)}") + }) +} + +func TestGenerateCodecStructMethod_SkipsEnums(t *testing.T) { + idlData := &idl.Idl{ + Types: idl.IdTypeDef_slice{ + { + Name: "MyStruct", + Ty: &idl.IdlTypeDefTyStruct{ + Fields: idl.IdlDefinedFieldsNamed{ + {Name: "value", Ty: &idltype.U64{}}, + }, + }, + }, + { + Name: "MySimpleEnum", + Ty: &idl.IdlTypeDefTyEnum{ + Variants: idl.VariantSlice{ + {Name: "VariantA"}, + {Name: "VariantB"}, + }, + }, + }, + { + Name: "AnotherStruct", + Ty: &idl.IdlTypeDefTyStruct{ + Fields: idl.IdlDefinedFieldsNamed{ + {Name: "name", Ty: &idltype.String{}}, + }, + }, + }, + }, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + methods, err := gen.generateCodecStructMethod() + require.NoError(t, err) + + require.Len(t, methods, 2, "only the two struct types should produce codec methods") + + rendered := make([]string, len(methods)) + for i, m := range methods { + rendered[i] = fmt.Sprintf("%#v", m) + } + + assert.Contains(t, rendered[0], "EncodeMyStructStruct") + assert.Contains(t, rendered[1], "EncodeAnotherStructStruct") + + for _, r := range rendered { + assert.NotContains(t, r, "MySimpleEnum", + "enum type should not appear in codec struct methods") + } +} + +func TestGenerateCodecMethods_EnumOnlyIDL(t *testing.T) { + idlData := &idl.Idl{ + Types: idl.IdTypeDef_slice{ + { + Name: "Status", + Ty: &idl.IdlTypeDefTyEnum{ + Variants: idl.VariantSlice{ + {Name: "Active"}, + {Name: "Inactive"}, + }, + }, + }, + }, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + methods, err := gen.generateCodecMethods() + require.NoError(t, err) + assert.Empty(t, methods, "codec should have no methods for an enum-only IDL") +} + +func TestGenerateCodecStructMethod_CodecInterfaceMatchesImpl(t *testing.T) { + idlData := &idl.Idl{ + Types: idl.IdTypeDef_slice{ + { + Name: "MyStruct", + Ty: &idl.IdlTypeDefTyStruct{ + Fields: idl.IdlDefinedFieldsNamed{ + {Name: "value", Ty: &idltype.U64{}}, + }, + }, + }, + { + Name: "MyEnum", + Ty: &idl.IdlTypeDefTyEnum{ + Variants: idl.VariantSlice{ + {Name: "On"}, + {Name: "Off"}, + }, + }, + }, + }, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + interfaceMethods, err := gen.generateCodecStructMethod() + require.NoError(t, err) + + interfaceMethodNames := make(map[string]bool) + for _, m := range interfaceMethods { + s := fmt.Sprintf("%#v", m) + for _, typ := range idlData.Types { + name := "Encode" + typ.Name + "Struct" + if strings.Contains(s, name) { + interfaceMethodNames[name] = true + } + } + } + + for _, typ := range idlData.Types { + name := "Encode" + typ.Name + "Struct" + if _, isStruct := typ.Ty.(*idl.IdlTypeDefTyStruct); isStruct { + assert.True(t, interfaceMethodNames[name], + "struct %q should have codec interface method %s", typ.Name, name) + + implCode := creGenerateCodecEncoderForTypes(typ.Name) + implStr := fmt.Sprintf("%#v", implCode) + assert.Contains(t, implStr, name, + "struct %q should have matching implementation", typ.Name) + } else { + assert.False(t, interfaceMethodNames[name], + "enum %q must not have codec interface method %s", typ.Name, name) + } + } +} + +func TestGenfileConstructor_WithEnumTypes(t *testing.T) { + idlData := &idl.Idl{ + Metadata: idl.IdlMetadata{ + Name: "test_program", + Version: "0.1.0", + Spec: "0.1.0", + }, + Types: idl.IdTypeDef_slice{ + { + Name: "Config", + Ty: &idl.IdlTypeDefTyStruct{ + Fields: idl.IdlDefinedFieldsNamed{ + {Name: "value", Ty: &idltype.U64{}}, + }, + }, + }, + { + Name: "Mode", + Ty: &idl.IdlTypeDefTyEnum{ + Variants: idl.VariantSlice{ + {Name: "Fast"}, + {Name: "Slow"}, + }, + }, + }, + }, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "testpkg"}, + } + + outputFile, err := gen.genfile_constructor() + require.NoError(t, err) + require.NotNil(t, outputFile) + + code := fmt.Sprintf("%#v", outputFile.File) + + assert.Contains(t, code, "EncodeConfigStruct", + "struct type Config should have a codec interface method") + assert.NotContains(t, code, "EncodeModeStruct", + "enum type Mode should not produce a codec interface method") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/cre.go b/cmd/generate-bindings/solana/anchor-go/generator/cre.go new file mode 100644 index 00000000..630722c2 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/cre.go @@ -0,0 +1,413 @@ +// This file contains all the cre specific code for the generator. +// The other files are copied from https://github.com/gagliardetto/anchor-go/blob/main/generator/ +// They simply call functions in this file. +// +//nolint:all +package generator + +import ( + "encoding/json" + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/tools" +) + +const ( + PkgCRE = "github.com/smartcontractkit/cre-sdk-go/cre" + PkgPbSdk = "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + PkgSolanaCre = "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + PkgBindings = "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/bindings" +) + +// func (c *Codec) Decode(data []byte) (*, error) { +func creDecodeAccountFn(name string) Code { + return Func(). + Params(Id("c").Op("*").Id("Codec")). + Id("Decode"+name). + Params(Id("data").Index().Byte()). + Params(Op("*").Id(name), Error()). + Block(Return(Id("ParseAccount_" + name).Call(Id("data")))) +} + +// func (c *Codec) EncodeStruct(in ) ([]byte, error) { +// return in.Marshal() +// } +func creGenerateCodecEncoderForTypes(exportedAccountName string) Code { + return Func(). + Params(Id("c").Op("*").Id("Codec")). + Id("Encode"+exportedAccountName+"Struct"). + Params(Id("in").Id(exportedAccountName)). + Params(Index().Byte(), Error()). + Block(Return(Id("in").Dot("Marshal").Call())) +} + +// if err block +// +// return cre.PromiseFromResult[*](nil, err) +// } +func creWriteReportErrorBlock() Code { + code := Empty() + code.If(Id("err").Op("!=").Nil()).Block( + Return( + Qual(PkgCRE, "PromiseFromResult").Types(Op("*").Qual(PkgSolanaCre, "WriteReportReply")).Call( + Nil(), Id("err"), + ))) + code.Line().Line() + return code +} + +func creWriteReportFromStructs(exportedAccountName string, g *Generator) Code { + code := Empty() + declarerName := newWriteReportFromInstructionFuncName(exportedAccountName) + code.Commentf("%s encodes the input struct, hashes the provided accounts,", declarerName) + code.Comment("generates a signed report, and submits it via WriteReport.") + code.Comment("") + code.Comment("remainingAccounts must follow the keystone-forwarder account layout:") + code.Comment(" - Index 0: forwarderState – the forwarder program's state account.") + code.Comment(" - Index 1: forwarderAuthority – PDA derived from seeds") + code.Comment(" [\"forwarder\", forwarderState, receiverProgram] under the forwarder program ID.") + code.Comment(" - Index 2+: receiver-specific accounts required by the target program.") + code.Comment("") + code.Comment("The full slice is hashed (via CalculateAccountsHash) into the report and forwarded") + code.Comment("as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1") + code.Comment("before CPI-ing into the receiver, so they must be present and correctly ordered.") + code.Line() + code.Func(). + Params(Id("c").Op("*").Id(tools.ToCamelUpper(g.options.Package))). // method receiver + Id(declarerName). + // params + Params( + ListMultiline(func(p *Group) { + p.Id("runtime").Qual(PkgCRE, "Runtime") + p.Id("input").Id(exportedAccountName) + p.Id("remainingAccounts").Index().Op("*").Qual(PkgSolanaCre, "AccountMeta") + p.Id("computeConfig").Op("*").Qual(PkgSolanaCre, "ComputeConfig") + }), + ). + // return type + Params(Qual(PkgCRE, "Promise").Types(Op("*").Qual(PkgSolanaCre, "WriteReportReply"))). + BlockFunc(func(block *Group) { + // encoded, err := c.Codec.EncodeStruct(input) + block.List(Id("encodedInput"), Id("err")).Op(":="). + Id("c").Dot("Codec").Dot("Encode" + exportedAccountName + "Struct").Call(Id("input")) + + // if err block + block.Add(creWriteReportErrorBlock()) + + // encodedAccountList, err := EncodeAccountList(accountList) + block.Id("encodedAccountList").Op(":="). + Qual(PkgBindings, "CalculateAccountsHash").Call(Id("remainingAccounts")).Line() + + // fwdReport := ForwarderReport{Payload: encodedInput, AccountHash: encodedAccountList} + block.Id("fwdReport").Op(":=").Qual(PkgBindings, "ForwarderReport").Values(Dict{ + Id("Payload"): Id("encodedInput"), + Id("AccountHash"): Id("encodedAccountList"), + }) + + // encodedFwdReport, err := fwdReport.Marshal() + block.List(Id("encodedFwdReport"), Id("err")).Op(":=").Id("fwdReport").Dot("Marshal").Call() + + // if err block + block.Add(creWriteReportErrorBlock()) + + // promise := runtime.GenerateReport(&pb2.ReportRequest{ ... }) + block.Id("promise").Op(":=").Id("runtime").Dot("GenerateReport").Call( + Op("&").Qual(PkgPbSdk, "ReportRequest").Values(Dict{ + Id("EncodedPayload"): Id("encodedFwdReport"), + Id("EncoderName"): Lit("solana"), + Id("SigningAlgo"): Lit("ecdsa"), + Id("HashingAlgo"): Lit("keccak256"), + }), + ).Line() + + //return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + // return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + // AccountList: typedAccountList, + // Receiver: ProgramID.Bytes(), + // Report: report, + // }) + // }) + block.Return( + Qual(PkgCRE, "ThenPromise").Call( + Id("promise"), + creWriteReportFromStructsLambda(), + ), + ) + }) + return code +} + +func creEncodeBorshVecU32() Code { + st := Empty() + st.Comment(`EncodeBorshVecU32 returns Anchor/Borsh encoding of a Vec whose elements are opaque byte payloads.`) + st.Comment(`Each [][]byte element must already be fully serialized for one Vec item on the wire.`) + st.Comment(`Layout: little-endian u32 length followed by concatenated element payloads.`) + st.Line() + st.Func(). + Id("EncodeBorshVecU32"). + Params(Id("elements").Index().Index().Byte()). + Params(Index().Byte(), Error()). + BlockFunc(func(b *Group) { + b.Id("buf").Op(":=").Qual("bytes", "NewBuffer").Call(Nil()) + b.If( + Err().Op(":=").Qual("encoding/binary", "Write").Call( + Id("buf"), + Qual("encoding/binary", "LittleEndian"), + Id("uint32").Call(Len(Id("elements"))), + ), + Err().Op("!=").Nil(), + ).Block(Return(Nil(), Err())) + b.For(Id("_").Op(",").Id("elem").Op(":=").Range().Id("elements")).Block( + List(Id("_"), Err()).Op(":=").Id("buf").Dot("Write").Call(Id("elem")), + If(Err().Op("!=").Nil()).Block( + Return(Nil(), Err()), + ), + ) + b.Return(Id("buf").Dot("Bytes").Call(), Nil()) + }) + return st +} + +// WriteReportFromBorshEncodedVec forwards a CRE report whose inner payload is EncodeBorshVecU32(elementPayloads). +func creWriteReportFromBorshEncodedVec(g *Generator) Code { + pkg := tools.ToCamelUpper(g.options.Package) + code := Empty() + code.Comment(`WriteReportFromBorshEncodedVec publishes through the CRE signer using a forwarder payload built from`) + code.Comment(`Borsh Vec semantics (EncodeBorshVecU32). Compose each elementPayload for your program (e.g. one encoded struct per row).`) + code.Comment(`Pass computeConfig = nil to use the host default Solana compute budget.`) + code.Line() + code.Func(). + Params(Id("c").Op("*").Id(pkg)). + Id("WriteReportFromBorshEncodedVec"). + Params(ListFunc(func(pl *Group) { + pl.Id("runtime").Qual(PkgCRE, "Runtime") + pl.Id("elementPayloads").Index().Index().Byte() + pl.Id("remainingAccounts").Index().Op("*").Qual(PkgSolanaCre, "AccountMeta") + pl.Id("computeConfig").Op("*").Qual(PkgSolanaCre, "ComputeConfig") + })). + Params(Qual(PkgCRE, "Promise").Types(Op("*").Qual(PkgSolanaCre, "WriteReportReply"))). + BlockFunc(func(block *Group) { + block.List(Id("payload"), Id("err")).Op(":=").Id("EncodeBorshVecU32").Call(Id("elementPayloads")) + block.Add(creWriteReportErrorBlock()) + block.Id("encodedAccountList").Op(":=").Qual(PkgBindings, "CalculateAccountsHash").Call(Id("remainingAccounts")) + block.Id("fwdReport").Op(":=").Qual(PkgBindings, "ForwarderReport").Values(Dict{ + Id("AccountHash"): Id("encodedAccountList"), + Id("Payload"): Id("payload"), + }) + block.List(Id("encodedFwdReport"), Id("err")).Op(":=").Id("fwdReport").Dot("Marshal").Call() + block.Add(creWriteReportErrorBlock()) + block.Id("promise").Op(":=").Id("runtime").Dot("GenerateReport").Call( + Op("&").Qual(PkgPbSdk, "ReportRequest").Values(Dict{ + Id("EncodedPayload"): Id("encodedFwdReport"), + Id("EncoderName"): Lit("solana"), + Id("SigningAlgo"): Lit("ecdsa"), + Id("HashingAlgo"): Lit("keccak256"), + }), + ).Line() + block.Return(Qual(PkgCRE, "ThenPromise").Call(Id("promise"), creWriteReportFromStructsLambda())) + }) + return code +} + +// creWriteReportFromStructsSlice emits: +// +// func (c *) WriteReportFroms(runtime cre.Runtime, inputs [], remainingAccounts []*solana.AccountMeta, computeConfig *solana.ComputeConfig) cre.Promise[*solana.WriteReportReply] { +// elements := make([][]byte, len(inputs)) +// for i, input := range inputs { +// encoded, err := c.Codec.EncodeStruct(input) +// if err != nil { return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) } +// elements[i] = encoded +// } +// return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +// } +func creWriteReportFromStructsSlice(exportedStructName string, g *Generator) Code { + pkg := tools.ToCamelUpper(g.options.Package) + declarerName := newWriteReportFromInstructionFuncName(exportedStructName) + "s" + return Func(). + Params(Id("c").Op("*").Id(pkg)). + Id(declarerName). + Params(ListMultiline(func(p *Group) { + p.Id("runtime").Qual(PkgCRE, "Runtime") + p.Id("inputs").Index().Id(exportedStructName) + p.Id("remainingAccounts").Index().Op("*").Qual(PkgSolanaCre, "AccountMeta") + p.Id("computeConfig").Op("*").Qual(PkgSolanaCre, "ComputeConfig") + })). + Params(Qual(PkgCRE, "Promise").Types(Op("*").Qual(PkgSolanaCre, "WriteReportReply"))). + BlockFunc(func(block *Group) { + block.Id("elements").Op(":=").Make(Index().Index().Byte(), Len(Id("inputs"))) + block.For(Id("i").Op(",").Id("input").Op(":=").Range().Id("inputs")).Block( + List(Id("encoded"), Err()).Op(":="). + Id("c").Dot("Codec").Dot("Encode"+exportedStructName+"Struct").Call(Id("input")), + If(Err().Op("!=").Nil()).Block( + Return(Qual(PkgCRE, "PromiseFromResult"). + Types(Op("*").Qual(PkgSolanaCre, "WriteReportReply")). + Call(Nil(), Err())), + ), + Id("elements").Index(Id("i")).Op("=").Id("encoded"), + ) + block.Return(Id("c").Dot("WriteReportFromBorshEncodedVec").Call( + Id("runtime"), + Id("elements"), + Id("remainingAccounts"), + Id("computeConfig"), + )) + }) +} + +func creWriteReportFromStructsLambda() *Statement { + // func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + // return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + // AccountList: typedAccountList, + // Receiver: ProgramID.Bytes(), + // Report: report, + // }) + // } + return Func(). + Params(Id("report").Op("*").Qual(PkgCRE, "Report")). + Qual(PkgCRE, "Promise").Types(Op("*").Qual(PkgSolanaCre, "WriteReportReply")). + Block( + Return( + Id("c").Dot("client").Dot("WriteReport").Call( + Id("runtime"), + Op("&").Qual(PkgSolanaCre, "WriteCreReportRequest").Values(Dict{ + Id("Receiver"): Id("ProgramID").Dot("Bytes").Call(), + Id("Report"): Id("report"), + Id("RemainingAccounts"): Id("remainingAccounts"), + Id("ComputeConfig"): Id("computeConfig"), + }), + ), + ), + ) +} + +// genfile_constructor generates the file `constructor.go`. +func (g *Generator) genfile_constructor() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains the constructor for the program.") + + { + // idl string + code := newStatement() + idlData, err := json.Marshal(g.idl) + if err != nil { + return nil, fmt.Errorf("error reading IDL file: %w", err) + } + code.Var().Id("IDL").Op("=").Lit(string(idlData)) + file.Add(code) + code.Line() + + // contract type + code = newStatement() + code.Type().Id(tools.ToCamelUpper(g.options.Package)).Struct( + Id("client").Op("*").Qual(PkgSolanaCre, "Client"), + Id("Codec").Id(tools.ToCamelUpper(g.options.Package)+"Codec"), + ) + code.Line() + file.Add(code) + code.Line() + + // codec type + code = newStatement() + code.Type().Id("Codec").Struct() + code.Line() + file.Add(code) + + // new constructor + code = newStatement() + code.Func(). + Id("New"+tools.ToCamelUpper(g.options.Package)). + Params( + Id("client").Op("*").Qual(PkgSolanaCre, "Client"), + ). + Params( + Op("*").Id(tools.ToCamelUpper(g.options.Package)), Error(), + ). + Block( + Return( + Op("&").Id(tools.ToCamelUpper(g.options.Package)).Values(Dict{ + Id("Codec"): Op("&").Id("Codec").Values(), + Id("client"): Id("client"), + }), + Nil(), + ), + ) + file.Add(code) + code.Line() + + file.Add(creEncodeBorshVecU32()) + code.Line() + file.Add(creWriteReportFromBorshEncodedVec(g)) + code.Line() + + methods, err := g.generateCodecMethods() + if err != nil { + return nil, err + } + + // Codec interface + code = newStatement() + code.Type().Id(tools.ToCamelUpper(g.options.Package) + "Codec").Interface(methods...) + file.Add(code) + code.Line() + } + + return &OutputFile{ + Name: "constructor.go", + File: file, + }, nil +} + +func (g *Generator) generateCodecAccountMethods() ([]Code, error) { + accountMethods := make([]Code, 0, len(g.idl.Accounts)) + for _, acc := range g.idl.Accounts { + exportedName := tools.ToCamelUpper(acc.Name) + methodName := "Decode" + exportedName + m := Id(methodName). + Params(Id("data").Index().Byte()). // ([]byte) + Params( + Op("*").Id(exportedName), // (*DataAccount) + Error(), // error + ) + + accountMethods = append(accountMethods, m) + } + + return accountMethods, nil +} + +func (g *Generator) generateCodecStructMethod() ([]Code, error) { + structMethods := make([]Code, 0, len(g.idl.Types)) + for _, typ := range g.idl.Types { + exportedName := tools.ToCamelUpper(typ.Name) + methodName := "Encode" + exportedName + "Struct" + if _, isEnum := typ.Ty.(*idl.IdlTypeDefTyEnum); isEnum { + continue + } + m := Id(methodName). + Params( + Id("in").Id(exportedName), // e.g., AccessLogged / DataAccount / ... + ). + Params( + Index().Byte(), // []byte + Error(), // error + ) + structMethods = append(structMethods, m) + } + return structMethods, nil +} + +func (g *Generator) generateCodecMethods() ([]Code, error) { + accountMethods, err := g.generateCodecAccountMethods() + if err != nil { + return nil, err + } + + structMethods, err := g.generateCodecStructMethod() + if err != nil { + return nil, err + } + return append(accountMethods, structMethods...), nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/discriminator.go b/cmd/generate-bindings/solana/anchor-go/generator/discriminator.go new file mode 100644 index 00000000..749d0079 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/discriminator.go @@ -0,0 +1,118 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + + . "github.com/dave/jennifer/jen" +) + +func (g *Generator) gen_discriminators() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains the discriminators for accounts and events defined in the IDL.") + + { + accountDiscriminatorsCodes := Empty() + accountDiscriminatorsCodes.Comment("Account discriminators") + accountDiscriminatorsCodes.Line() + accountDiscriminatorsCodes.Var().Parens( + DoGroup(func(code *Group) { + for _, account := range g.idl.Accounts { + if account.Discriminator == nil { + continue + } + + discriminator := account.Discriminator + if len(discriminator) != 8 { + panic(fmt.Errorf("discriminator for account %s must be exactly 8 bytes long, got %d bytes", account.Name, len(discriminator))) + } + + discriminatorName := FormatAccountDiscriminatorName(account.Name) + { + // binary.TypeID (not [8]byte) matches ReadDiscriminator's return type; mixing + // types breaks equality checks on wasm/wasip1 (runtime.memequal pointer types). + code.Id(discriminatorName).Op("=").Qual(PkgBinary, "TypeID").ValuesFunc(func(byteGroup *Group) { + for _, byteVal := range discriminator[:] { + byteGroup.Lit(int(byteVal)) + } + }) + } + code.Line() + } + }), + ) + file.Add(accountDiscriminatorsCodes) + file.Line() + } + { + // Generate the discriminators for events. + eventDiscriminatorsCodes := Empty() + eventDiscriminatorsCodes.Comment("Event discriminators") + eventDiscriminatorsCodes.Line() + eventDiscriminatorsCodes.Var().Parens( + DoGroup(func(code *Group) { + for _, event := range g.idl.Events { + if event.Discriminator == nil { + continue + } + + discriminator := event.Discriminator + if len(discriminator) != 8 { + panic(fmt.Errorf("discriminator for event %s must be exactly 8 bytes long", event.Name)) + } + + discriminatorName := FormatEventDiscriminatorName(event.Name) + { + code.Id(discriminatorName).Op("=").Qual(PkgBinary, "TypeID").ValuesFunc(func(byteGroup *Group) { + for _, byteVal := range discriminator[:] { + byteGroup.Lit(int(byteVal)) + } + }) + } + code.Line() + } + }), + ) + file.Add(eventDiscriminatorsCodes) + file.Line() + } + { + // Generate the discriminators for instructions. + instructionDiscriminatorsCodes := Empty() + instructionDiscriminatorsCodes.Comment("Instruction discriminators") + instructionDiscriminatorsCodes.Line() + instructionDiscriminatorsCodes.Var().Parens( + DoGroup( + func(code *Group) { + for _, instruction := range g.idl.Instructions { + if instruction.Discriminator == nil { + continue + } + + discriminator := instruction.Discriminator + if len(discriminator) != 8 { + panic(fmt.Errorf("discriminator for instruction %s must be exactly 8 bytes long", instruction.Name)) + } + + discriminatorName := FormatInstructionDiscriminatorName(instruction.Name) + { + code.Id(discriminatorName).Op("=").Qual(PkgBinary, "TypeID").ValuesFunc(func(byteGroup *Group) { + for _, byteVal := range discriminator[:] { + byteGroup.Lit(int(byteVal)) + } + }) + } + code.Line() + } + }, + ), + ) + file.Add(instructionDiscriminatorsCodes) + file.Line() + } + return &OutputFile{ + Name: "discriminators.go", + File: file, + }, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/doc.go b/cmd/generate-bindings/solana/anchor-go/generator/doc.go new file mode 100644 index 00000000..152d5a80 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/doc.go @@ -0,0 +1,34 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + . "github.com/dave/jennifer/jen" //nolint:staticcheck // ST1019: dot import used for code generation convenience +) + +func (g *Generator) genfile_doc() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains documentation and example usage for the generated code.") + + // TODO: + // - example usage + // - documentation + + file.Line().Line() + + if len(g.idl.Docs) == 0 { + file.Comment("No documentation available from the IDL.") + file.Comment("Please refer to the IDL source or the program documentation for more information.") + file.Line() + } else { + file.Comment("Documentation from the IDL:") + for _, comment := range g.idl.Docs { + file.Comment(comment) + } + } + + return &OutputFile{ + Name: "doc.go", + File: file, + }, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/errors.go b/cmd/generate-bindings/solana/anchor-go/generator/errors.go new file mode 100644 index 00000000..f8efd9a4 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/errors.go @@ -0,0 +1,101 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "encoding/json" + "errors" + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/solana-go/rpc/jsonrpc" +) + +func (g *Generator) gen_errors() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains errors.") + { + code := Empty() + for _, e := range g.idl.Errors { + _ = e + // spew.Dump(e) + + // type IdlErrorCode struct { + // Code uint32 `json:"code"` + // Name string `json:"name"` + // // #[serde(skip_serializing_if = "is_default")] + // // pub msg: Option, + // Msg Option[string] `json:"msg,omitzero"` + // } + } + file.Add(code) + } + return &OutputFile{ + Name: "errors.go", + File: file, + }, nil +} + +type CustomError interface { + Code() int + Name() string + Error() string +} +type customErrorDef struct { + code int + name string + msg string +} + +func (e *customErrorDef) Code() int { + return e.code +} + +func (e *customErrorDef) Name() string { + return e.name +} + +func (e *customErrorDef) Error() string { + return fmt.Sprintf("%s(%d): %s", e.name, e.code, e.msg) +} + +var Errors = map[int]CustomError{} + +func DecodeCustomError(rpcErr error) (err error, ok bool) { + if errCode, o := decodeErrorCode(rpcErr); o { + if customErr, o := Errors[errCode]; o { + err = customErr + ok = true + return + } + } + return +} + +func decodeErrorCode(rpcErr error) (errorCode int, ok bool) { + var jErr *jsonrpc.RPCError + if errors.As(rpcErr, &jErr) && jErr.Data != nil { + if root, o := jErr.Data.(map[string]any); o { + if rootErr, o := root["err"].(map[string]any); o { + if rootErrInstructionError, o := rootErr["InstructionError"]; o { + if rootErrInstructionErrorItems, o := rootErrInstructionError.([]any); o { + if len(rootErrInstructionErrorItems) == 2 { + if v, o := rootErrInstructionErrorItems[1].(map[string]any); o { + if v2, o := v["Custom"].(json.Number); o { + if code, err := v2.Int64(); err == nil { + ok = true + errorCode = int(code) + } + } else if v2, o := v["Custom"].(float64); o { + ok = true + errorCode = int(v2) + } + } + } + } + } + } + } + } + return +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/events.go b/cmd/generate-bindings/solana/anchor-go/generator/events.go new file mode 100644 index 00000000..61969234 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/events.go @@ -0,0 +1,113 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/tools" +) + +func (g *Generator) genfile_events() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains parsers for the events defined in the IDL.") + + names := []string{} + { + for _, event := range g.idl.Events { + names = append(names, tools.ToCamelUpper(event.Name)) + } + } + { + code, err := g.gen_eventParser(names) + if err != nil { + return nil, fmt.Errorf("error generating event parser: %w", err) + } + file.Add(code) + } + + return &OutputFile{ + Name: "events.go", + File: file, + }, nil +} + +func (g *Generator) gen_eventParser(eventNames []string) (Code, error) { + code := Empty() + { + code.Func().Id("ParseAnyEvent"). + Params(Id("eventData").Index().Byte()). + Params(Any(), Error()). + BlockFunc(func(block *Group) { + block.Id("decoder").Op(":=").Qual(PkgBinary, "NewBorshDecoder").Call(Id("eventData")) + block.List(Id("discriminator"), Err()).Op(":=").Id("decoder").Dot("ReadDiscriminator").Call() + + block.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to peek event discriminator: %w"), Err()), + ), + ) + + block.Switch(Id("discriminator")).BlockFunc(func(switchBlock *Group) { + for _, name := range eventNames { + switchBlock.Case(Id(FormatEventDiscriminatorName(name))).Block( + Id("value").Op(":=").New(Id(name)), + Err().Op(":=").Id("value").Dot("UnmarshalWithDecoder").Call(Id("decoder")), + If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to unmarshal event as "+name+": %w"), Err()), + ), + ), + Return(Id("value"), Nil()), + ) + } + switchBlock.Default().Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("unknown discriminator: %s"), Qual(PkgBinary, "FormatDiscriminator").Call(Id("discriminator")))), + ) + }) + }) + } + { + code.Line().Line() + // for each event, generate a function to parse it: + for _, name := range eventNames { + discriminatorName := FormatEventDiscriminatorName(name) + + code.Func().Id("ParseEvent_"+name). + Params(Id("eventData").Index().Byte()). + Params(Op("*").Id(name), Error()). + BlockFunc(func(block *Group) { + block.Id("decoder").Op(":=").Qual(PkgBinary, "NewBorshDecoder").Call(Id("eventData")) + block.List(Id("discriminator"), Err()).Op(":=").Id("decoder").Dot("ReadDiscriminator").Call() + + block.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to peek discriminator: %w"), Err()), + ), + ) + + block.If(Id("discriminator").Op("!=").Id(discriminatorName)).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("expected discriminator %v, got %s"), Id(discriminatorName), Qual(PkgBinary, "FormatDiscriminator").Call(Id("discriminator")))), + ) + + block.Id("event").Op(":=").New(Id(name)) + block.Err().Op("=").Id("event").Dot("UnmarshalWithDecoder").Call(Id("decoder")) + + block.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to unmarshal event of type "+name+": %w"), Err()), + ), + ) + + block.Return(Id("event"), Nil()) + }) + code.Line().Line() + } + } + return code, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/fetchers.go b/cmd/generate-bindings/solana/anchor-go/generator/fetchers.go new file mode 100644 index 00000000..b5007851 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/fetchers.go @@ -0,0 +1,18 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + . "github.com/dave/jennifer/jen" +) + +func (g *Generator) gen_fetchers() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains fetcher functions.") + { + } + return &OutputFile{ + Name: "fetchers.go", + File: file, + }, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/generator.go b/cmd/generate-bindings/solana/anchor-go/generator/generator.go new file mode 100644 index 00000000..b95e1bc5 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/generator.go @@ -0,0 +1,170 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/solana-go" +) + +var Debug = false // Set to true to enable debug logging. + +type Generator struct { + options *GeneratorOptions + idl *idl.Idl + complexEnumRegistry map[string]struct{} +} + +type GeneratorOptions struct { + OutputDir string // Directory to write the generated code to. + Package string // Package name for the generated code. + ModPath string // Module path for the generated code. E.g. "github.com/gagliardetto/mysolana-program-go" + ProgramId *solana.PublicKey // Program ID to use in the generated code. + ProgramName string // Name of the program for the generated code. + SkipGoMod bool // If true, skip generating the go.mod file. +} + +func NewGenerator(idl *idl.Idl, options *GeneratorOptions) *Generator { + return &Generator{ + idl: idl, + options: options, + } +} + +type OutputFile struct { + Name string // Name of the output file. + File *File +} + +type Output struct { + Files []*OutputFile // List of output files to be generated. + GoMod []byte // Go module file content. +} + +func (g *Generator) Generate() (*Output, error) { + if g.idl == nil { + return nil, fmt.Errorf("IDL is nil, cannot generate code") + } + if g.options == nil { + g.options = &GeneratorOptions{ + OutputDir: "generated", + Package: "idlclient", + ModPath: "github.com/gagliardetto/anchor-go/idlclient", + ProgramId: nil, + ProgramName: "myprogram", + } + } + if err := g.idl.Validate(); err != nil { + return nil, fmt.Errorf("invalid IDL: %w", err) + } + output := &Output{ + Files: make([]*OutputFile, 0), + } + + g.complexEnumRegistry = make(map[string]struct{}) + + { + // Register complex enums. + { + // TODO: .types is the only place where we can find complex enums? (or enums in general?) + for _, typ := range g.idl.Types { + g.registerComplexEnums(typ) + } + } + if len(g.idl.Docs) > 0 { + file, err := g.genfile_doc() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + if len(g.idl.Accounts) > 0 { + file, err := g.genfile_accounts() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + if len(g.idl.Events) > 0 { + file, err := g.genfile_events() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.genfile_constructor() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.genfile_types() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.gen_discriminators() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.gen_fetchers() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.gen_errors() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.gen_constants() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.gen_tests() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + { + file, err := g.gen_instructions() + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + if g.options.ProgramId != nil { + file, err := g.genfile_programID(*g.options.ProgramId) + if err != nil { + return nil, err + } + output.Files = append(output.Files, file) + } + if !g.options.SkipGoMod { + goMod, err := g.gen_gomod() + if err != nil { + return nil, err + } + output.GoMod = goMod + } + } + + return output, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/gomod.go b/cmd/generate-bindings/solana/anchor-go/generator/gomod.go new file mode 100644 index 00000000..6ac113f6 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/gomod.go @@ -0,0 +1,27 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "golang.org/x/mod/modfile" +) + +// gen_gomod generates a `go.mod` file for the generated code, and writes +// it to the destination directory. +func (g *Generator) gen_gomod() ([]byte, error) { + mdf := &modfile.File{} + mdf.AddModuleStmt(g.options.ModPath) + + mdf.AddNewRequire("github.com/gagliardetto/solana-go", "v1.12.0", false) + mdf.AddNewRequire("github.com/gagliardetto/anchor-go", "v0.3.2", false) + mdf.AddNewRequire("github.com/gagliardetto/binary", "v0.8.0", false) + mdf.AddNewRequire("github.com/gagliardetto/treeout", "v0.1.4", false) + mdf.AddNewRequire("github.com/gagliardetto/gofuzz", "v1.2.2", false) + mdf.AddNewRequire("github.com/stretchr/testify", "v1.10.0", false) + mdf.AddNewRequire("github.com/davecgh/go-spew", "v1.1.1", false) + + // add replacement for "github.com/gagliardetto/anchor-go/errors" to ../../demo-anchor-go/errors + // mdf.AddReplace("github.com/gagliardetto/anchor-go", "", "../../demo-anchor-go", "") + mdf.Cleanup() + + return mdf.Format() +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/id.go b/cmd/generate-bindings/solana/anchor-go/generator/id.go new file mode 100644 index 00000000..9476a497 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/id.go @@ -0,0 +1,26 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/solana-go" +) + +// TODO: +// - generate program IDs for mainnet, devnet, testnet, and localnet. + +func (g *Generator) genfile_programID(id solana.PublicKey) (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains the program ID.") + + { + idDecl := Var().Id("ProgramID").Op("=").Qual(PkgSolanaGo, "MustPublicKeyFromBase58").Call(Lit(id.String())) + file.Add(idDecl) + } + + return &OutputFile{ + Name: "program_id.go", + File: file, + }, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/idl_validate.go b/cmd/generate-bindings/solana/anchor-go/generator/idl_validate.go new file mode 100644 index 00000000..35f41e69 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/idl_validate.go @@ -0,0 +1,215 @@ +package generator + +import ( + "fmt" + "strings" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/tools" +) + +// ValidateIDLDerivedIdentifiers checks that names from the IDL produce valid Go identifiers +// after the same transforms used by the Jennifer-based generator. Call this before Generate(). +func ValidateIDLDerivedIdentifiers(i *idl.Idl) error { + if i == nil { + return fmt.Errorf("idl is nil") + } + for ai, acc := range i.Accounts { + ctx := fmt.Sprintf("accounts[%d](name=%q)", ai, acc.Name) + if err := validatePascalIdent(ctx, acc.Name); err != nil { + return err + } + disc := FormatAccountDiscriminatorName(acc.Name) + if err := validateRawIdent(ctx+".discriminatorVar", acc.Name, disc); err != nil { + return err + } + } + for ei, ev := range i.Events { + ctx := fmt.Sprintf("events[%d](name=%q)", ei, ev.Name) + if err := validatePascalIdent(ctx, ev.Name); err != nil { + return err + } + disc := FormatEventDiscriminatorName(ev.Name) + if err := validateRawIdent(ctx+".discriminatorVar", ev.Name, disc); err != nil { + return err + } + } + for ci, co := range i.Constants { + if co.Name == "" { + continue + } + ctx := fmt.Sprintf("constants[%d]", ci) + if err := validateRawIdent(ctx, co.Name, co.Name); err != nil { + return err + } + } + for ixIdx, ix := range i.Instructions { + ctx := fmt.Sprintf("instructions[%d](name=%q)", ixIdx, ix.Name) + if err := validatePascalIdent(ctx, ix.Name); err != nil { + return err + } + disc := FormatInstructionDiscriminatorName(ix.Name) + if err := validateRawIdent(ctx+".discriminatorVar", ix.Name, disc); err != nil { + return err + } + fn := newInstructionFuncName(ix.Name) + if err := validateRawIdent(ctx+".constructor", ix.Name, fn); err != nil { + return err + } + typeName := instructionStructTypeName(ix.Name) + if err := validateRawIdent(ctx+".instructionStructType", ix.Name, typeName); err != nil { + return err + } + for _, arg := range ix.Args { + argCtx := ctx + ".args(name=" + quoteIDL(arg.Name) + ")" + if err := validatePascalIdent(argCtx, arg.Name); err != nil { + return err + } + param := formatParamName(arg.Name) + if err := validateRawIdent(argCtx+".builderParam", arg.Name, param); err != nil { + return err + } + } + for ai, accItem := range ix.Accounts { + switch acc := accItem.(type) { + case *idl.IdlInstructionAccount: + acCtx := fmt.Sprintf("%s.accounts[%d](name=%q)", ctx, ai, acc.Name) + if err := validatePascalIdent(acCtx, acc.Name); err != nil { + return err + } + fieldBase := tools.ToCamelUpper(acc.Name) + if err := validateRawIdent(acCtx+".accountField", acc.Name, fieldBase); err != nil { + return err + } + if acc.Writable { + if err := validateRawIdent(acCtx+".writableFlag", acc.Name, fieldBase+"Writable"); err != nil { + return err + } + } + if acc.Signer { + if err := validateRawIdent(acCtx+".signerFlag", acc.Name, fieldBase+"Signer"); err != nil { + return err + } + } + if acc.Optional { + if err := validateRawIdent(acCtx+".optionalFlag", acc.Name, fieldBase+"Optional"); err != nil { + return err + } + } + param := formatAccountNameParam(acc.Name) + if err := validateRawIdent(acCtx+".builderParam", acc.Name, param); err != nil { + return err + } + case *idl.IdlInstructionAccounts: + return fmt.Errorf("%s.accounts[%d]: composite account groups are not supported", ctx, ai) + default: + return fmt.Errorf("%s.accounts[%d]: unknown account item type %T", ctx, ai, accItem) + } + } + } + for ti, def := range i.Types { + ctx := fmt.Sprintf("types[%d](name=%q)", ti, def.Name) + if err := validatePascalIdent(ctx, def.Name); err != nil { + return err + } + if err := validateTypeDefTy(ctx, def.Name, def.Ty); err != nil { + return err + } + } + return nil +} + +func instructionStructTypeName(instructionName string) string { + lower := strings.ToLower(instructionName) + if strings.HasSuffix(lower, "instruction") { + return tools.ToCamelUpper(instructionName) + } + return tools.ToCamelUpper(instructionName) + "Instruction" +} + +func quoteIDL(s string) string { + return fmt.Sprintf("%q", s) +} + +func validateTypeDefTy(ctx, typeName string, ty idl.IdlTypeDefTy) error { + if ty == nil { + return fmt.Errorf("%s: type definition has nil type body", ctx) + } + switch vv := ty.(type) { + case *idl.IdlTypeDefTyStruct: + fields := vv.Fields + if fields == nil { + return nil + } + switch f := fields.(type) { + case idl.IdlDefinedFieldsNamed: + for fi, field := range f { + fctx := fmt.Sprintf("%s.fields[%d](name=%q)", ctx, fi, field.Name) + if err := validatePascalIdent(fctx, field.Name); err != nil { + return err + } + } + case idl.IdlDefinedFieldsTuple: + _ = f + } + case *idl.IdlTypeDefTyEnum: + enumExported := tools.ToCamelUpper(typeName) + if vv.Variants.IsAllSimple() { + for vi, variant := range vv.Variants { + vctx := fmt.Sprintf("%s.variants[%d](name=%q)", ctx, vi, variant.Name) + if err := validatePascalIdent(vctx, variant.Name); err != nil { + return err + } + combo := formatSimpleEnumVariantName(variant.Name, enumExported) + if err := validateRawIdent(vctx+".simpleEnumConst", variant.Name, combo); err != nil { + return err + } + } + } else { + for vi, variant := range vv.Variants { + vctx := fmt.Sprintf("%s.variants[%d](name=%q)", ctx, vi, variant.Name) + if err := validatePascalIdent(vctx, variant.Name); err != nil { + return err + } + vt := formatComplexEnumVariantTypeName(enumExported, variant.Name) + if err := validateRawIdent(vctx+".complexVariantType", variant.Name, vt); err != nil { + return err + } + if !variant.Fields.IsSome() { + continue + } + switch df := variant.Fields.Unwrap().(type) { + case idl.IdlDefinedFieldsNamed: + for fi, field := range df { + fctx := fmt.Sprintf("%s.fields[%d](name=%q)", vctx, fi, field.Name) + if err := validatePascalIdent(fctx, field.Name); err != nil { + return err + } + } + case idl.IdlDefinedFieldsTuple: + } + } + } + default: + return fmt.Errorf("%s: unsupported IDL type definition shape %T", ctx, ty) + } + return nil +} + +func validatePascalIdent(context, raw string) error { + ident := tools.ToCamelUpper(raw) + return validateRawIdent(context, raw, ident) +} + +func validateRawIdent(context, idlSource, goIdent string) error { + if goIdent == "" { + return fmt.Errorf("%s: empty Go identifier derived from IDL name %q", context, idlSource) + } + if !tools.IsValidIdent(goIdent) { + return fmt.Errorf("%s: IDL name %q yields invalid Go identifier %q (must be a valid Go identifier for generated bindings)", context, idlSource, goIdent) + } + if tools.IsReservedKeyword(goIdent) { + return fmt.Errorf("%s: IDL name %q yields Go reserved keyword %q", context, idlSource, goIdent) + } + return nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/idl_validate_test.go b/cmd/generate-bindings/solana/anchor-go/generator/idl_validate_test.go new file mode 100644 index 00000000..dda3a799 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/idl_validate_test.go @@ -0,0 +1,49 @@ +package generator + +import ( + "testing" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" +) + +func testProgramID(t *testing.T) *solana.PublicKey { + t.Helper() + pk, err := solana.PublicKeyFromBase58("ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL") + require.NoError(t, err) + return &pk +} + +func minimalInstruction(name string) idl.IdlInstruction { + return idl.IdlInstruction{ + Name: name, + Discriminator: idl.IdlDiscriminator{175, 175, 109, 31, 13, 152, 155, 237}, + Accounts: []idl.IdlInstructionAccountItem{}, + Args: []idl.IdlField{}, + } +} + +func TestValidateIDLDerivedIdentifiers_valid(t *testing.T) { + i := &idl.Idl{ + Address: testProgramID(t), + Instructions: []idl.IdlInstruction{minimalInstruction("initialize")}, + } + require.NoError(t, ValidateIDLDerivedIdentifiers(i)) +} + +func TestValidateIDLDerivedIdentifiers_invalidConstantName(t *testing.T) { + i := &idl.Idl{ + Address: testProgramID(t), + Instructions: []idl.IdlInstruction{minimalInstruction("initialize")}, + Constants: []idl.IdlConst{{ + Name: "123bad", + Ty: &idltype.U8{}, + Value: "1", + }}, + } + err := ValidateIDLDerivedIdentifiers(i) + require.Error(t, err) + require.Contains(t, err.Error(), "123bad") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/instructions.go b/cmd/generate-bindings/solana/anchor-go/generator/instructions.go new file mode 100644 index 00000000..679949a0 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/instructions.go @@ -0,0 +1,797 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + "strings" + + . "github.com/dave/jennifer/jen" + "github.com/davecgh/go-spew/spew" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/tools" +) + +func (g *Generator) gen_instructions() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains instructions and instruction parsers.") + { + for _, instruction := range g.idl.Instructions { + uniqueParamNames := generateUniqueParamNames(instruction.Args) + ixCode := Empty() + { + declarerName := newInstructionFuncName(instruction.Name) + ixCode.Commentf("Builds a %q instruction.", instruction.Name) + { + if len(instruction.Docs) > 0 { + ixCode.Line() + // Add documentation comments for the instruction. + for _, doc := range instruction.Docs { + ixCode.Comment(doc) + } + } + } + ixCode.Line() + ixCode.Func().Id(declarerName). + Params( + DoGroup( + func(g *Group) { + addCommentSections := len(instruction.Args) > 0 && len(instruction.Accounts) > 0 + if addCommentSections { + g.Line().Comment("Params:") + } + g.Add( + ListMultiline( + func(paramsCode *Group) { + for _, param := range instruction.Args { + paramType := genTypeName(param.Ty) + if IsOption(param.Ty) || IsCOption(param.Ty) { + paramType = Op("*").Add(paramType) + } + paramsCode.Id(uniqueParamNames[param.Name]).Add(paramType) + } + }, + ), + ) + if addCommentSections { + g.Line().Comment("Accounts:") + } + g.Add( + ListMultiline( + func(accountsCode *Group) { + for _, account := range instruction.Accounts { + switch acc := account.(type) { + case *idl.IdlInstructionAccount: + { + accountsCode.Id(formatAccountNameParam(acc.Name)).Qual(PkgSolanaGo, "PublicKey") + } + // TODO: for accounts: + // - Optional? + // - PDA? + // - Address? + // - Relations? + case *idl.IdlInstructionAccounts: + { + panic(fmt.Errorf("Accounts groups are not supported yet: %s", acc.Name)) + // accs := acc.Accounts + // // add comment for the accounts + // if len(accs) > 0 { + // accountsCode.Commentf("Accounts group %q:", acc.Name) + // } + // for _, acc := range accs { + // // If the account has a name, use it as the parameter name. + // // Otherwise, use a generic name. + // acc := acc.(*idl.IdlInstructionAccount) + // accountName := formatAccountNameParam(acc.Name) + // accountsCode.Id(accountName).Qual(PkgSolanaGo, "PublicKey") + // } + } + default: + panic("unknown account type: " + spew.Sdump(account)) + } + } + }, + ), + ) + }, + ), + ). + ParamsFunc(func(returnsCode *Group) { + returnsCode.Qual(PkgSolanaGo, "Instruction") + returnsCode.Error() + }).BlockFunc(func(body *Group) { + body.Id("buf__").Op(":=").New(Qual("bytes", "Buffer")) + body.Id("enc__").Op(":=").Qual(PkgBinary, "NewBorshEncoder").Call(Id("buf__")) + + { + body.Line().Comment("Encode the instruction discriminator.") + discriminatorName := FormatInstructionDiscriminatorName(instruction.Name) + body.Err().Op(":=").Id("enc__").Dot("WriteBytes").Call(Id(discriminatorName).Index(Op(":")), False()) + body.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed to write instruction discriminator: %w"), Err()), + ), + ) + } + if len(instruction.Args) > 0 { + checkNil := true + body.BlockFunc(func(grp *Group) { + g.gen_marshal_DefinedFieldsNamed( + grp, + instruction.Args, + checkNil, + func(param idl.IdlField) *Statement { + return Id(uniqueParamNames[param.Name]) + }, + "enc__", + true, // returnNilErr + func(param idl.IdlField) string { + return uniqueParamNames[param.Name] + }, + ) + }) + } + body.Id("accounts__").Op(":=").Qual(PkgSolanaGo, "AccountMetaSlice").Block() + if len(instruction.Accounts) > 0 { + body.Line().Comment("Add the accounts to the instruction.") + + body.Block( + DoGroup(func(body *Group) { + for ai, account := range instruction.Accounts { + switch acc := account.(type) { + case *idl.IdlInstructionAccount: + { + if ai > 0 { + body.Line() + } + body.Comment(formatAccountCommentDocs(ai, acc)) + body.Line() + { + // add comment for the account + if len(acc.Docs) > 0 { + for _, doc := range acc.Docs { + body.Comment(doc).Line() + } + } + } + accountName := formatAccountNameParam(acc.Name) + body.Id("accounts__").Dot("Append").Call( + Qual(PkgSolanaGo, "NewAccountMeta").Call( + Id(accountName), + Lit(acc.Writable), + Lit(acc.Signer), + ), + ) + } + + case *idl.IdlInstructionAccounts: + { + panic(fmt.Errorf("Accounts groups are not supported yet: %s", acc.Name)) + // if ai > 0 { + // body.Line() + // } + // body.Commentf("Accounts group: %s", acc.Name) + // body.Line() + // accs := acc.Accounts + // for acci, acc := range accs { + // acc := acc.(*idl.IdlInstructionAccount) + // body.Comment(formatAccountCommentDocs(acci, acc)) + // body.Line() + // accountName := formatAccountNameParam(acc.Name) + // body.Id("accounts__").Dot("Append").Call( + // Qual(PkgSolanaGo, "NewAccountMeta").Call( + // Id(accountName), + // Lit(acc.Writable), + // Lit(acc.Signer), + // ), + // ) + // } + } + default: + panic("unknown account type: " + spew.Sdump(account)) + } + } + }), + ) + } + + // create the return instruction + body.Line().Comment("Create the instruction.") + body.Return( + Qual(PkgSolanaGo, "NewInstruction").CallFunc( + func(g *Group) { + g.Add( + ListMultiline(func(gg *Group) { + gg.Id("ProgramID") + gg.Id("accounts__") + gg.Id("buf__").Dot("Bytes").Call() + }), + ) + }, + ), + Nil(), // No error + ) + }) + } + file.Add(ixCode) + } + } + + // Add instruction types and parsers + { + typeNames := []string{} + discriminatorNames := []string{} + for _, instruction := range g.idl.Instructions { + // Check if the instruction name already ends with "instruction" (case-insensitive) + instructionNameLower := strings.ToLower(instruction.Name) + if strings.HasSuffix(instructionNameLower, "instruction") { + // Already has "instruction" suffix, don't add it again + typeNames = append(typeNames, tools.ToCamelUpper(instruction.Name)) + } else { + // Add "Instruction" suffix + typeNames = append(typeNames, tools.ToCamelUpper(instruction.Name)+"Instruction") + } + discriminatorNames = append(discriminatorNames, tools.ToCamelUpper(instruction.Name)) + } + + // Generate instruction struct types + { + for _, instruction := range g.idl.Instructions { + typeCode, err := g.gen_instructionType(instruction) + if err != nil { + return nil, fmt.Errorf("error generating instruction type for %s: %w", instruction.Name, err) + } + file.Add(typeCode) + } + } + + // Generate instruction parsers + { + code, err := g.gen_instructionParser(typeNames, discriminatorNames) + if err != nil { + return nil, fmt.Errorf("error generating instruction parser: %w", err) + } + file.Add(code) + } + } + + return &OutputFile{ + Name: "instructions.go", + File: file, + }, nil +} + +func formatAccountNameParam(accountName string) string { + accountName = accountName + "Account" + if tools.IsReservedKeyword(accountName) { + return accountName + "_" + } + if !tools.IsValidIdent(accountName) { + return "a_" + tools.ToCamelUpper(accountName) + } + return tools.ToCamelLower(accountName) +} + +func formatParamName(paramName string) string { + paramName = paramName + "Param" + if tools.IsReservedKeyword(paramName) { + return paramName + "_" + } + if !tools.IsValidIdent(paramName) { + return "p_" + tools.ToCamelUpper(paramName) + } + return tools.ToCamelLower(paramName) +} + +// generateUniqueParamNames creates unique Go parameter names for instruction arguments, +// mirroring generateUniqueFieldNames but using formatParamName as the base identifier +// (builder params use a different convention than struct field names). +func generateUniqueParamNames(fields []idl.IdlField) map[string]string { + fieldNameMap := make(map[string]string) + usedNames := make(map[string]int) + + for _, field := range fields { + baseName := formatParamName(field.Name) + finalName := baseName + + if count, exists := usedNames[baseName]; exists { + finalName = baseName + fmt.Sprintf("%d", count+1) + usedNames[baseName] = count + 1 + } else { + usedNames[baseName] = 0 + } + + fieldNameMap[field.Name] = finalName + } + + return fieldNameMap +} + +func newInstructionFuncName(instructionName string) string { + // Check if the instruction name already ends with "instruction" (case-insensitive) + instructionNameLower := strings.ToLower(instructionName) + if strings.HasSuffix(instructionNameLower, "instruction") { + // Already has "instruction" suffix, don't add it again + return "New" + tools.ToCamelUpper(instructionName) + } else { + // Add "Instruction" suffix + return "New" + tools.ToCamelUpper(instructionName) + "Instruction" + } +} + +func newWriteReportFromInstructionFuncName(instructionName string) string { + return "WriteReportFrom" + tools.ToCamelUpper(instructionName) +} + +func formatAccountCommentDocs(index int, account *idl.IdlInstructionAccount) string { + buf := new(strings.Builder) + buf.WriteString(fmt.Sprintf("Account %d %q", index, account.Name)) + buf.WriteString(": ") + if account.Writable { + buf.WriteString("Writable") + } else { + buf.WriteString("Read-only") + } + if account.Signer { + buf.WriteString(", Signer") + } else { + buf.WriteString(", Non-signer") + } + if account.Optional { + buf.WriteString(", Optional") + } else { + buf.WriteString(", Required") + } + if account.Address.IsSome() && !account.Address.Unwrap().IsZero() { + buf.WriteString(fmt.Sprintf(", Address: %s", account.Address.Unwrap().String())) + } + // TODO: Handle PDA and Relations + return buf.String() +} + +func (g *Generator) gen_instructionParser(typeNames []string, discriminatorNames []string) (Code, error) { + code := Empty() + + // Generate Instruction interface + code.Line().Line() + code.Comment("Instruction interface defines common methods for all instruction types") + code.Line() + code.Type().Id("Instruction").Interface( + Id("GetDiscriminator").Params().Params(Index().Byte()), + Line(), + Id("UnmarshalWithDecoder").Params(Id("decoder").Op("*").Qual(PkgBinary, "Decoder")).Params(Error()), + Line(), + Id("UnmarshalAccountIndices").Params(Id("buf").Index().Byte()).Params(Index().Uint8(), Error()), + Line(), + Id("PopulateFromAccountIndices").Params(Id("indices").Index().Uint8(), Id("accountKeys").Index().Qual(PkgSolanaGo, "PublicKey")).Params(Error()), + Line(), + Id("GetAccountKeys").Params().Params(Index().Qual(PkgSolanaGo, "PublicKey")), + ) + + // Single unified ParseInstruction function with optional accounts + code.Line().Line() + code.Comment("ParseInstruction parses instruction data and optionally populates accounts").Line() + code.Comment("If accountIndicesData is nil or empty, accounts will not be populated") + code.Line() + code.Func().Id("ParseInstruction"). + Params( + Id("instructionData").Index().Byte(), + Id("accountIndicesData").Index().Byte(), + Id("accountKeys").Index().Qual(PkgSolanaGo, "PublicKey"), + ). + Params(Id("Instruction"), Error()). + BlockFunc(func(block *Group) { + block.Comment("Validate inputs") + block.If(Len(Id("instructionData")).Op("<").Lit(8)).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("instruction data too short: expected at least 8 bytes, got %d"), Len(Id("instructionData")))), + ) + + block.Comment("Extract discriminator (TypeID for consistent equality with generated constants)") + block.Id("discriminator").Op(":=").Qual(PkgBinary, "TypeIDFromBytes").Call(Id("instructionData").Index(Lit(0), Lit(8))) + + block.Comment("Parse based on discriminator") + block.Switch(Id("discriminator")).BlockFunc(func(switchBlock *Group) { + // This for loop runs during code generation, not at runtime + for i, typeName := range typeNames { + discriminatorName := discriminatorNames[i] + switchBlock.Case(Id(FormatInstructionDiscriminatorName(discriminatorName))).Block( + Id("instruction").Op(":=").New(Id(typeName)), + Id("decoder").Op(":=").Qual(PkgBinary, "NewBorshDecoder").Call(Id("instructionData")), + Id("err").Op(":=").Id("instruction").Dot("UnmarshalWithDecoder").Call(Id("decoder")), + If(Id("err").Op("!=").Nil()).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("failed to unmarshal instruction as "+typeName+": %w"), Id("err"))), + ), + If(Id("accountIndicesData").Op("!=").Nil().Op("&&").Len(Id("accountIndicesData")).Op(">").Lit(0)).Block( + Id("indices").Op(",").Id("err").Op(":=").Id("instruction").Dot("UnmarshalAccountIndices").Call(Id("accountIndicesData")), + If(Id("err").Op("!=").Nil()).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("failed to unmarshal account indices: %w"), Id("err"))), + ), + Id("err").Op("=").Id("instruction").Dot("PopulateFromAccountIndices").Call(Id("indices"), Id("accountKeys")), + If(Id("err").Op("!=").Nil()).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("failed to populate accounts: %w"), Id("err"))), + ), + ), + Return(Id("instruction"), Nil()), + ) + } + switchBlock.Default().Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("unknown instruction discriminator: %s"), Qual(PkgBinary, "FormatDiscriminator").Call(Id("discriminator")))), + ) + }) + }) + + // Generic ParseInstructionTyped function for type-safe parsing + code.Line().Line() + code.Comment("ParseInstructionTyped parses instruction data and returns a specific instruction type") + code.Comment("T must implement the Instruction interface") + code.Line() + code.Func().Id("ParseInstructionTyped"). + Types(Id("T").Id("Instruction")). + Params( + Id("instructionData").Index().Byte(), + Id("accountIndicesData").Index().Byte(), + Id("accountKeys").Index().Qual(PkgSolanaGo, "PublicKey"), + ). + Params(Id("T"), Error()). + BlockFunc(func(block *Group) { + block.Id("instruction").Op(",").Id("err").Op(":=").Id("ParseInstruction").Call(Id("instructionData"), Id("accountIndicesData"), Id("accountKeys")) + block.If(Id("err").Op("!=").Nil()).Block( + Return(Op("*").New(Id("T")), Id("err")), + ) + block.Id("typed").Op(",").Id("ok").Op(":=").Id("instruction").Assert(Id("T")) + block.If(Op("!").Id("ok")).Block( + Return(Op("*").New(Id("T")), Qual("fmt", "Errorf").Call(Lit("instruction is not of expected type"))), + ) + block.Return(Id("typed"), Nil()) + }) + + // Convenience function for parsing without accounts + code.Line().Line() + code.Comment("ParseInstructionWithoutAccounts parses instruction data without account information") + code.Line() + code.Func().Id("ParseInstructionWithoutAccounts"). + Params(Id("instructionData").Index().Byte()). + Params(Id("Instruction"), Error()). + Block( + Return(Id("ParseInstruction").Call(Id("instructionData"), Nil(), Index().Qual(PkgSolanaGo, "PublicKey").Op("{}"))), + ) + + // Convenience function for parsing with accounts + code.Line().Line() + code.Comment("ParseInstructionWithAccounts parses instruction data with account information") + code.Line() + code.Func().Id("ParseInstructionWithAccounts"). + Params( + Id("instructionData").Index().Byte(), + Id("accountIndicesData").Index().Byte(), + Id("accountKeys").Index().Qual(PkgSolanaGo, "PublicKey"), + ). + Params(Id("Instruction"), Error()). + Block( + Return(Id("ParseInstruction").Call(Id("instructionData"), Id("accountIndicesData"), Id("accountKeys"))), + ) + + return code, nil +} + +func (g *Generator) gen_instructionType(instruction idl.IdlInstruction) (Code, error) { + code := Empty() + + uniqueArgFieldNames := generateUniqueFieldNames(instruction.Args) + + // Check if the instruction name already ends with "instruction" (case-insensitive) + instructionNameLower := strings.ToLower(instruction.Name) + var typeName string + if strings.HasSuffix(instructionNameLower, "instruction") { + // Already has "instruction" suffix, don't add it again + typeName = tools.ToCamelUpper(instruction.Name) + } else { + // Add "Instruction" suffix + typeName = tools.ToCamelUpper(instruction.Name) + "Instruction" + } + + // Generate the instruction struct type + code.Type().Id(typeName).StructFunc(func(structGroup *Group) { + // Add fields for each instruction argument + for _, arg := range instruction.Args { + fieldType := genTypeName(arg.Ty) + if IsOption(arg.Ty) || IsCOption(arg.Ty) { + fieldType = Op("*").Add(fieldType) + } + structGroup.Id(uniqueArgFieldNames[arg.Name]).Add(fieldType).Tag(map[string]string{ + "json": arg.Name, + }) + } + + // Add fields for each instruction account + if len(instruction.Accounts) > 0 { + structGroup.Line().Comment("Accounts:") + for _, account := range instruction.Accounts { + switch acc := account.(type) { + case *idl.IdlInstructionAccount: + { + // Add account field with metadata + fieldName := tools.ToCamelUpper(acc.Name) + structGroup.Id(fieldName).Qual(PkgSolanaGo, "PublicKey").Tag(map[string]string{ + "json": acc.Name, + }) + + // Add account metadata fields + if acc.Writable { + structGroup.Id(fieldName + "Writable").Bool().Tag(map[string]string{ + "json": acc.Name + "_writable", + }) + } + if acc.Signer { + structGroup.Id(fieldName + "Signer").Bool().Tag(map[string]string{ + "json": acc.Name + "_signer", + }) + } + if acc.Optional { + structGroup.Id(fieldName + "Optional").Bool().Tag(map[string]string{ + "json": acc.Name + "_optional", + }) + } + } + case *idl.IdlInstructionAccounts: + { + // Handle account groups (not fully implemented yet) + structGroup.Commentf("Account group: %s (not fully supported)", acc.Name) + } + } + } + } + }) + + // Generate GetDiscriminator method (required by Instruction interface) + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("GetDiscriminator"). + Params(). + Params(Index().Byte()). + Block( + Return(Id(FormatInstructionDiscriminatorName(tools.ToCamelUpper(instruction.Name))).Index(Op(":"))), + ) + + // Generate UnmarshalWithDecoder method + code.Line().Line() + code.Commentf("UnmarshalWithDecoder unmarshals the %s from Borsh-encoded bytes prefixed with its discriminator.", typeName).Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("UnmarshalWithDecoder"). + Params(Id("decoder").Op("*").Qual(PkgBinary, "Decoder")). + Params(Error()). + BlockFunc(func(block *Group) { + // Note: discriminator has already been read and validated by the parser + // Read instruction arguments + if len(instruction.Args) > 0 { + block.Var().Id("err").Error() + } + { + // Read the discriminator and check it against the expected value + block.Comment("Read the discriminator and check it against the expected value:") + block.List(Id("discriminator"), Err()).Op(":=").Id("decoder").Dot("ReadDiscriminator").Call() + block.If(Err().Op("!=").Nil()).Block( + Return(Qual("fmt", "Errorf").Call(Lit("failed to read instruction discriminator for %s: %w"), Lit(typeName), Err())), + ) + block.If(Id("discriminator").Op("!=").Id(FormatInstructionDiscriminatorName(tools.ToCamelUpper(instruction.Name)))).Block( + Return( + Qual("fmt", "Errorf").Call( + Lit("instruction discriminator mismatch for %s: expected %s, got %s"), + Lit(typeName), + Id(FormatInstructionDiscriminatorName(tools.ToCamelUpper(instruction.Name))), + Id("discriminator"), + ), + ), + ) + } + for _, arg := range instruction.Args { + fieldName := uniqueArgFieldNames[arg.Name] + block.Commentf("Deserialize `%s`:", fieldName) + + if IsOption(arg.Ty) || IsCOption(arg.Ty) { + var optionalityReaderName string + switch { + case IsOption(arg.Ty): + optionalityReaderName = "ReadOption" + case IsCOption(arg.Ty): + optionalityReaderName = "ReadCOption" + } + + block.BlockFunc(func(optGroup *Group) { + optGroup.List(Id("ok"), Err()).Op(":=").Id("decoder").Dot(optionalityReaderName).Call() + optGroup.If(Err().Op("!=").Nil()).Block( + Return(Err()), + ) + optGroup.If(Id("ok")).Block( + List(Err()).Op("=").Id("decoder").Dot("Decode").Call(Op("&").Id("obj").Dot(fieldName)), + If(Err().Op("!=").Nil()).Block( + Return(Err()), + ), + ) + }) + } else { + block.List(Err()).Op("=").Id("decoder").Dot("Decode").Call(Op("&").Id("obj").Dot(fieldName)) + block.If(Err().Op("!=").Nil()).Block( + Return(Err()), + ) + } + } + + // Note: Accounts are not typically serialized in instruction data + // They are passed as part of the transaction's account metas + // This method only deserializes the instruction arguments + + block.Return(Nil()) + }) + + // Generate account-related methods if instruction has accounts + if len(instruction.Accounts) > 0 { + // Generate UnmarshalAccountIndices method + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("UnmarshalAccountIndices"). + Params(Id("buf").Index().Byte()). + Params(Index().Uint8(), Error()). + BlockFunc(func(block *Group) { + block.Comment("UnmarshalAccountIndices decodes account indices from Borsh-encoded bytes") + block.Id("decoder").Op(":=").Qual(PkgBinary, "NewBorshDecoder").Call(Id("buf")) + block.Id("indices").Op(":=").Make(Index().Uint8(), Lit(0)) + block.Id("index").Op(":=").Uint8().Call(Lit(0)) + block.Var().Id("err").Error() + + for _, account := range instruction.Accounts { + switch acc := account.(type) { + case *idl.IdlInstructionAccount: + { + block.Commentf("Decode from %s account index", acc.Name) + block.Id("index").Op("=").Uint8().Call(Lit(0)) + block.List(Err()).Op("=").Id("decoder").Dot("Decode").Call(Op("&").Id("index")) + block.If(Err().Op("!=").Nil()).Block( + Return(Nil(), Qual("fmt", "Errorf").Call(Lit("failed to decode %s account index: %w"), Lit(acc.Name), Err())), + ) + block.Id("indices").Op("=").Append(Id("indices"), Id("index")) + } + case *idl.IdlInstructionAccounts: + { + block.Commentf("Account group: %s (not fully supported)", acc.Name) + } + } + } + + block.Return(Id("indices"), Nil()) + }) + + // Generate PopulateFromAccountIndices method + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("PopulateFromAccountIndices"). + Params(Id("indices").Index().Uint8(), Id("accountKeys").Index().Qual(PkgSolanaGo, "PublicKey")). + Params(Error()). + BlockFunc(func(block *Group) { + block.Comment("PopulateFromAccountIndices sets account public keys from indices and account keys array") + + // Count expected accounts + expectedAccountCount := 0 + for _, account := range instruction.Accounts { + switch account.(type) { + case *idl.IdlInstructionAccount: + expectedAccountCount++ + } + } + + block.If(Len(Id("indices")).Op("!=").Lit(expectedAccountCount)).Block( + Return(Qual("fmt", "Errorf").Call(Lit("mismatch between expected accounts (%d) and provided indices (%d)"), Lit(expectedAccountCount), Len(Id("indices")))), + ) + + block.Id("indexOffset").Op(":=").Lit(0) + + for _, account := range instruction.Accounts { + switch acc := account.(type) { + case *idl.IdlInstructionAccount: + { + fieldName := tools.ToCamelUpper(acc.Name) + block.Commentf("Set %s account from index", acc.Name) + block.If(Id("indices").Index(Id("indexOffset")).Op(">=").Uint8().Call(Len(Id("accountKeys")))).Block( + Return(Qual("fmt", "Errorf").Call(Lit("account index %d for %s is out of bounds (max: %d)"), Id("indices").Index(Id("indexOffset")), Lit(acc.Name), Len(Id("accountKeys")).Op("-").Lit(1))), + ) + block.Id("obj").Dot(fieldName).Op("=").Id("accountKeys").Index(Id("indices").Index(Id("indexOffset"))) + block.Id("indexOffset").Op("++") + } + case *idl.IdlInstructionAccounts: + { + block.Commentf("Account group: %s (not fully supported)", acc.Name) + } + } + } + + block.Return(Nil()) + }) + + // Generate GetAccountKeys method + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("GetAccountKeys"). + Params(). + Params(Index().Qual(PkgSolanaGo, "PublicKey")). + BlockFunc(func(block *Group) { + block.Id("keys").Op(":=").Make(Index().Qual(PkgSolanaGo, "PublicKey"), Lit(0)) + + for _, account := range instruction.Accounts { + switch acc := account.(type) { + case *idl.IdlInstructionAccount: + { + fieldName := tools.ToCamelUpper(acc.Name) + block.Id("keys").Op("=").Append(Id("keys"), Id("obj").Dot(fieldName)) + } + case *idl.IdlInstructionAccounts: + { + block.Commentf("Account group: %s (not fully supported)", acc.Name) + } + } + } + + block.Return(Id("keys")) + }) + } else { + // Generate empty implementations for instructions without accounts + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("UnmarshalAccountIndices"). + Params(Id("buf").Index().Byte()). + Params(Index().Uint8(), Error()). + Block( + Return(Index().Uint8().Op("{}"), Nil()), + ) + + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("PopulateFromAccountIndices"). + Params(Id("indices").Index().Uint8(), Id("accountKeys").Index().Qual(PkgSolanaGo, "PublicKey")). + Params(Error()). + Block( + Return(Nil()), + ) + + code.Line().Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("GetAccountKeys"). + Params(). + Params(Index().Qual(PkgSolanaGo, "PublicKey")). + Block( + Return(Index().Qual(PkgSolanaGo, "PublicKey").Op("{}")), + ) + } + + // Generate Unmarshal method + code.Line().Line() + code.Commentf("Unmarshal unmarshals the %s from Borsh-encoded bytes prefixed with the discriminator.", typeName).Line() + code.Func().Params(Id("obj").Op("*").Id(typeName)).Id("Unmarshal"). + Params(Id("buf").Index().Byte()). + Params(Error()). + BlockFunc(func(block *Group) { + block.Var().Id("err").Error() + block.List(Err()).Op("=").Id("obj").Dot("UnmarshalWithDecoder").Call( + Qual(PkgBinary, "NewBorshDecoder").Call(Id("buf")), + ) + block.If(Err().Op("!=").Nil()).Block( + Return( + Qual("fmt", "Errorf").Call( + Lit("error while unmarshaling "+typeName+": %w"), + Err(), + ), + ), + ) + block.Return(Nil()) + }) + + // Generate Unmarshal function + code.Line().Line() + code.Commentf("Unmarshal%s unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator.", typeName).Line() + code.Func().Id("Unmarshal"+typeName). + Params(Id("buf").Index().Byte()). + Params(Op("*").Id(typeName), Error()). + BlockFunc(func(block *Group) { + block.Id("obj").Op(":=").New(Id(typeName)) + block.Var().Id("err").Error() + block.List(Err()).Op("=").Id("obj").Dot("Unmarshal").Call(Id("buf")) + block.If(Err().Op("!=").Nil()).Block( + Return(Nil(), Err()), + ) + block.Return(Id("obj"), Nil()) + }) + + return code, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/instructions_test.go b/cmd/generate-bindings/solana/anchor-go/generator/instructions_test.go new file mode 100644 index 00000000..05e5fc74 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/instructions_test.go @@ -0,0 +1,48 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "strings" + "testing" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenInstructionsZeroArgsAlwaysWritesDiscriminator(t *testing.T) { + idlData := &idl.Idl{ + Instructions: []idl.IdlInstruction{ + { + Name: "ping", + Discriminator: idl.IdlDiscriminator{1, 2, 3, 4, 5, 6, 7, 8}, + Args: []idl.IdlField{}, + }, + }, + } + + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + outputFile, err := gen.gen_instructions() + require.NoError(t, err) + require.NotNil(t, outputFile) + + code := outputFile.File.GoString() + + assert.Contains(t, code, "NewBorshEncoder", + "zero-arg instruction builder must allocate an encoder for the discriminator") + assert.Contains(t, code, "WriteBytes", + "zero-arg instruction builder must write the discriminator bytes") + assert.NotContains(t, code, "nil, // No arguments to encode", + "zero-arg instruction must not pass nil as instruction data") + + // The generated code must reference buf__.Bytes() so the discriminator is sent. + builderIdx := strings.Index(code, "func NewPingInstruction") + require.Greater(t, builderIdx, 0, "expected to find NewPingInstruction in generated code") + builderSnippet := code[builderIdx:] + assert.Contains(t, builderSnippet, "buf__.Bytes()", + "zero-arg instruction must pass buf__.Bytes() (containing the discriminator) as instruction data") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/is.go b/cmd/generate-bindings/solana/anchor-go/generator/is.go new file mode 100644 index 00000000..46050b74 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/is.go @@ -0,0 +1,101 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import "github.com/gagliardetto/anchor-go/idl/idltype" + +func IsOption(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.Option: + return true + default: + return false + } +} + +func IsCOption(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.COption: + return true + default: + return false + } +} + +func IsDefined(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.Defined: + return true + default: + return false + } +} + +func IsVec(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.Vec: + return true + default: + return false + } +} + +func IsArray(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.Array: + return true + default: + return false + } +} + +func IsIDLTypeKind(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.Bool: + return true + case *idltype.U8: + return true + case *idltype.I8: + return true + case *idltype.U16: + return true + case *idltype.I16: + return true + case *idltype.U32: + return true + case *idltype.I32: + return true + case *idltype.F32: + return true + case *idltype.U64: + return true + case *idltype.I64: + return true + case *idltype.F64: + return true + case *idltype.U128: + return true + case *idltype.I128: + return true + case *idltype.U256: + return true + case *idltype.I256: + return true + case *idltype.Bytes: + return true + case *idltype.String: + return true + case *idltype.Pubkey: + return true + default: + return false + } +} + +func IsBool(v idltype.IdlType) bool { + switch v.(type) { + case *idltype.Bool: + return true + default: + return false + } +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/marshal.go b/cmd/generate-bindings/solana/anchor-go/generator/marshal.go new file mode 100644 index 00000000..cc024ac6 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/marshal.go @@ -0,0 +1,444 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" +) + +func (g *Generator) gen_MarshalWithEncoder_struct( + idl_ *idl.Idl, + withDiscriminator bool, + receiverTypeName string, + discriminatorName string, + fields idl.IdlDefinedFields, + checkNil bool, +) Code { + code := Empty() + { + // Declare MarshalWithEncoder + code.Func().Params(Id("obj").Id(receiverTypeName)).Id("MarshalWithEncoder"). + Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("encoder").Op("*").Qual(PkgBinary, "Encoder") + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Err().Error() + }), + ). + BlockFunc(func(body *Group) { + // Body: + if withDiscriminator && discriminatorName != "" { + body.Comment("Write account discriminator:") + body.Err().Op("=").Id("encoder").Dot("WriteBytes").Call(Id(discriminatorName).Index(Op(":")), False()) + body.If(Err().Op("!=").Nil()).Block( + Return(Err()), + ) + } + switch fields := fields.(type) { + case idl.IdlDefinedFieldsNamed: + uniqueFieldNames := generateUniqueFieldNames(fields) + g.gen_marshal_DefinedFieldsNamed( + body, + fields, + checkNil, + func(field idl.IdlField) *Statement { + return Id("obj").Dot(uniqueFieldNames[field.Name]) + }, + "encoder", + false, // returnNilErr + func(field idl.IdlField) string { + return uniqueFieldNames[field.Name] + }, + ) + case idl.IdlDefinedFieldsTuple: + convertedFields := tupleToFieldsNamed(fields) + uniqueFieldNames := generateUniqueFieldNames(convertedFields) + g.gen_marshal_DefinedFieldsNamed( + body, + convertedFields, + checkNil, + func(field idl.IdlField) *Statement { + return Id("obj").Dot(uniqueFieldNames[field.Name]) + }, + "encoder", + false, // returnNilErr + func(field idl.IdlField) string { + return uniqueFieldNames[field.Name] + }, + ) + case nil: + // No fields, just an empty struct. + // TODO: should we panic here? + default: + panic(fmt.Sprintf("unexpected fields type: %T", fields)) + } + + body.Return(Nil()) + }) + } + { + code.Line().Line() + // also generate a + // func (obj ) Marshal() ([]byte, error) { + // return obj.MarshalWithEncoder(bin.NewBorshEncoder(buf)) + // } + // func (obj ) Marshal() ([]byte, error) { + // buf := new(bytes.Buffer) + // enc := bin.NewBorshEncoder(buf) + // err := enc.Encode(meta) + // if err != nil { + // return nil, err + // } + // return buf.Bytes(), nil + // } + code.Func().Params(Id("obj").Id(receiverTypeName)).Id("Marshal"). + Params( + ListFunc(func(results *Group) { + // no parameters + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Index().Byte() + results.Error() + }), + ). + BlockFunc(func(body *Group) { + // Body: + body.Id("buf").Op(":=").Qual("bytes", "NewBuffer").Call(Nil()) + body.Id("encoder").Op(":=").Qual(PkgBinary, "NewBorshEncoder").Call(Id("buf")) + body.Err().Op(":=").Id("obj").Dot("MarshalWithEncoder").Call(Id("encoder")) + body.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call( + Lit("error while encoding "+receiverTypeName+": %w"), + Err(), + ), + ), + ) + body.Return( + Id("buf").Dot("Bytes").Call(), + Nil(), + ) + }) + } + + return code +} + +func (g *Generator) gen_marshal_DefinedFieldsNamed( + body *Group, + fields idl.IdlDefinedFieldsNamed, + checkNil bool, + nameFormatter func(field idl.IdlField) *Statement, + encoderVariableName string, + returnNilErr bool, + traceNameFormatter func(field idl.IdlField) string, +) { + for _, field := range fields { + exportedArgName := traceNameFormatter(field) + if IsOption(field.Ty) || IsCOption(field.Ty) { + body.Commentf("Serialize `%s` (optional):", exportedArgName) + } else { + body.Commentf("Serialize `%s`:", exportedArgName) + } + + if g.isComplexEnum(field.Ty) || (IsArray(field.Ty) && g.isComplexEnum(field.Ty.(*idltype.Array).Type)) || (IsVec(field.Ty) && g.isComplexEnum(field.Ty.(*idltype.Vec).Vec)) || g.isOptionalComplexEnum(field.Ty) { + switch field.Ty.(type) { + case *idltype.Defined: + enumTypeName := field.Ty.(*idltype.Defined).Name + body.BlockFunc(func(argBody *Group) { + argBody.Err().Op(":=").Id(formatEnumEncoderName(enumTypeName)).Call(Id(encoderVariableName), nameFormatter(field)) + argBody.If( + Err().Op("!=").Nil(), + ).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ) + }, + ), + ) + }) + case *idltype.Array: + enumTypeName := field.Ty.(*idltype.Array).Type.(*idltype.Defined).Name + // TODO: handle array length, which is defined in the type. + body.BlockFunc(func(argBody *Group) { + argBody.For( + Id("i").Op(":=").Lit(0), + Id("i").Op("<").Len(nameFormatter(field)), + Id("i").Op("++"), + ).BlockFunc(func(forBody *Group) { + forBody.Err().Op(":=").Id(formatEnumEncoderName(enumTypeName)).Call( + Id(encoderVariableName), + nameFormatter(field).Index(Id("i")), + ) + forBody.If( + Err().Op("!=").Nil(), + ).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual(PkgAnchorGoErrors, "NewIndex").Call( + Id("i"), + Err(), + ), + ) + }, + ), + ) + }) + }) + case *idltype.Vec: + enumTypeName := field.Ty.(*idltype.Vec).Vec.(*idltype.Defined).Name + body.BlockFunc(func(argBody *Group) { + argBody.Err().Op(":=").Id(encoderVariableName).Dot("WriteLength").Call( + Len(nameFormatter(field)), + ) + argBody.If( + Err().Op("!=").Nil(), + ).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while writing vector length: %w"), + Err(), + ), + ) + }, + ), + ) + argBody.For( + Id("i").Op(":=").Lit(0), + Id("i").Op("<").Len(nameFormatter(field)), + Id("i").Op("++"), + ).BlockFunc(func(forBody *Group) { + forBody.Err().Op(":=").Id(formatEnumEncoderName(enumTypeName)).Call( + Id(encoderVariableName), + nameFormatter(field).Index(Id("i")), + ) + forBody.If( + Err().Op("!=").Nil(), + ).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual(PkgAnchorGoErrors, "NewIndex").Call( + Id("i"), + Err(), + ), + ) + }, + ), + ) + }) + }) + case *idltype.Option: + enumTypeName := field.Ty.(*idltype.Option).Option.(*idltype.Defined).Name + gen_marshal_optionalComplexEnum(body, "WriteOption", enumTypeName, field, checkNil, nameFormatter, encoderVariableName, returnNilErr, exportedArgName) + case *idltype.COption: + enumTypeName := field.Ty.(*idltype.COption).COption.(*idltype.Defined).Name + gen_marshal_optionalComplexEnum(body, "WriteCOption", enumTypeName, field, checkNil, nameFormatter, encoderVariableName, returnNilErr, exportedArgName) + } + } else { + if IsOption(field.Ty) || IsCOption(field.Ty) { + var optionalityWriterName string + if IsOption(field.Ty) { + optionalityWriterName = "WriteOption" + } else { + optionalityWriterName = "WriteCOption" + } + if checkNil { + body.BlockFunc(func(optGroup *Group) { + // if nil: + optGroup.If(nameFormatter(field).Op("==").Nil()).Block( + Err().Op("=").Id(encoderVariableName).Dot(optionalityWriterName).Call(False()), + If(Err().Op("!=").Nil()).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewOption").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while encoding optionality: %w"), + Err(), + ), + ) + }, + ), + ), + ).Else().Block( + Err().Op("=").Id(encoderVariableName).Dot(optionalityWriterName).Call(True()), + If(Err().Op("!=").Nil()).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewOption").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while encoding optionality: %w"), + Err(), + ), + ) + }, + ), + ), + Err().Op("=").Id(encoderVariableName).Dot("Encode").Call(nameFormatter(field)), + If(Err().Op("!=").Nil()).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ) + }, + ), + ), + ) + }) + } else { + body.BlockFunc(func(optGroup *Group) { + // TODO: make optional fields of accounts a pointer. + // Write as if not nil: + optGroup.Err().Op("=").Id(encoderVariableName).Dot(optionalityWriterName).Call(True()) + optGroup.If(Err().Op("!=").Nil()).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewOption").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while encoding optionality: %w"), + Err(), + ), + ) + }, + ), + ) + optGroup.Err().Op("=").Id(encoderVariableName).Dot("Encode").Call(nameFormatter(field)) + optGroup.If(Err().Op("!=").Nil()).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ) + }, + ), + ) + }) + } + } else { + body.Err().Op("=").Id(encoderVariableName).Dot("Encode").Call(nameFormatter(field)) + body.If(Err().Op("!=").Nil()).Block( + ReturnFunc( + func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ) + }, + ), + ) + } + } + } +} + +func gen_marshal_optionalComplexEnum( + body *Group, + optionalityWriterName string, + enumTypeName string, + field idl.IdlField, + checkNil bool, + nameFormatter func(field idl.IdlField) *Statement, + encoderVariableName string, + returnNilErr bool, + exportedArgName string, +) { + errReturn := func(wrapped Code) *Statement { + return ReturnFunc(func(returnBody *Group) { + if returnNilErr { + returnBody.Nil() + } + returnBody.Add(wrapped) + }) + } + optionalityErr := func() *Statement { + return errReturn( + Qual(PkgAnchorGoErrors, "NewOption").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call(Lit("error while encoding optionality: %w"), Err()), + ), + ) + } + fieldErr := func() *Statement { + return errReturn( + Qual(PkgAnchorGoErrors, "NewField").Call(Lit(exportedArgName), Err()), + ) + } + + if checkNil { + body.BlockFunc(func(optGroup *Group) { + optGroup.If(nameFormatter(field).Op("==").Nil()).Block( + Err().Op("=").Id(encoderVariableName).Dot(optionalityWriterName).Call(False()), + If(Err().Op("!=").Nil()).Block(optionalityErr()), + ).Else().Block( + Err().Op("=").Id(encoderVariableName).Dot(optionalityWriterName).Call(True()), + If(Err().Op("!=").Nil()).Block(optionalityErr()), + Err().Op("=").Id(formatEnumEncoderName(enumTypeName)).Call(Id(encoderVariableName), nameFormatter(field)), + If(Err().Op("!=").Nil()).Block(fieldErr()), + ) + }) + } else { + body.BlockFunc(func(optGroup *Group) { + optGroup.Err().Op("=").Id(encoderVariableName).Dot(optionalityWriterName).Call(True()) + optGroup.If(Err().Op("!=").Nil()).Block(optionalityErr()) + optGroup.Err().Op("=").Id(formatEnumEncoderName(enumTypeName)).Call(Id(encoderVariableName), nameFormatter(field)) + optGroup.If(Err().Op("!=").Nil()).Block(fieldErr()) + }) + } +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/tests.go b/cmd/generate-bindings/solana/anchor-go/generator/tests.go new file mode 100644 index 00000000..64389fed --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/tests.go @@ -0,0 +1,18 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + . "github.com/dave/jennifer/jen" +) + +func (g *Generator) gen_tests() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains tests.") + { + } + return &OutputFile{ + Name: "tests_test.go", + File: file, + }, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/tools.go b/cmd/generate-bindings/solana/anchor-go/generator/tools.go new file mode 100644 index 00000000..91dd956a --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/tools.go @@ -0,0 +1,69 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "bytes" + "os" + "path" + + . "github.com/dave/jennifer/jen" +) + +const ( + PkgBinary = "github.com/gagliardetto/binary" + PkgSolanaGo = "github.com/gagliardetto/solana-go" + PkgSolanaGoText = "github.com/gagliardetto/solana-go/text" + PkgAnchorGoErrors = "github.com/gagliardetto/anchor-go/errors" + + // TODO: use or remove this: + PkgTreeout = "github.com/gagliardetto/treeout" + PkgFormat = "github.com/gagliardetto/solana-go/text/format" + PkgGoFuzz = "github.com/gagliardetto/gofuzz" + PkgTestifyRequire = "github.com/stretchr/testify/require" +) + +func WriteFile(outDir string, assetFileName string, file *File) error { + assetFilepath := path.Join(outDir, assetFileName) + var buf bytes.Buffer + if err := file.Render(&buf); err != nil { + return err + } + return os.WriteFile(assetFilepath, buf.Bytes(), 0o644) +} + +func DoGroup(f func(*Group)) *Statement { + g := &Group{} + g.CustomFunc(Options{ + Multi: false, + }, f) + s := newStatement() + *s = append(*s, g) + return s +} + +func DoGroupMultiline(f func(*Group)) *Statement { + g := &Group{} + g.CustomFunc(Options{ + Multi: true, + }, f) + s := newStatement() + *s = append(*s, g) + return s +} + +func ListMultiline(f func(*Group)) *Statement { + g := &Group{} + g.CustomFunc(Options{ + Multi: true, + Separator: ",", + Open: "", + Close: " ", + }, f) + s := newStatement() + *s = append(*s, g) + return s +} + +func newStatement() *Statement { + return &Statement{} +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/types.go b/cmd/generate-bindings/solana/anchor-go/generator/types.go new file mode 100644 index 00000000..ea39a20d --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/types.go @@ -0,0 +1,418 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/davecgh/go-spew/spew" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/tools" +) + +// genfile_types generates the file `types.go`. +func (g *Generator) genfile_types() (*OutputFile, error) { + file := NewFile(g.options.Package) + file.HeaderComment("Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.") + file.HeaderComment("This file contains parsers for the types defined in the IDL.") + + { + for index, typ := range g.idl.Types { + code, err := g.gen_IDLTypeDef(typ) + if err != nil { + return nil, fmt.Errorf("error generating type %d: %w", index, err) + } + file.Add(code) + } + } + + return &OutputFile{ + Name: "types.go", + File: file, + }, nil +} + +// `def.Type` is `IDLTypeDefTy` (which is an interface): +// either `IDLTypeDefTyEnum` or `IDLTypeDefTyStruct`. +func (g *Generator) gen_IDLTypeDef(def idl.IdlTypeDef) (Code, error) { + switch vv := def.Ty.(type) { + case *idl.IdlTypeDefTyStruct: + return g.gen_IDLTypeDefTyStruct(def.Name, def.Docs, *vv, false) + case *idl.IdlTypeDefTyEnum: + return g.gen_IDLTypeDefTyEnum(def.Name, def.Docs, *vv) + default: + panic(fmt.Errorf("unhandled type: %T", vv)) + } +} + +func (g *Generator) gen_IDLTypeDefTyEnum(name string, docs []string, typ idl.IdlTypeDefTyEnum) (Code, error) { + if typ.Variants.IsAllSimple() { + return g.gen_simpleEnum(name, docs, typ) + } + return g.gen_complexEnum(name, docs, typ) +} + +func (g *Generator) gen_simpleEnum(name string, docs []string, typ idl.IdlTypeDefTyEnum) (Code, error) { + st := newStatement() + + code := newStatement() + enumTypeName := tools.ToCamelUpper(name) + + addComments(code, docs) + { + code.Type().Id(enumTypeName).Qual(PkgBinary, "BorshEnum") + code.Line().Const().Parens(DoGroup(func(gr *Group) { + for variantIndex, variant := range typ.Variants { + // TODO: enum variants should have docs too. + // for docIndex, doc := range variant.Docs { + // if docIndex == 0 { + // gr.Line() + // } + // gr.Comment(doc).Line() + // } + + gr.Id(formatSimpleEnumVariantName(variant.Name, enumTypeName)).Add(func() Code { + if variantIndex == 0 { + return Id(enumTypeName).Op("=").Iota() + } + return nil + }()).Line() + } + // TODO: check for fields, etc. + })) + + // Generate stringer for the uint8 enum values: + code.Line().Line().Func().Params(Id("value").Id(enumTypeName)).Id("String"). + Params(). + Params(String()). + BlockFunc(func(body *Group) { + body.Switch(Id("value")).BlockFunc(func(switchBlock *Group) { + for _, variant := range typ.Variants { + switchBlock.Case(Id(formatSimpleEnumVariantName(variant.Name, enumTypeName))).Line().Return(Lit(variant.Name)) + } + switchBlock.Default().Line().Return(Lit("")) + }) + }) + st.Add(code.Line()) + } + return st, nil +} + +func addComments(code *Statement, docs []string) { + for _, doc := range docs { + code.Line() + code.Comment(doc) + } + if len(docs) > 0 { + code.Line() + } +} + +func (g *Generator) gen_complexEnum(name string, docs []string, typ idl.IdlTypeDefTyEnum) (Code, error) { + st := newStatement() + + code := newStatement() + enumTypeName := tools.ToCamelUpper(name) + + // Add comments for the enum type: + addComments(code, docs) + { + g.registerComplexEnumType(name) + containerName := formatEnumContainerName(enumTypeName) + interfaceMethodName := formatInterfaceMethodName(enumTypeName) + + // Declare the interface of the enum type: + code.Commentf("The %q interface for the %q complex enum.", interfaceMethodName, enumTypeName).Line() + code.Type().Id(enumTypeName).Interface( + Id(interfaceMethodName).Call(), + ).Line().Line() + + // Declare the enum variants container (non-exported, used internally) + code.Type().Id(containerName).StructFunc( + func(structGroup *Group) { + structGroup.Id("Enum").Qual(PkgBinary, "BorshEnum").Tag(map[string]string{ + "bin": "enum", + }) + + for _, variant := range typ.Variants { + structGroup.Id(tools.ToCamelUpper(variant.Name)).Id(formatComplexEnumVariantTypeName(enumTypeName, variant.Name)) + } + }, + ).Line().Line() + + // Declare parser function for the enum type: + code.Func().Id(formatEnumParserName(enumTypeName)).Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("decoder").Op("*").Qual(PkgBinary, "Decoder") + }), + ).Params( + ListFunc(func(results *Group) { + // Results: + results.Id(enumTypeName) + results.Error() + }), + ). + BlockFunc(func(body *Group) { + enumName := enumTypeName + body.BlockFunc(func(argBody *Group) { + argBody.List(Id("tmp")).Op(":=").New(Id(formatEnumContainerName(enumName))) + + argBody.Err().Op(":=").Id("decoder").Dot("Decode").Call(Id("tmp")) + + argBody.If( + Err().Op("!=").Nil(), + ).Block( + Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit("failed parsing "+enumTypeName+": %w"), Err()), + ), + ) + + argBody.Switch(Id("tmp").Dot("Enum")). + BlockFunc(func(switchGroup *Group) { + interfaceType := g.idl.Types.ByName(name) + if interfaceType == nil { + panic(fmt.Errorf("complex enum type %q not found in IDL types", name)) + } + + for variantIndex, variant := range interfaceType.Ty.(*idl.IdlTypeDefTyEnum).Variants { + switchGroup.Case(Lit(variantIndex)). + BlockFunc(func(caseGroup *Group) { + caseGroup.Return( + Op("&").Id("tmp").Dot(tools.ToCamelUpper(variant.Name)), + Nil(), + ) + }) + } + switchGroup.Default(). + BlockFunc(func(caseGroup *Group) { + caseGroup.Return( + Nil(), + Qual("fmt", "Errorf").Call(Lit(enumTypeName+": unknown enum index: %v"), Id("tmp").Dot("Enum")), + ) + }) + }) + }) + }).Line().Line() + + // Declare the marshaler for the enum type: + code.Func().Id(formatEnumEncoderName(enumTypeName)).Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("encoder").Op("*").Qual(PkgBinary, "Encoder") + params.Id("value").Id(enumTypeName) + }), + ).Params( + ListFunc(func(results *Group) { + // Results: + results.Error() + }), + ). + BlockFunc(func(body *Group) { + body.BlockFunc(func(argBody *Group) { + argBody.List(Id("tmp")).Op(":=").Id(formatEnumContainerName(enumTypeName)).Block() + argBody.Switch(Id("realvalue").Op(":=").Id("value").Op(".").Parens(Type())). + BlockFunc(func(switchGroup *Group) { + switchGroup.Case(Nil()). + BlockFunc(func(caseGroup *Group) { + caseGroup.Return( + Qual("fmt", "Errorf").Call(Lit(enumTypeName + ": cannot encode nil value")), + ) + }) + + interfaceType := g.idl.Types.ByName(name) + if interfaceType == nil { + panic(fmt.Errorf("complex enum type %q not found in IDL types", name)) + } + for variantIndex, variant := range interfaceType.Ty.(*idl.IdlTypeDefTyEnum).Variants { + variantTypeNameStruct := formatComplexEnumVariantTypeName(enumTypeName, variant.Name) + + switchGroup.Case(Op("*").Id(variantTypeNameStruct)). + BlockFunc(func(caseGroup *Group) { + caseGroup.If(Id("realvalue").Op("==").Nil()).Block( + Return(Qual("fmt", "Errorf").Call(Lit(enumTypeName+": cannot encode nil *"+variantTypeNameStruct))), + ) + caseGroup.Id("tmp").Dot("Enum").Op("=").Lit(variantIndex) + caseGroup.Id("tmp").Dot(tools.ToCamelUpper(variant.Name)).Op("=").Op("*").Id("realvalue") + }) + } + + switchGroup.Default(). + BlockFunc(func(caseGroup *Group) { + caseGroup.Return( + Qual("fmt", "Errorf").Call(Lit(enumTypeName+": unknown variant type %T"), Id("value")), + ) + }) + }) + + argBody.Return(Id("encoder").Dot("Encode").Call(Id("tmp"))) + }) + }).Line().Line() + + for _, variant := range typ.Variants { + // Name of the variant type if the enum is a complex enum (i.e. enum variants are inline structs): + variantTypeNameComplex := formatComplexEnumVariantTypeName(enumTypeName, variant.Name) + + // Declare the enum variant types: + if variant.IsSimple() { + code.Type().Id(variantTypeNameComplex).Qual(PkgBinary, "EmptyVariant").Line().Line() + } else if variant.Fields.IsSome() { + code.Commentf("Variant %q of enum %q", variant.Name, enumTypeName).Line() + code.Type().Id(variantTypeNameComplex).StructFunc( + func(structGroup *Group) { + switch fields := variant.Fields.Unwrap().(type) { + case idl.IdlDefinedFieldsNamed: + for _, variantField := range fields { + optionality := IsOption(variantField.Ty) || IsCOption(variantField.Ty) + structGroup.Add(g.genField(variantField, optionality)). + Add(func() Code { + tagMap := map[string]string{} + if IsOption(variantField.Ty) { + tagMap["bin"] = "optional" + } + if IsCOption(variantField.Ty) { + tagMap["bin"] = "coption" + } + // add json tag: + tagMap["json"] = tools.ToCamelLower(variantField.Name) + func() string { + if optionality { + return ",omitempty" + } + return "" + }() + return Tag(tagMap) + }()) + } + case idl.IdlDefinedFieldsTuple: + for itemIndex, tupleItem := range fields { + optionality := IsOption(tupleItem) || IsCOption(tupleItem) + tupleItemName := FormatTupleItemName(itemIndex) + structGroup.Add(g.genFieldNamed(tupleItemName, tupleItem, optionality)). + Add(func() Code { + tagMap := map[string]string{} + if IsOption(tupleItem) { + tagMap["bin"] = "optional" + } + if IsCOption(tupleItem) { + tagMap["bin"] = "coption" + } + // add json tag: + tagMap["json"] = tools.ToCamelLower(tupleItemName) + func() string { + if optionality { + return ",omitempty" + } + return "" + }() + return Tag(tagMap) + }()) + } + default: + panic("not handled: " + spew.Sdump(variant.Fields)) + } + }, + ).Line().Line() + } + + if variant.IsSimple() { + // Declare MarshalWithEncoder + code.Line().Line().Func().Params(Id("obj").Id(variantTypeNameComplex)).Id("MarshalWithEncoder"). + Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("encoder").Op("*").Qual(PkgBinary, "Encoder") + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Err().Error() + }), + ). + BlockFunc(func(body *Group) { + body.Return(Nil()) + }) + code.Line().Line() + + // Declare UnmarshalWithDecoder + code.Func().Params(Id("obj").Op("*").Id(variantTypeNameComplex)).Id("UnmarshalWithDecoder"). + Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("decoder").Op("*").Qual(PkgBinary, "Decoder") + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Err().Error() + }), + ). + BlockFunc(func(body *Group) { + body.Return(Nil()) + }) + code.Line().Line() + } else if variant.Fields.IsSome() { + switch fields := variant.Fields.Unwrap().(type) { + case idl.IdlDefinedFieldsNamed: + // Declare MarshalWithEncoder: + code.Line().Line().Add( + g.gen_MarshalWithEncoder_struct( + g.idl, + false, + variantTypeNameComplex, + "", + fields, + true, + )) + + // Declare UnmarshalWithDecoder + code.Line().Line().Add( + g.gen_UnmarshalWithDecoder_struct( + g.idl, + false, + variantTypeNameComplex, + "", + fields, + )) + code.Line().Line() + case idl.IdlDefinedFieldsTuple: + // TODO: handle tuples + // Declare MarshalWithEncoder: + code.Line().Line().Add( + g.gen_MarshalWithEncoder_struct( + g.idl, + false, + variantTypeNameComplex, + "", + fields, + true, + )) + + // Declare UnmarshalWithDecoder + code.Line().Line().Add( + g.gen_UnmarshalWithDecoder_struct( + g.idl, + false, + variantTypeNameComplex, + "", + fields, + )) + code.Line().Line() + default: + panic("not handled: " + spew.Sdump(variant.Fields)) + } + } + + // Declare the method to implement the parent enum interface: + if variant.IsSimple() { + code.Func().Params(Id("_").Op("*").Id(variantTypeNameComplex)).Id(interfaceMethodName).Params().Block().Line().Line() + } else { + code.Func().Params(Id("_").Op("*").Id(variantTypeNameComplex)).Id(interfaceMethodName).Params().Block().Line().Line() + } + } + + st.Add(code.Line().Line()) + } + return st, nil +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/types_test.go b/cmd/generate-bindings/solana/anchor-go/generator/types_test.go new file mode 100644 index 00000000..72712eb8 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/types_test.go @@ -0,0 +1,103 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "testing" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeComplexEnumIDL(enumName string) *idl.Idl { + enumType := &idl.IdlTypeDefTyEnum{ + Kind: "enum", + Variants: idl.VariantSlice{ + {Name: "Simple"}, + { + Name: "WithFields", + Fields: idl.Some[idl.IdlDefinedFields](idl.IdlDefinedFieldsNamed{ + {Name: "value", Ty: &idltype.U64{}}, + }), + }, + }, + } + + return &idl.Idl{ + Types: idl.IdTypeDef_slice{ + { + Name: enumName, + Ty: enumType, + }, + }, + } +} + +func TestGenComplexEnum_ConsecutiveUppercase(t *testing.T) { + // "HTTPStatus" is stored in the IDL as-is. ToCamelUpper converts it to + // "HttpStatus" (via snake_case intermediary), so ByName("HttpStatus") + // won't find the original "HTTPStatus" entry and returns nil. + idlData := makeComplexEnumIDL("HTTPStatus") + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + complexEnumRegistry: make(map[string]struct{}), + } + + // Register the complex enum as the generator normally would. + for _, typ := range gen.idl.Types { + gen.registerComplexEnums(typ) + } + + outputFile, err := gen.genfile_types() + require.NoError(t, err, "genfile_types should not panic or error for enum named HTTPStatus") + require.NotNil(t, outputFile) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "HttpStatus") +} + +func TestGenComplexEnum_SnakeCaseName(t *testing.T) { + // "my_status" is stored in the IDL. ToCamelUpper converts it to + // "MyStatus", so ByName("MyStatus") won't find "my_status". + idlData := makeComplexEnumIDL("my_status") + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + complexEnumRegistry: make(map[string]struct{}), + } + + for _, typ := range gen.idl.Types { + gen.registerComplexEnums(typ) + } + + outputFile, err := gen.genfile_types() + require.NoError(t, err, "genfile_types should not panic or error for enum named my_status") + require.NotNil(t, outputFile) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "MyStatus") +} + +func TestGenComplexEnum_AlreadyCamelCase(t *testing.T) { + // "MyStatus" is already CamelCase. ToCamelUpper("MyStatus") == "MyStatus", + // so ByName should find it. This should always work. + idlData := makeComplexEnumIDL("MyStatus") + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + complexEnumRegistry: make(map[string]struct{}), + } + + for _, typ := range gen.idl.Types { + gen.registerComplexEnums(typ) + } + + outputFile, err := gen.genfile_types() + require.NoError(t, err, "genfile_types should not panic or error for enum named MyStatus") + require.NotNil(t, outputFile) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "MyStatus") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/u256_test.go b/cmd/generate-bindings/solana/anchor-go/generator/u256_test.go new file mode 100644 index 00000000..13fb77ae --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/u256_test.go @@ -0,0 +1,148 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "testing" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIDLTypeKind_ToTypeDeclCode_U256(t *testing.T) { + assert.NotPanics(t, func() { + result := IDLTypeKind_ToTypeDeclCode(&idltype.U256{}) + assert.NotNil(t, result) + }) +} + +func TestIDLTypeKind_ToTypeDeclCode_I256(t *testing.T) { + assert.NotPanics(t, func() { + result := IDLTypeKind_ToTypeDeclCode(&idltype.I256{}) + assert.NotNil(t, result) + }) +} + +func TestGenTypeName_U256(t *testing.T) { + assert.NotPanics(t, func() { + result := genTypeName(&idltype.U256{}) + assert.NotNil(t, result) + }) +} + +func TestGenTypeName_I256(t *testing.T) { + assert.NotPanics(t, func() { + result := genTypeName(&idltype.I256{}) + assert.NotNil(t, result) + }) +} + +func TestGenConstants_U256(t *testing.T) { + idlData := &idl.Idl{ + Constants: []idl.IdlConst{ + { + Name: "MAX_SUPPLY", + Ty: &idltype.U256{}, + Value: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + }, + } + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var MAX_SUPPLY = func() *big.Int") + assert.Contains(t, generatedCode, ".SetString(\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 10)") +} + +func TestGenConstants_I256(t *testing.T) { + idlData := &idl.Idl{ + Constants: []idl.IdlConst{ + { + Name: "MIN_VALUE", + Ty: &idltype.I256{}, + Value: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + }, + }, + } + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var MIN_VALUE = func() *big.Int") + assert.Contains(t, generatedCode, ".SetString(\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 10)") +} + +func TestGenConstants_U256_Invalid(t *testing.T) { + idlData := &idl.Idl{ + Constants: []idl.IdlConst{ + { + Name: "INVALID_U256", + Ty: &idltype.U256{}, + Value: "not_a_number", + }, + }, + } + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + _, err := gen.gen_constants() + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse u256") +} + +func TestGenConstants_I256_Invalid(t *testing.T) { + idlData := &idl.Idl{ + Constants: []idl.IdlConst{ + { + Name: "INVALID_I256", + Ty: &idltype.I256{}, + Value: "not_a_number", + }, + }, + } + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + _, err := gen.gen_constants() + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse i256") +} + +func TestGenConstants_U256_WithUnderscores(t *testing.T) { + idlData := &idl.Idl{ + Constants: []idl.IdlConst{ + { + Name: "LARGE_U256", + Ty: &idltype.U256{}, + Value: "1_000_000_000_000_000_000_000_000_000", + }, + }, + } + gen := &Generator{ + idl: idlData, + options: &GeneratorOptions{Package: "test"}, + } + + outputFile, err := gen.gen_constants() + require.NoError(t, err) + + generatedCode := outputFile.File.GoString() + assert.Contains(t, generatedCode, "var LARGE_U256 = func() *big.Int") + assert.Contains(t, generatedCode, ".SetString(\"1000000000000000000000000000\", 10)") +} diff --git a/cmd/generate-bindings/solana/anchor-go/generator/unmarshal.go b/cmd/generate-bindings/solana/anchor-go/generator/unmarshal.go new file mode 100644 index 00000000..a0f45fb1 --- /dev/null +++ b/cmd/generate-bindings/solana/anchor-go/generator/unmarshal.go @@ -0,0 +1,432 @@ +//nolint:all // Forked from anchor-go generator, maintaining original code structure +package generator + +import ( + "fmt" + + . "github.com/dave/jennifer/jen" + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/idl/idltype" + "github.com/gagliardetto/anchor-go/tools" +) + +func formatComplexEnumVariantTypeName(enumTypeName string, variantName string) string { + return fmt.Sprintf("%s_%s", tools.ToCamelUpper(enumTypeName), tools.ToCamelUpper(variantName)) +} + +func formatSimpleEnumVariantName(variantName string, enumTypeName string) string { + return fmt.Sprintf("%s_%s", tools.ToCamelUpper(enumTypeName), tools.ToCamelUpper(variantName)) +} + +func FormatTupleItemName(index int) string { + return tools.ToCamelUpper(fmt.Sprintf("V%d", index)) +} + +func formatEnumContainerName(enumTypeName string) string { + return tools.ToCamelLower(enumTypeName) + "EnumContainer" +} + +func formatInterfaceMethodName(enumTypeName string) string { + return "is" + tools.ToCamelUpper(enumTypeName) +} + +func formatDiscriminatorName(kind string, exportedAccountName string) string { + // trim prefix or suffix "Account" or "Event" from exportedAccountName + exportedAccountName = tools.ToCamelUpper(exportedAccountName) + + // // TODO: sometimes there's accounts/events like this: + // // - "Fund" + // // - "FundAccount" + // // This will create a name collision and fail to compile because + // // we remove the "Account" or "Event" suffix from the second one, + // // so there's a duplicate name "Fund". + // exportedAccountName = strings.TrimSuffix(exportedAccountName, "Account") + // exportedAccountName = strings.TrimSuffix(exportedAccountName, "Event") + // exportedAccountName = strings.TrimPrefix(exportedAccountName, "Account") + // exportedAccountName = strings.TrimPrefix(exportedAccountName, "Event") + + return kind + "_" + tools.ToCamelUpper(exportedAccountName) +} + +func FormatAccountDiscriminatorName(exportedAccountName string) string { + return formatDiscriminatorName("Account", exportedAccountName) +} + +func FormatEventDiscriminatorName(exportedEventName string) string { + return formatDiscriminatorName("Event", exportedEventName) +} + +func FormatInstructionDiscriminatorName(exportedInstructionName string) string { + return formatDiscriminatorName("Instruction", exportedInstructionName) +} + +func formatBuilderFuncName(insExportedName string) string { + return "New" + insExportedName + "InstructionBuilder" +} + +func formatEnumParserName(enumTypeName string) string { + return "Decode" + enumTypeName +} + +func formatEnumEncoderName(enumTypeName string) string { + return "Encode" + enumTypeName +} + +func (g *Generator) gen_UnmarshalWithDecoder_struct( + idl_ *idl.Idl, + withDiscriminator bool, + receiverTypeName string, + discriminatorName string, + fields idl.IdlDefinedFields, +) Code { + code := Empty() + { + // Declare UnmarshalWithDecoder + code.Func().Params(Id("obj").Op("*").Id(receiverTypeName)).Id("UnmarshalWithDecoder"). + Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("decoder").Op("*").Qual(PkgBinary, "Decoder") + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Err().Error() + }), + ). + BlockFunc(func(body *Group) { + // Body: + if withDiscriminator && discriminatorName != "" { + body.Comment("Read and check account discriminator:") + body.BlockFunc(func(discReadBody *Group) { + discReadBody.List(Id("discriminator"), Err()).Op(":=").Id("decoder").Dot("ReadDiscriminator").Call() + discReadBody.If(Err().Op("!=").Nil()).Block( + Return(Err()), + ) + discReadBody.If(Op("!").Id("discriminator").Dot("Equal").Call(Id(discriminatorName).Index(Op(":")))).Block( + Return( + Qual("fmt", "Errorf").Call( + Line().Lit("wrong discriminator: wanted %s, got %s"), + Line().Id(discriminatorName).Index(Op(":")), + Line().Qual("fmt", "Sprint").Call(Id("discriminator").Index(Op(":"))), + ), + ), + ) + }) + } + + switch fields := fields.(type) { + case idl.IdlDefinedFieldsNamed: + g.gen_unmarshal_DefinedFieldsNamed(body, fields, generateUniqueFieldNames(fields)) + case idl.IdlDefinedFieldsTuple: + convertedFields := tupleToFieldsNamed(fields) + g.gen_unmarshal_DefinedFieldsNamed(body, convertedFields, generateUniqueFieldNames(convertedFields)) + case nil: + // No fields, just an empty struct. + // TODO: should we panic here? + default: + panic(fmt.Sprintf("unexpected fields type: %T", fields)) + } + + body.Return(Nil()) + }) + } + { + code.Line().Line() + // func (obj *) Unmarshal(buf []byte) (err error) { + // return obj.UnmarshalWithDecoder(bin.NewBorshDecoder(buf)) + // } + code.Func().Params(Id("obj").Op("*").Id(receiverTypeName)).Id("Unmarshal"). + Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("buf").Index().Byte() + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Error() + }), + ). + BlockFunc(func(body *Group) { + // Body: + body.Err().Op(":=").Id("obj").Dot("UnmarshalWithDecoder").Call( + Qual(PkgBinary, "NewBorshDecoder").Call(Id("buf")), + ) + body.If(Err().Op("!=").Nil()).Block( + // If there was an error, return it. + Return( + Qual("fmt", "Errorf").Call( + Lit("error while unmarshaling "+receiverTypeName+": %w"), + Err(), + ), + ), + ) + body.Return( + Nil(), // No error. + ) + }) + } + { + code.Line().Line() + // func Unmarshal(buf []byte) (, error) { + // obj := new() + // err := obj.Unmarshal(buf) + // if err != nil { + // return nil, err + // } + // return obj, nil + // } + code.Func().Id("Unmarshal" + receiverTypeName). + Params( + ListFunc(func(params *Group) { + // Parameters: + params.Id("buf").Index().Byte() + }), + ). + Params( + ListFunc(func(results *Group) { + // Results: + results.Op("*").Id(receiverTypeName) + results.Error() + }), + ). + BlockFunc(func(body *Group) { + // Body: + body.Id("obj").Op(":=").New(Id(receiverTypeName)) + body.Err().Op(":=").Id("obj").Dot("Unmarshal").Call(Id("buf")) + body.If(Err().Op("!=").Nil()).Block( + Return( + Nil(), + Err(), + ), + ) + body.Return( + Id("obj"), + Nil(), // No error. + ) + }) + } + return code +} + +func tupleToFieldsNamed( + tuple idl.IdlDefinedFieldsTuple, +) idl.IdlDefinedFieldsNamed { + fields := make(idl.IdlDefinedFieldsNamed, len(tuple)) + for i, item := range tuple { + tupleItemName := FormatTupleItemName(i) + fields[i] = idl.IdlField{ + Name: tupleItemName, + Ty: item, + } + } + return fields +} + +func (g *Generator) gen_unmarshal_DefinedFieldsNamed( + body *Group, + fields idl.IdlDefinedFieldsNamed, + uniqueFieldNames map[string]string, +) { + for _, field := range fields { + goFieldName := uniqueFieldNames[field.Name] + exportedArgName := goFieldName + if IsOption(field.Ty) || IsCOption(field.Ty) { + body.Commentf("Deserialize `%s` (optional):", exportedArgName) + } else { + body.Commentf("Deserialize `%s`:", exportedArgName) + } + + if g.isComplexEnum(field.Ty) || (IsArray(field.Ty) && g.isComplexEnum(field.Ty.(*idltype.Array).Type)) || (IsVec(field.Ty) && g.isComplexEnum(field.Ty.(*idltype.Vec).Vec)) || g.isOptionalComplexEnum(field.Ty) { + switch field.Ty.(type) { + case *idltype.Defined: + enumName := field.Ty.(*idltype.Defined).Name + body.BlockFunc(func(argBody *Group) { + { + argBody.Var().Err().Error() + argBody.List( + Id("obj").Dot(goFieldName), + Err(), + ).Op("=").Id(formatEnumParserName(enumName)).Call(Id("decoder")) + } + argBody.If( + Err().Op("!=").Nil(), + ).Block( + Return(Err()), + ) + }) + case *idltype.Array: + enumTypeName := field.Ty.(*idltype.Array).Type.(*idltype.Defined).Name + body.BlockFunc(func(argBody *Group) { + // Read the array items: + argBody.For( + Id("i").Op(":=").Lit(0), + Id("i").Op("<").Len(Id("obj").Dot(goFieldName)), + Id("i").Op("++"), + ).BlockFunc(func(forBody *Group) { + forBody.List( + Id("obj").Dot(goFieldName).Index(Id("i")), + Err(), + ).Op("=").Id(formatEnumParserName(enumTypeName)).Call(Id("decoder")) + forBody.If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual(PkgAnchorGoErrors, "NewIndex").Call( + Id("i"), + Err(), + ), + ), + ), + ) + }) + }) + case *idltype.Vec: + enumTypeName := field.Ty.(*idltype.Vec).Vec.(*idltype.Defined).Name + body.BlockFunc(func(argBody *Group) { + // Read the vector length: + argBody.List(Id("vecLen"), Err()).Op(":=").Id("decoder").Dot("ReadLength").Call() + argBody.If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while reading vector length: %w"), + Err(), + ), + ), + ), + ) + argBody.If(Id("vecLen").Op(">").Id("decoder").Dot("Remaining").Call()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("vector length %d exceeds remaining decoder bytes %d"), + Id("vecLen"), + Id("decoder").Dot("Remaining").Call(), + ), + ), + ), + ) + // Create the vector: + argBody.Id("obj").Dot(goFieldName).Op("=").Make(Index().Id(enumTypeName), Id("vecLen")) + // Read the vector items: + argBody.For( + Id("i").Op(":=").Lit(0), + Id("i").Op("<").Id("vecLen"), + Id("i").Op("++"), + ).BlockFunc(func(forBody *Group) { + forBody.List( + Id("obj").Dot(goFieldName).Index(Id("i")), + Err(), + ).Op("=").Id(formatEnumParserName(enumTypeName)).Call(Id("decoder")) + forBody.If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Qual(PkgAnchorGoErrors, "NewIndex").Call( + Id("i"), + Err(), + ), + ), + ), + ) + }) + }) + case *idltype.Option: + enumTypeName := field.Ty.(*idltype.Option).Option.(*idltype.Defined).Name + gen_unmarshal_optionalComplexEnum(body, "ReadOption", enumTypeName, exportedArgName) + case *idltype.COption: + enumTypeName := field.Ty.(*idltype.COption).COption.(*idltype.Defined).Name + gen_unmarshal_optionalComplexEnum(body, "ReadCOption", enumTypeName, exportedArgName) + } + } else { + if IsOption(field.Ty) || IsCOption(field.Ty) { + var optionalityReaderName string + switch { + case IsOption(field.Ty): + optionalityReaderName = "ReadOption" + case IsCOption(field.Ty): + optionalityReaderName = "ReadCOption" + } + + body.BlockFunc(func(optGroup *Group) { + // if nil: + optGroup.List(Id("ok"), Err()).Op(":=").Id("decoder").Dot(optionalityReaderName).Call() + optGroup.If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewOption").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while reading optionality: %w"), + Err(), + ), + ), + ), + ) + optGroup.If(Id("ok")).Block( + Err().Op("=").Id("decoder").Dot("Decode").Call(Op("&").Id("obj").Dot(goFieldName)), + If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ), + ), + ), + ) + }) + } else { + body.Err().Op("=").Id("decoder").Dot("Decode").Call(Op("&").Id("obj").Dot(goFieldName)) + body.If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ), + ), + ) + } + } + } +} + +func gen_unmarshal_optionalComplexEnum( + body *Group, + optionalityReaderName string, + enumTypeName string, + exportedArgName string, +) { + body.BlockFunc(func(optGroup *Group) { + optGroup.List(Id("ok"), Err()).Op(":=").Id("decoder").Dot(optionalityReaderName).Call() + optGroup.If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewOption").Call( + Lit(exportedArgName), + Qual("fmt", "Errorf").Call( + Lit("error while reading optionality: %w"), + Err(), + ), + ), + ), + ) + optGroup.If(Id("ok")).Block( + List( + Id("obj").Dot(exportedArgName), + Err(), + ).Op("=").Id(formatEnumParserName(enumTypeName)).Call(Id("decoder")), + If(Err().Op("!=").Nil()).Block( + Return( + Qual(PkgAnchorGoErrors, "NewField").Call( + Lit(exportedArgName), + Err(), + ), + ), + ), + ) + }) +} diff --git a/cmd/generate-bindings/solana/bindgen.go b/cmd/generate-bindings/solana/bindgen.go new file mode 100644 index 00000000..f9f5b98e --- /dev/null +++ b/cmd/generate-bindings/solana/bindgen.go @@ -0,0 +1,155 @@ +package solana + +import ( + "bytes" + "fmt" + "go/token" + "log/slog" + "os" + "path" + "strings" + + "github.com/gagliardetto/anchor-go/idl" + "github.com/gagliardetto/anchor-go/tools" + bin "github.com/gagliardetto/binary" + + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/solana/anchor-go/generator" +) + +func GenerateBindings( + pathToIdl string, + programName string, + outputDir string, +) error { + if pathToIdl == "" { + return fmt.Errorf("pathToIdl is empty") + } + if programName == "" { + return fmt.Errorf("programName is empty") + } + if outputDir == "" { + return fmt.Errorf("outputDir is empty") + } + if err := os.MkdirAll(outputDir, 0o777); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + slog.Info("Starting code generation", + "outputDir", outputDir, + "pathToIdl", pathToIdl, + ) + parsedIdl, err := idl.ParseFromFilepath(pathToIdl) + if err != nil { + return fmt.Errorf("failed to parse IDL: %w", err) + } + if parsedIdl == nil { + return fmt.Errorf("parsedIdl is nil") + } + if err := parsedIdl.Validate(); err != nil { + return fmt.Errorf("invalid IDL: %w", err) + } + if parsedIdl.Address == nil || parsedIdl.Address.IsZero() { + return fmt.Errorf("address is empty in idl file: %s", pathToIdl) + } + slog.Info("Using IDL address as program ID", "address", parsedIdl.Address.String()) + + parsedIdl.Metadata.Name = bin.ToSnakeForSighash(parsedIdl.Metadata.Name) + // check that the name is not a reserved keyword: + if parsedIdl.Metadata.Name != "" { + if tools.IsReservedKeyword(parsedIdl.Metadata.Name) { + slog.Warn("The IDL metadata.name is a reserved Go keyword: adding a suffix to avoid conflicts.", + "name", parsedIdl.Metadata.Name, + "reservedKeyword", token.Lookup(parsedIdl.Metadata.Name).String(), + ) + // Add a suffix to the name to avoid conflicts with Go reserved keywords: + parsedIdl.Metadata.Name += "_program" + } + if !tools.IsValidIdent(parsedIdl.Metadata.Name) { + // add a prefix to the name to avoid conflicts with Go reserved keywords: + parsedIdl.Metadata.Name = "my_" + parsedIdl.Metadata.Name + } + } + + packageName, err := normalizeGoPackageName(programName) + if err != nil { + return err + } + if err := generator.ValidateIDLDerivedIdentifiers(parsedIdl); err != nil { + return fmt.Errorf("IDL contains names that cannot be mapped to valid Go identifiers: %w", err) + } + + options := generator.GeneratorOptions{ + OutputDir: outputDir, + Package: packageName, + ProgramName: programName, + ProgramId: parsedIdl.Address, + } + + slog.Info("Parsed IDL successfully", + "version", parsedIdl.Metadata.Version, + "name", parsedIdl.Metadata.Name, + "address", parsedIdl.Address, + "programId", parsedIdl.Address.String(), + "instructionsCount", len(parsedIdl.Instructions), + "accountsCount", len(parsedIdl.Accounts), + "eventsCount", len(parsedIdl.Events), + "typesCount", len(parsedIdl.Types), + "constantsCount", len(parsedIdl.Constants), + "errorsCount", len(parsedIdl.Errors), + ) + + gen := generator.NewGenerator(parsedIdl, &options) + generatedFiles, err := gen.Generate() + if err != nil { + return fmt.Errorf("failed to generate: %w", err) + } + + for _, file := range generatedFiles.Files { + assetFilename := file.Name + assetFilepath := path.Join(options.OutputDir, assetFilename) + + var buf bytes.Buffer + if err := file.File.Render(&buf); err != nil { + return fmt.Errorf("failed to render generated file %q: %w", assetFilename, err) + } + + slog.Info("Writing file", + "filepath", assetFilepath, + "name", file.Name, + "modPath", options.ModPath, + ) + if err := os.WriteFile(assetFilepath, buf.Bytes(), 0o600); err != nil { + return fmt.Errorf("failed to write file %q: %w", assetFilepath, err) + } + } + slog.Info("Generation completed successfully", + "outputDir", options.OutputDir, + "modPath", options.ModPath, + "package", options.Package, + "programName", options.ProgramName, + ) + return nil +} + +// normalizeGoPackageName maps a contract filename stem or program label to a valid Go package name. +func normalizeGoPackageName(name string) (string, error) { + if strings.TrimSpace(name) == "" { + return "", fmt.Errorf("contract/program name for Go package is empty") + } + var b strings.Builder + for _, r := range strings.ToLower(name) { + if r == '-' { + b.WriteByte('_') + } else { + b.WriteRune(r) + } + } + out := b.String() + if !tools.IsValidIdent(out) { + return "", fmt.Errorf("invalid Go package name after normalization (from contract/program name %q): %q is not a valid Go identifier; use only letters, digits, and underscores, and do not start with a digit", name, out) + } + if tools.IsReservedKeyword(out) { + return "", fmt.Errorf("invalid Go package name: normalized name %q is a Go reserved keyword (from contract/program name %q)", out, name) + } + return out, nil +} diff --git a/cmd/generate-bindings/solana/bindings_test.go b/cmd/generate-bindings/solana/bindings_test.go new file mode 100644 index 00000000..d7349319 --- /dev/null +++ b/cmd/generate-bindings/solana/bindings_test.go @@ -0,0 +1,179 @@ +package solana_test + +import ( + "context" + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/test-go/testify/require" + "google.golang.org/protobuf/proto" + + ocr3types "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" + "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + solanasdk "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + solanasdkmock "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/mock" + "github.com/smartcontractkit/cre-sdk-go/cre/testutils" + consensusmock "github.com/smartcontractkit/cre-sdk-go/internal_testing/capabilities/consensus/mock" + + datastorage "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/solana/testdata/data_storage" +) + +const anyChainSelector = uint64(1337) + +func TestGeneratedBindingsCodec(t *testing.T) { + codec := datastorage.Codec{} + + t.Run("encode functions", func(t *testing.T) { + // structs + userData := datastorage.UserData{ + Key: "testKey", + Value: "testValue", + } + _, err := codec.EncodeUserDataStruct(userData) + require.NoError(t, err) + + testPrivKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + testPubKey := testPrivKey.PublicKey() + + logAccess := datastorage.AccessLogged{ + Caller: testPubKey, + Message: "testMessage", + } + _, err = codec.EncodeAccessLoggedStruct(logAccess) + require.NoError(t, err) + + readData := datastorage.DataAccount{ + Sender: testPubKey.String(), + Key: "testKey", + Value: "testValue", + } + _, err = codec.EncodeDataAccountStruct(readData) + require.NoError(t, err) + + storeData := datastorage.DynamicEvent{ + Key: "testKey", + UserData: userData, + Sender: testPubKey.String(), + Metadata: []byte("testMetadata"), + MetadataArray: [][]byte{}, + } + _, err = codec.EncodeDynamicEventStruct(storeData) + require.NoError(t, err) + + storeUserData := datastorage.UpdateReserves{ + TotalMinted: 100, + TotalReserve: uint64(200), + } + _, err = codec.EncodeUpdateReservesStruct(storeUserData) + require.NoError(t, err) + }) +} + +func TestWriteReportMethods(t *testing.T) { + client := &solanasdk.Client{ChainSelector: anyChainSelector} + ds, err := datastorage.NewDataStorage(client) + require.NoError(t, err, "Failed to create DataStorage instance") + + report := ocr3types.Metadata{ + Version: 1, + ExecutionID: "1234567890123456789012345678901234567890123456789012345678901234", + Timestamp: 1620000000, + DONID: 1, + DONConfigVersion: 1, + WorkflowID: "1234567890123456789012345678901234567890123456789012345678901234", + WorkflowName: "12", + WorkflowOwner: "1234567890123456789012345678901234567890", + ReportID: "1234", + } + + rawReport, err := report.Encode() + require.NoError(t, err) + + consensusCap, err := consensusmock.NewConsensusCapability(t) + require.NoError(t, err, "Failed to create Consensus capability") + consensusCap.Report = func(_ context.Context, input *sdk.ReportRequest) (*sdk.ReportResponse, error) { + return &sdk.ReportResponse{ + RawReport: rawReport, + }, nil + } + + solanaCap, err := solanasdkmock.NewClientCapability(anyChainSelector, t) + require.NoError(t, err, "Failed to create Solana client capability") + solanaCap.WriteReport = func(_ context.Context, req *solanasdk.WriteReportRequest) (*solanasdk.WriteReportReply, error) { + return &solanasdk.WriteReportReply{ + TxStatus: solanasdk.TxStatus_TX_STATUS_SUCCESS, + TxSignature: []byte{0x01, 0x02, 0x03, 0x04}, + }, nil + } + + runtime := testutils.NewRuntime(t, testutils.Secrets{}) + + reply := ds.WriteReportFromUserData(runtime, datastorage.UserData{ + Key: "testKey", + Value: "testValue", + }, nil, nil) + require.NoError(t, err, "WriteReportDataStorageUserData should not return an error") + response, err := reply.Await() + require.NoError(t, err, "Awaiting WriteReportDataStorageUserData reply should not return an error") + require.NotNil(t, response, "Response from WriteReportDataStorageUserData should not be nil") + require.True(t, proto.Equal(&solanasdk.WriteReportReply{ + TxStatus: solanasdk.TxStatus_TX_STATUS_SUCCESS, + TxSignature: []byte{0x01, 0x02, 0x03, 0x04}, + }, response), "Response should match expected WriteReportReply") +} + +func TestZeroArgInstructionRoundTrip(t *testing.T) { + zeroArgInstructions := []struct { + name string + buildFn func() (solana.Instruction, error) + expectedTyp datastorage.Instruction + }{ + { + name: "get_multiple_reserves", + buildFn: datastorage.NewGetMultipleReservesInstruction, + expectedTyp: &datastorage.GetMultipleReservesInstruction{}, + }, + { + name: "get_reserves", + buildFn: datastorage.NewGetReservesInstruction, + expectedTyp: &datastorage.GetReservesInstruction{}, + }, + { + name: "get_tuple_reserves", + buildFn: datastorage.NewGetTupleReservesInstruction, + expectedTyp: &datastorage.GetTupleReservesInstruction{}, + }, + } + + for _, tc := range zeroArgInstructions { + t.Run(tc.name, func(t *testing.T) { + ix, err := tc.buildFn() + require.NoError(t, err, "building instruction should succeed") + + data, err := ix.Data() + require.NoError(t, err) + require.Len(t, data, 8, "zero-arg instruction data must be exactly the 8-byte discriminator") + + parsed, err := datastorage.ParseInstructionWithoutAccounts(data) + require.NoError(t, err, "ParseInstruction must accept the discriminator-only data produced by the builder") + require.IsType(t, tc.expectedTyp, parsed) + }) + } +} + +func TestEncodeStruct(t *testing.T) { + client := &solanasdk.Client{ChainSelector: anyChainSelector} + ds, err := datastorage.NewDataStorage(client) + require.NoError(t, err, "Failed to create DataStorage instance") + + str := datastorage.DataAccount{ + Key: "testKey", + Value: "testValue", + Sender: "testSender", + } + + encoded, err := ds.Codec.EncodeDataAccountStruct(str) + require.NoError(t, err, "Encoding DataStorageDataAccount should not return an error") + require.NotNil(t, encoded, "Encoded data should not be nil") +} diff --git a/cmd/generate-bindings/solana/gen.go b/cmd/generate-bindings/solana/gen.go new file mode 100644 index 00000000..b74413f3 --- /dev/null +++ b/cmd/generate-bindings/solana/gen.go @@ -0,0 +1,2 @@ +//go:generate go run ./testdata/gen +package solana diff --git a/cmd/generate-bindings/solana/gen_test.go b/cmd/generate-bindings/solana/gen_test.go new file mode 100644 index 00000000..1a97f327 --- /dev/null +++ b/cmd/generate-bindings/solana/gen_test.go @@ -0,0 +1,17 @@ +package solana_test + +import ( + "testing" + + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/solana" +) + +func TestGenerateBindings(t *testing.T) { + if err := solana.GenerateBindings( + "./testdata/contracts/idl/data_storage.json", + "data_storage", + "./testdata/data_storage", + ); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/generate-bindings/solana/solana.go b/cmd/generate-bindings/solana/solana.go new file mode 100644 index 00000000..32ee92b4 --- /dev/null +++ b/cmd/generate-bindings/solana/solana.go @@ -0,0 +1,259 @@ +package solana + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/smartcontractkit/cre-cli/internal/constants" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/ui" + "github.com/smartcontractkit/cre-cli/internal/validation" +) + +type Inputs struct { + ProjectRoot string `validate:"required,dir" cli:"--project-root"` + Language string `validate:"required,oneof=go" cli:"--language"` + IdlPath string `validate:"required,path_read" cli:"--idl"` + OutPath string `validate:"required" cli:"--out"` +} + +func New(runtimeContext *runtime.Context) *cobra.Command { + var generateBindingsCmd = &cobra.Command{ + Use: "solana", + Short: "Generate bindings from contract IDL", + Long: `This command generates bindings from contract IDL files. +Supports Solana chain family and Go language. +Each contract gets its own package subdirectory to avoid naming conflicts. +For example, data_storage.json generates bindings in generated/data_storage/ package.`, + Example: " cre generate-bindings-solana", + RunE: func(cmd *cobra.Command, args []string) error { + handler := newHandler(runtimeContext) + inputs, err := handler.ResolveInputs(runtimeContext.Viper) + if err != nil { + return err + } + if err := handler.ValidateInputs(inputs); err != nil { + return err + } + return handler.Execute(inputs) + }, + } + + generateBindingsCmd.Flags().StringP("project-root", "p", "", "Path to project root directory (defaults to current directory)") + generateBindingsCmd.Flags().StringP("language", "l", "go", "Target language (go)") + generateBindingsCmd.Flags().StringP("idl", "i", "", "Path to IDL directory (defaults to contracts/solana/src/idl/)") + generateBindingsCmd.Flags().StringP("out", "o", "", "Path to output directory (defaults to contracts/solana/src/generated/)") + + return generateBindingsCmd +} + +type handler struct { + log *zerolog.Logger +} + +func newHandler(ctx *runtime.Context) *handler { + return &handler{ + log: ctx.Logger, + } +} + +func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { + // Get current working directory as default project root + currentDir, err := os.Getwd() + if err != nil { + return Inputs{}, fmt.Errorf("failed to get current working directory: %w", err) + } + + // Resolve project root with fallback to current directory + projectRoot := v.GetString("project-root") + if projectRoot == "" { + projectRoot = currentDir + } + + contractsPath := filepath.Join(projectRoot, "contracts") + if _, err := os.Stat(contractsPath); err != nil { + return Inputs{}, fmt.Errorf("contracts folder not found in project root: %s", contractsPath) + } + + // Language defaults are handled by StringP + language := v.GetString("language") + + // Resolve IDL path with fallback to contracts/solana/src/idl/ + idlPath := v.GetString("idl") + if idlPath == "" { + idlPath = filepath.Join(projectRoot, "contracts", "solana", "src", "idl") + } + + // Resolve output path with fallback to contracts/solana/src/generated/ + outPath := v.GetString("out") + if outPath == "" { + outPath = filepath.Join(projectRoot, "contracts", "solana", "src", "generated") + } + + return Inputs{ + ProjectRoot: projectRoot, + Language: language, + IdlPath: idlPath, + OutPath: outPath, + }, nil +} + +func (h *handler) ValidateInputs(inputs Inputs) error { + validate, err := validation.NewValidator() + if err != nil { + return fmt.Errorf("failed to initialize validator: %w", err) + } + + if err = validate.Struct(inputs); err != nil { + return validate.ParseValidationErrors(err) + } + + // Additional validation for Idl path + if _, err := os.Stat(inputs.IdlPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("IDL path does not exist: %s", inputs.IdlPath) + } + return fmt.Errorf("failed to access IDL path: %w", err) + } + + // Validate that if IdlPath is a directory, it contains .json files + if info, err := os.Stat(inputs.IdlPath); err == nil && info.IsDir() { + files, err := filepath.Glob(filepath.Join(inputs.IdlPath, "*.json")) + if err != nil { + return fmt.Errorf("failed to check for IDL files in directory: %w", err) + } + if len(files) == 0 { + return fmt.Errorf("no .json files found in directory: %s", inputs.IdlPath) + } + } + + return nil +} + +func (h *handler) processIdlDirectory(inputs Inputs) error { + // Read all .json files in the directory + files, err := filepath.Glob(filepath.Join(inputs.IdlPath, "*.json")) + if err != nil { + return fmt.Errorf("failed to find IDL files: %w", err) + } + + if len(files) == 0 { + return fmt.Errorf("no .json files found in directory: %s", inputs.IdlPath) + } + + // Process each IDL file + for _, idlFile := range files { + // Extract contract name from filename (remove .json extension) + contractName := filepath.Base(idlFile) + contractName = contractName[:len(contractName)-5] // Remove .json extension + + // Create per-contract output directory + contractOutDir := filepath.Join(inputs.OutPath, contractName) + if err := os.MkdirAll(contractOutDir, 0755); err != nil { + return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err) + } + + // Create output file path in contract-specific directory + outputFile := filepath.Join(contractOutDir, contractName+".go") + + ui.Dim(fmt.Sprintf("Processing IDL file: %s, contract: %s, output: %s\n", idlFile, contractName, outputFile)) + + err = GenerateBindings( + idlFile, + contractName, + contractOutDir, + ) + if err != nil { + return fmt.Errorf("failed to generate bindings for %s: %w", idlFile, err) + } + } + + return nil +} + +func (h *handler) processSingleIdl(inputs Inputs) error { + // Extract contract name from IDL file path + contractName := filepath.Base(inputs.IdlPath) + if filepath.Ext(contractName) == ".json" { + contractName = contractName[:len(contractName)-5] // Remove .json extension + } + + // Create per-contract output directory + contractOutDir := filepath.Join(inputs.OutPath, contractName) + if err := os.MkdirAll(contractOutDir, 0755); err != nil { + return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err) + } + + ui.Dim(fmt.Sprintf("Processing single IDL file: %s, contract: %s, output: %s\n", inputs.IdlPath, contractName, contractOutDir)) + + return GenerateBindings( + inputs.IdlPath, + contractName, + contractOutDir, + ) +} + +func (h *handler) Execute(inputs Inputs) error { + // Validate language + switch inputs.Language { + case "go": + // Language supported, continue + default: + return fmt.Errorf("unsupported language: %s", inputs.Language) + } + + // Create output directory if it doesn't exist + if err := os.MkdirAll(inputs.OutPath, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // Check if IDL path is a directory or file + info, err := os.Stat(inputs.IdlPath) + if err != nil { + return fmt.Errorf("failed to access IDL path: %w", err) + } + + if info.IsDir() { + if err := h.processIdlDirectory(inputs); err != nil { + return err + } + } else { + if err := h.processSingleIdl(inputs); err != nil { + return err + } + } + + err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go@"+constants.SdkVersion) + if err != nil { + return err + } + err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana@"+constants.SolanaCapabilitiesVersion) + if err != nil { + return err + } + err = runCommand(inputs.ProjectRoot, "go", "mod", "tidy") + if err != nil { + return err + } + + return nil +} + +// runCommand executes a command in a specified directory +func runCommand(dir string, command string, args ...string) error { + cmd := exec.Command(command, args...) + cmd.Dir = dir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run %s: %w", command, err) + } + + return nil +} diff --git a/cmd/generate-bindings/solana/solana_test.go b/cmd/generate-bindings/solana/solana_test.go new file mode 100644 index 00000000..e3eebfa9 --- /dev/null +++ b/cmd/generate-bindings/solana/solana_test.go @@ -0,0 +1,285 @@ +package solana + +import ( + "os" + "path/filepath" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/cre-cli/internal/runtime" +) + +func TestResolveSolanaInputs_DefaultFallbacks(t *testing.T) { + // Create a temporary directory for testing + tempDir, err := os.MkdirTemp("", "generate-bindings-test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + // Create required contracts directory and go.mod + contractsDir := filepath.Join(tempDir, "contracts") + err = os.MkdirAll(contractsDir, 0755) + require.NoError(t, err) + + goModPath := filepath.Join(contractsDir, "go.mod") + err = os.WriteFile(goModPath, []byte("module test/contracts\n\ngo 1.20\n"), 0600) + require.NoError(t, err) + + // Change to temp directory + originalDir, err := os.Getwd() + require.NoError(t, err) + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Errorf("Failed to restore original directory: %v", err) + } + }() + + err = os.Chdir(tempDir) + require.NoError(t, err) + + // Test with minimal input + v := viper.New() + v.Set("language", "go") // Default from StringP + + runtimeCtx := &runtime.Context{} + handler := newHandler(runtimeCtx) + + inputs, err := handler.ResolveInputs(v) + require.NoError(t, err) + + // Use filepath.EvalSymlinks to handle macOS /var vs /private/var symlink issues + expectedRoot, _ := filepath.EvalSymlinks(tempDir) + actualRoot, _ := filepath.EvalSymlinks(inputs.ProjectRoot) + assert.Equal(t, expectedRoot, actualRoot) + assert.Equal(t, "go", inputs.Language) + expectedIdl, _ := filepath.EvalSymlinks(filepath.Join(tempDir, "contracts", "solana", "src", "idl")) + actualIdl, _ := filepath.EvalSymlinks(inputs.IdlPath) + assert.Equal(t, expectedIdl, actualIdl) + expectedOut, _ := filepath.EvalSymlinks(filepath.Join(tempDir, "contracts", "solana", "src", "generated")) + actualOut, _ := filepath.EvalSymlinks(inputs.OutPath) + assert.Equal(t, expectedOut, actualOut) +} + +func TestResolveSolanaInputs_CustomOutPath(t *testing.T) { + tempDir, err := os.MkdirTemp("", "generate-bindings-test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + contractsDir := filepath.Join(tempDir, "contracts") + err = os.MkdirAll(contractsDir, 0755) + require.NoError(t, err) + + originalDir, err := os.Getwd() + require.NoError(t, err) + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Errorf("Failed to restore original directory: %v", err) + } + }() + + err = os.Chdir(tempDir) + require.NoError(t, err) + + customOut := filepath.Join(tempDir, "my-custom-output") + + v := viper.New() + v.Set("language", "go") + v.Set("out", customOut) + + runtimeCtx := &runtime.Context{} + handler := newHandler(runtimeCtx) + + inputs, err := handler.ResolveInputs(v) + require.NoError(t, err) + + assert.Equal(t, customOut, inputs.OutPath) +} + +func TestProcessSolanaSingleIdl(t *testing.T) { + // Create a temporary directory structure + tempDir, err := os.MkdirTemp("", "generate-bindings-test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + idlDir := filepath.Join(tempDir, "idl") + outDir := filepath.Join(tempDir, "generated") + + err = os.MkdirAll(idlDir, 0755) + require.NoError(t, err) + + // Create a simple IDL file + simpleIdl := `{ + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "simple_contract", + "version": "0.1.0", + "spec": "0.1.0" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [], + "args": [] + } + ], + "accounts": [], + "types": [] +}` + + idlFile := filepath.Join(idlDir, "simple_contract.json") + err = os.WriteFile(idlFile, []byte(simpleIdl), 0600) + require.NoError(t, err) + + // Create contracts directory with go.mod for module path detection + contractsDir := filepath.Join(tempDir, "contracts") + err = os.MkdirAll(contractsDir, 0755) + require.NoError(t, err) + + goModPath := filepath.Join(contractsDir, "go.mod") + err = os.WriteFile(goModPath, []byte("module test/contracts\n\ngo 1.20\n"), 0600) + require.NoError(t, err) + + inputs := Inputs{ + ProjectRoot: tempDir, + Language: "go", + IdlPath: idlFile, + OutPath: outDir, + } + + runtimeCtx := &runtime.Context{} + handler := newHandler(runtimeCtx) + + // Process the single IDL file + err = handler.processSingleIdl(inputs) + + // We expect this might fail due to missing dependencies or generator issues, + // but we can verify that the contract directory was created + if err != nil { + t.Logf("Expected error occurred: %v", err) + } + + // Verify that the contract directory was created + contractDir := filepath.Join(outDir, "simple_contract") + assert.DirExists(t, contractDir, "Expected contract directory to be created at %s", contractDir) +} + +func TestProcessSolanaIdlDirectory(t *testing.T) { + // Create a temporary directory structure + tempDir, err := os.MkdirTemp("", "generate-bindings-test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + idlDir := filepath.Join(tempDir, "idl") + outDir := filepath.Join(tempDir, "generated") + + err = os.MkdirAll(idlDir, 0755) + require.NoError(t, err) + + // Create multiple simple IDL files + simpleIdl1 := `{ + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "contract_one", + "version": "0.1.0", + "spec": "0.1.0" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [], + "args": [] + } + ], + "accounts": [], + "types": [] +}` + + simpleIdl2 := `{ + "address": "FDL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfM", + "metadata": { + "name": "contract_two", + "version": "0.1.0", + "spec": "0.1.0" + }, + "instructions": [ + { + "name": "execute", + "discriminator": [100, 100, 100, 31, 13, 152, 155, 237], + "accounts": [], + "args": [] + } + ], + "accounts": [], + "types": [] +}` + + err = os.WriteFile(filepath.Join(idlDir, "contract_one.json"), []byte(simpleIdl1), 0600) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(idlDir, "contract_two.json"), []byte(simpleIdl2), 0600) + require.NoError(t, err) + + // Create contracts directory with go.mod for module path detection + contractsDir := filepath.Join(tempDir, "contracts") + err = os.MkdirAll(contractsDir, 0755) + require.NoError(t, err) + + goModPath := filepath.Join(contractsDir, "go.mod") + err = os.WriteFile(goModPath, []byte("module test/contracts\n\ngo 1.20\n"), 0600) + require.NoError(t, err) + + inputs := Inputs{ + ProjectRoot: tempDir, + Language: "go", + IdlPath: idlDir, + OutPath: outDir, + } + + runtimeCtx := &runtime.Context{} + handler := newHandler(runtimeCtx) + + // Process the IDL directory + err = handler.processIdlDirectory(inputs) + + // We expect this might fail due to missing dependencies or generator issues, + // but we can verify that the contract directories were created + if err != nil { + t.Logf("Expected error occurred: %v", err) + } + + // Verify that per-contract directories were created + contract1Dir := filepath.Join(outDir, "contract_one") + contract2Dir := filepath.Join(outDir, "contract_two") + assert.DirExists(t, contract1Dir, "Expected contract directory to be created at %s", contract1Dir) + assert.DirExists(t, contract2Dir, "Expected contract directory to be created at %s", contract2Dir) +} + +func TestProcessSolanaIdlDirectory_NoIdlFiles(t *testing.T) { + // Create a temporary directory structure + tempDir, err := os.MkdirTemp("", "generate-bindings-test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + idlDir := filepath.Join(tempDir, "idl") + outDir := filepath.Join(tempDir, "generated") + + err = os.MkdirAll(idlDir, 0755) + require.NoError(t, err) + + inputs := Inputs{ + ProjectRoot: tempDir, + Language: "go", + IdlPath: idlDir, + OutPath: outDir, + } + + runtimeCtx := &runtime.Context{} + handler := newHandler(runtimeCtx) + + err = handler.processIdlDirectory(inputs) + require.Error(t, err) + assert.Contains(t, err.Error(), "no .json files found") +} diff --git a/cmd/generate-bindings/solana/testdata/contracts/idl/data_storage.json b/cmd/generate-bindings/solana/testdata/contracts/idl/data_storage.json new file mode 100644 index 00000000..2ff93a7a --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/contracts/idl/data_storage.json @@ -0,0 +1,511 @@ +{ + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "get_multiple_reserves", + "discriminator": [ + 104, + 122, + 140, + 104, + 175, + 151, + 70, + 42 + ], + "accounts": [], + "args": [], + "returns": { + "vec": { + "defined": { + "name": "UpdateReserves" + } + } + } + }, + { + "name": "get_reserves", + "discriminator": [ + 121, + 140, + 237, + 84, + 218, + 105, + 48, + 17 + ], + "accounts": [], + "args": [], + "returns": { + "defined": { + "name": "UpdateReserves" + } + } + }, + { + "name": "get_tuple_reserves", + "discriminator": [ + 189, + 83, + 186, + 20, + 127, + 80, + 109, + 49 + ], + "accounts": [], + "args": [] + }, + { + "name": "initialize_data_account", + "discriminator": [ + 9, + 64, + 78, + 49, + 71, + 193, + 15, + 250 + ], + "accounts": [ + { + "name": "data_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97, + 95, + 97, + 99, + 99, + 111, + 117, + 110, + 116 + ] + }, + { + "kind": "account", + "path": "user" + } + ] + } + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "input", + "type": { + "defined": { + "name": "UserData" + } + } + } + ] + }, + { + "name": "log_access", + "discriminator": [ + 196, + 55, + 194, + 24, + 5, + 224, + 161, + 204 + ], + "accounts": [ + { + "name": "user", + "signer": true + } + ], + "args": [ + { + "name": "message", + "type": "string" + } + ] + }, + { + "name": "on_report", + "discriminator": [ + 214, + 173, + 18, + 221, + 173, + 148, + 151, + 208 + ], + "accounts": [ + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "data_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97, + 95, + 97, + 99, + 99, + 111, + 117, + 110, + 116 + ] + }, + { + "kind": "account", + "path": "user" + } + ] + } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "_metadata", + "type": "bytes" + }, + { + "name": "payload", + "type": "bytes" + } + ] + }, + { + "name": "update_key_value_data", + "discriminator": [ + 67, + 137, + 144, + 35, + 210, + 126, + 254, + 79 + ], + "accounts": [ + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "data_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97, + 95, + 97, + 99, + 99, + 111, + 117, + 110, + 116 + ] + }, + { + "kind": "account", + "path": "user" + } + ] + } + } + ], + "args": [ + { + "name": "key", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] + }, + { + "name": "update_user_data", + "discriminator": [ + 11, + 13, + 114, + 150, + 194, + 224, + 192, + 78 + ], + "accounts": [ + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "data_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97, + 95, + 97, + 99, + 99, + 111, + 117, + 110, + 116 + ] + }, + { + "kind": "account", + "path": "user" + } + ] + } + } + ], + "args": [ + { + "name": "input", + "type": { + "defined": { + "name": "UserData" + } + } + } + ] + } + ], + "accounts": [ + { + "name": "DataAccount", + "discriminator": [ + 85, + 240, + 182, + 158, + 76, + 7, + 18, + 233 + ] + } + ], + "events": [ + { + "name": "AccessLogged", + "discriminator": [ + 243, + 53, + 225, + 71, + 64, + 120, + 109, + 25 + ] + }, + { + "name": "DynamicEvent", + "discriminator": [ + 236, + 145, + 224, + 161, + 9, + 222, + 218, + 237 + ] + }, + { + "name": "NoFields", + "discriminator": [ + 160, + 156, + 94, + 85, + 77, + 122, + 98, + 240 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "DataNotFound", + "msg": "data not found" + } + ], + "types": [ + { + "name": "AccessLogged", + "type": { + "kind": "struct", + "fields": [ + { + "name": "caller", + "type": "pubkey" + }, + { + "name": "message", + "type": "string" + } + ] + } + }, + { + "name": "DataAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "sender", + "type": "string" + }, + { + "name": "key", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] + } + }, + { + "name": "DynamicEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": "string" + }, + { + "name": "user_data", + "type": { + "defined": { + "name": "UserData" + } + } + }, + { + "name": "sender", + "type": "string" + }, + { + "name": "metadata", + "type": "bytes" + }, + { + "name": "metadata_array", + "type": { + "vec": "bytes" + } + } + ] + } + }, + { + "name": "NoFields", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "UpdateReserves", + "type": { + "kind": "struct", + "fields": [ + { + "name": "total_minted", + "type": "u64" + }, + { + "name": "total_reserve", + "type": "u64" + } + ] + } + }, + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/cmd/generate-bindings/solana/testdata/contracts/source/data_storage.rs b/cmd/generate-bindings/solana/testdata/contracts/source/data_storage.rs new file mode 100644 index 00000000..dc5b02b3 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/contracts/source/data_storage.rs @@ -0,0 +1,251 @@ +use anchor_lang::prelude::*; + +declare_id!("ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL"); + +#[program] +pub mod data_storage { + use super::*; + + // simulate + pub fn get_reserves(_ctx: Context) -> Result { + Ok(UpdateReserves { + total_minted: 100, + total_reserve: 200, + }) + } + + // simulate + pub fn get_multiple_reserves( + _ctx: Context, + ) -> Result> { + let reserves = vec![ + UpdateReserves { + total_minted: 100, + total_reserve: 200, + }, + UpdateReserves { + total_minted: 300, + total_reserve: 400, + }, + ]; + + Ok(reserves) + } + + // simulate + pub fn get_tuple_reserves(_ctx: Context) -> Result<(u64, u64)> { + Ok((100, 200)) + } + + pub fn initialize_data_account(ctx: Context, input: UserData) -> Result<()> { + ctx.accounts.data_account.sender = ctx.accounts.user.key().to_string(); + ctx.accounts.data_account.key = input.key; + ctx.accounts.data_account.value = input.value; + Ok(()) + } + + // no event + pub fn update_key_value_data( + ctx: Context, + key: String, + value: String, + ) -> Result<()> { + let acc = &mut ctx.accounts.data_account; + + acc.sender = ctx.accounts.user.key().to_string(); + acc.key = key.clone(); + acc.value = value.clone(); + + let user_data = UserData { + key: key.clone(), + value: value.clone(), + }; + + emit!(DynamicEvent { + key: key, + user_data: user_data, + sender: ctx.accounts.user.key().to_string(), + metadata: vec![1, 2, 3], + metadata_array: vec![], + }); + + Ok(()) + } + + pub fn update_user_data(ctx: Context, input: UserData) -> Result<()> { + let acc = &mut ctx.accounts.data_account; + + acc.sender = ctx.accounts.user.key().to_string(); + acc.key = input.key.clone(); + acc.value = input.value.clone(); + + let user_data_cloned = input.clone(); + + emit!(DynamicEvent { + key: input.key, + user_data: user_data_cloned, + sender: ctx.accounts.user.key().to_string(), + metadata: vec![1, 2, 3], + metadata_array: vec![], + }); + + Ok(()) + } + + pub fn log_access(ctx: Context, message: String) -> Result<()> { + emit!(AccessLogged { + caller: ctx.accounts.user.key(), + message, + }); + Ok(()) + } + + pub fn on_report(ctx: Context, _metadata: Vec, payload: Vec) -> Result<()> { + // decode payload into UserData + let mut bytes: &[u8] = &payload; + let user = UserData::deserialize(&mut bytes)?; // requires AnchorDeserialize on UserData + + // update mapping-equivalent: this user's PDA + let acc = &mut ctx.accounts.data_account; + acc.sender = ctx.accounts.user.key().to_string(); + acc.key = user.key.clone(); + acc.value = user.value.clone(); + + let user_cloned = user.clone(); + + // emit event + emit!(DynamicEvent { + sender: ctx.accounts.user.key().to_string(), + key: user.key, + user_data: user_cloned, + metadata: vec![1, 2, 3], + metadata_array: vec![], + }); + + Ok(()) + } + + pub fn handle_forwarder_report( + _ctx: Context, + _report: ForwarderReport, + ) -> Result<()> { + // TODO: implement forwarding logic here + Ok(()) + } +} + +// read data from here +#[account] +pub struct DataAccount { + pub sender: String, + pub key: String, + pub value: String, +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account( + init, + payer = user, + space = 8 + + (4 + 64) // sender max 64 + + (4 + 64) // key max 64 + + (4 + 256) // value max 256 + + 1, // bump + seeds = [b"data_account", user.key().as_ref()], // seed for deterministic PDA + bump + )] + pub data_account: Account<'info, DataAccount>, + + #[account(mut)] + pub user: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct UpdateData<'info> { + #[account(mut)] + pub user: Signer<'info>, + + // PDA: one account per user, same seeds as Initialize + #[account( + mut, + seeds = [b"data_account", user.key().as_ref()], + bump, + )] + pub data_account: Account<'info, DataAccount>, +} + +// just use to have a complex event type ? +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq)] +pub struct UserData { + pub key: String, + pub value: String, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq)] +pub struct ForwarderReport { + pub account_hash: Vec, + pub payload: Vec, +} + +#[event] +pub struct DynamicEvent { + pub key: String, + pub user_data: UserData, + pub sender: String, + pub metadata: Vec, + pub metadata_array: Vec>, +} + +#[event] +pub struct AccessLogged { + pub caller: Pubkey, + pub message: String, +} + +#[event] +pub struct NoFields {} + +#[error_code] +pub enum DataError { + #[msg("data not found")] + DataNotFound = 0, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct UpdateReserves { + pub total_minted: u64, + pub total_reserve: u64, +} + +// empty contexts +#[derive(Accounts)] +pub struct GetReserves {} +#[derive(Accounts)] +pub struct GetMultipleReserves {} +#[derive(Accounts)] +pub struct GetTupleReserves {} + +#[derive(Accounts)] +pub struct HandleForwarderReport {} + +#[derive(Accounts)] +pub struct LogAccess<'info> { + pub user: Signer<'info>, +} + +#[derive(Accounts)] +pub struct OnReport<'info> { + #[account(mut)] + pub user: Signer<'info>, + + #[account( + mut, + seeds = [b"data_account", user.key().as_ref()], + bump, + )] + pub data_account: Account<'info, DataAccount>, + + pub system_program: Program<'info, System>, +} diff --git a/cmd/generate-bindings/solana/testdata/data_storage/accounts.go b/cmd/generate-bindings/solana/testdata/data_storage/accounts.go new file mode 100644 index 00000000..f5711951 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/accounts.go @@ -0,0 +1,50 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains parsers for the accounts defined in the IDL. +// Code generated by https://github.com/smartcontractkit/cre-cli. DO NOT EDIT. + +package data_storage + +import ( + "fmt" + binary "github.com/gagliardetto/binary" +) + +func ParseAnyAccount(accountData []byte) (any, error) { + decoder := binary.NewBorshDecoder(accountData) + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return nil, fmt.Errorf("failed to peek account discriminator: %w", err) + } + switch discriminator { + case Account_DataAccount: + value := new(DataAccount) + err := value.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account as DataAccount: %w", err) + } + return value, nil + default: + return nil, fmt.Errorf("unknown discriminator: %s", binary.FormatDiscriminator(discriminator)) + } +} + +func ParseAccount_DataAccount(accountData []byte) (*DataAccount, error) { + decoder := binary.NewBorshDecoder(accountData) + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return nil, fmt.Errorf("failed to peek discriminator: %w", err) + } + if discriminator != Account_DataAccount { + return nil, fmt.Errorf("expected discriminator %v, got %s", Account_DataAccount, binary.FormatDiscriminator(discriminator)) + } + acc := new(DataAccount) + err = acc.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account of type DataAccount: %w", err) + } + return acc, nil +} + +func (c *Codec) DecodeDataAccount(data []byte) (*DataAccount, error) { + return ParseAccount_DataAccount(data) +} diff --git a/cmd/generate-bindings/solana/testdata/data_storage/constants.go b/cmd/generate-bindings/solana/testdata/data_storage/constants.go new file mode 100644 index 00000000..0c192cb2 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/constants.go @@ -0,0 +1,4 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains constants. + +package data_storage diff --git a/cmd/generate-bindings/solana/testdata/data_storage/constructor.go b/cmd/generate-bindings/solana/testdata/data_storage/constructor.go new file mode 100644 index 00000000..65dd788f --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/constructor.go @@ -0,0 +1,88 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains the constructor for the program. + +package data_storage + +import ( + "bytes" + "encoding/binary" + sdk "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + bindings "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/bindings" + cre "github.com/smartcontractkit/cre-sdk-go/cre" +) + +var IDL = "{\"address\":\"ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL\",\"metadata\":{\"name\":\"data_storage\",\"version\":\"0.1.0\",\"spec\":\"0.1.0\",\"description\":\"Created with Anchor\"},\"instructions\":[{\"name\":\"get_multiple_reserves\",\"discriminator\":[104,122,140,104,175,151,70,42],\"accounts\":[],\"args\":[],\"returns\":{\"vec\":{\"defined\":{\"name\":\"UpdateReserves\"}}}},{\"name\":\"get_reserves\",\"discriminator\":[121,140,237,84,218,105,48,17],\"accounts\":[],\"args\":[],\"returns\":{\"defined\":{\"name\":\"UpdateReserves\"}}},{\"name\":\"get_tuple_reserves\",\"discriminator\":[189,83,186,20,127,80,109,49],\"accounts\":[],\"args\":[]},{\"name\":\"initialize_data_account\",\"discriminator\":[9,64,78,49,71,193,15,250],\"accounts\":[{\"name\":\"data_account\",\"writable\":true,\"pda\":{\"seeds\":[{\"kind\":\"const\",\"value\":[100,97,116,97,95,97,99,99,111,117,110,116]},{\"kind\":\"account\",\"path\":\"user\"}]}},{\"name\":\"user\",\"writable\":true,\"signer\":true},{\"name\":\"system_program\",\"address\":\"11111111111111111111111111111111\"}],\"args\":[{\"name\":\"input\",\"type\":{\"defined\":{\"name\":\"UserData\"}}}]},{\"name\":\"log_access\",\"discriminator\":[196,55,194,24,5,224,161,204],\"accounts\":[{\"name\":\"user\",\"signer\":true}],\"args\":[{\"name\":\"message\",\"type\":\"string\"}]},{\"name\":\"on_report\",\"discriminator\":[214,173,18,221,173,148,151,208],\"accounts\":[{\"name\":\"user\",\"writable\":true,\"signer\":true},{\"name\":\"data_account\",\"writable\":true,\"pda\":{\"seeds\":[{\"kind\":\"const\",\"value\":[100,97,116,97,95,97,99,99,111,117,110,116]},{\"kind\":\"account\",\"path\":\"user\"}]}},{\"name\":\"system_program\",\"address\":\"11111111111111111111111111111111\"}],\"args\":[{\"name\":\"_metadata\",\"type\":\"bytes\"},{\"name\":\"payload\",\"type\":\"bytes\"}]},{\"name\":\"update_key_value_data\",\"discriminator\":[67,137,144,35,210,126,254,79],\"accounts\":[{\"name\":\"user\",\"writable\":true,\"signer\":true},{\"name\":\"data_account\",\"writable\":true,\"pda\":{\"seeds\":[{\"kind\":\"const\",\"value\":[100,97,116,97,95,97,99,99,111,117,110,116]},{\"kind\":\"account\",\"path\":\"user\"}]}}],\"args\":[{\"name\":\"key\",\"type\":\"string\"},{\"name\":\"value\",\"type\":\"string\"}]},{\"name\":\"update_user_data\",\"discriminator\":[11,13,114,150,194,224,192,78],\"accounts\":[{\"name\":\"user\",\"writable\":true,\"signer\":true},{\"name\":\"data_account\",\"writable\":true,\"pda\":{\"seeds\":[{\"kind\":\"const\",\"value\":[100,97,116,97,95,97,99,99,111,117,110,116]},{\"kind\":\"account\",\"path\":\"user\"}]}}],\"args\":[{\"name\":\"input\",\"type\":{\"defined\":{\"name\":\"UserData\"}}}]}],\"accounts\":[{\"name\":\"DataAccount\",\"discriminator\":[85,240,182,158,76,7,18,233]}],\"events\":[{\"name\":\"AccessLogged\",\"discriminator\":[243,53,225,71,64,120,109,25]},{\"name\":\"DynamicEvent\",\"discriminator\":[236,145,224,161,9,222,218,237]},{\"name\":\"NoFields\",\"discriminator\":[160,156,94,85,77,122,98,240]}],\"errors\":[{\"code\":6000,\"name\":\"DataNotFound\",\"msg\":\"data not found\"}],\"types\":[{\"name\":\"AccessLogged\",\"type\":{\"kind\":\"struct\",\"fields\":[{\"name\":\"caller\",\"type\":\"pubkey\"},{\"name\":\"message\",\"type\":\"string\"}]}},{\"name\":\"DataAccount\",\"type\":{\"kind\":\"struct\",\"fields\":[{\"name\":\"sender\",\"type\":\"string\"},{\"name\":\"key\",\"type\":\"string\"},{\"name\":\"value\",\"type\":\"string\"}]}},{\"name\":\"DynamicEvent\",\"type\":{\"kind\":\"struct\",\"fields\":[{\"name\":\"key\",\"type\":\"string\"},{\"name\":\"user_data\",\"type\":{\"defined\":{\"name\":\"UserData\"}}},{\"name\":\"sender\",\"type\":\"string\"},{\"name\":\"metadata\",\"type\":\"bytes\"},{\"name\":\"metadata_array\",\"type\":{\"vec\":\"bytes\"}}]}},{\"name\":\"NoFields\",\"type\":{\"kind\":\"struct\",\"fields\":[]}},{\"name\":\"UpdateReserves\",\"type\":{\"kind\":\"struct\",\"fields\":[{\"name\":\"total_minted\",\"type\":\"u64\"},{\"name\":\"total_reserve\",\"type\":\"u64\"}]}},{\"name\":\"UserData\",\"type\":{\"kind\":\"struct\",\"fields\":[{\"name\":\"key\",\"type\":\"string\"},{\"name\":\"value\",\"type\":\"string\"}]}}]}" + +type DataStorage struct { + client *solana.Client + Codec DataStorageCodec +} + +type Codec struct{} + +func NewDataStorage(client *solana.Client) (*DataStorage, error) { + return &DataStorage{ + Codec: &Codec{}, + client: client, + }, nil +} + +// EncodeBorshVecU32 returns Anchor/Borsh encoding of a Vec whose elements are opaque byte payloads. // Each [][]byte element must already be fully serialized for one Vec item on the wire. // Layout: little-endian u32 length followed by concatenated element payloads. +func EncodeBorshVecU32(elements [][]byte) ([]byte, error) { + buf := bytes.NewBuffer(nil) + if err := binary.Write(buf, binary.LittleEndian, uint32(len(elements))); err != nil { + return nil, err + } + for _, elem := range elements { + _, err := buf.Write(elem) + if err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} + +// WriteReportFromBorshEncodedVec publishes through the CRE signer using a forwarder payload built from // Borsh Vec semantics (EncodeBorshVecU32). Compose each elementPayload for your program (e.g. one encoded struct per row). // Pass computeConfig = nil to use the host default Solana compute budget. +func (c *DataStorage) WriteReportFromBorshEncodedVec(runtime cre.Runtime, elementPayloads [][]byte, remainingAccounts []*solana.AccountMeta, computeConfig *solana.ComputeConfig) cre.Promise[*solana.WriteReportReply] { + payload, err := EncodeBorshVecU32(elementPayloads) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: payload, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +type DataStorageCodec interface { + DecodeDataAccount(data []byte) (*DataAccount, error) + EncodeAccessLoggedStruct(in AccessLogged) ([]byte, error) + EncodeDataAccountStruct(in DataAccount) ([]byte, error) + EncodeDynamicEventStruct(in DynamicEvent) ([]byte, error) + EncodeNoFieldsStruct(in NoFields) ([]byte, error) + EncodeUpdateReservesStruct(in UpdateReserves) ([]byte, error) + EncodeUserDataStruct(in UserData) ([]byte, error) +} diff --git a/cmd/generate-bindings/solana/testdata/data_storage/discriminators.go b/cmd/generate-bindings/solana/testdata/data_storage/discriminators.go new file mode 100644 index 00000000..251e3d0b --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/discriminators.go @@ -0,0 +1,30 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains the discriminators for accounts and events defined in the IDL. + +package data_storage + +import binary "github.com/gagliardetto/binary" + +// Account discriminators +var ( + Account_DataAccount = binary.TypeID{85, 240, 182, 158, 76, 7, 18, 233} +) + +// Event discriminators +var ( + Event_AccessLogged = binary.TypeID{243, 53, 225, 71, 64, 120, 109, 25} + Event_DynamicEvent = binary.TypeID{236, 145, 224, 161, 9, 222, 218, 237} + Event_NoFields = binary.TypeID{160, 156, 94, 85, 77, 122, 98, 240} +) + +// Instruction discriminators +var ( + Instruction_GetMultipleReserves = binary.TypeID{104, 122, 140, 104, 175, 151, 70, 42} + Instruction_GetReserves = binary.TypeID{121, 140, 237, 84, 218, 105, 48, 17} + Instruction_GetTupleReserves = binary.TypeID{189, 83, 186, 20, 127, 80, 109, 49} + Instruction_InitializeDataAccount = binary.TypeID{9, 64, 78, 49, 71, 193, 15, 250} + Instruction_LogAccess = binary.TypeID{196, 55, 194, 24, 5, 224, 161, 204} + Instruction_OnReport = binary.TypeID{214, 173, 18, 221, 173, 148, 151, 208} + Instruction_UpdateKeyValueData = binary.TypeID{67, 137, 144, 35, 210, 126, 254, 79} + Instruction_UpdateUserData = binary.TypeID{11, 13, 114, 150, 194, 224, 192, 78} +) diff --git a/cmd/generate-bindings/solana/testdata/data_storage/errors.go b/cmd/generate-bindings/solana/testdata/data_storage/errors.go new file mode 100644 index 00000000..576b057b --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/errors.go @@ -0,0 +1,4 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains errors. + +package data_storage diff --git a/cmd/generate-bindings/solana/testdata/data_storage/events.go b/cmd/generate-bindings/solana/testdata/data_storage/events.go new file mode 100644 index 00000000..804e0344 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/events.go @@ -0,0 +1,93 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains parsers for the events defined in the IDL. + +package data_storage + +import ( + "fmt" + binary "github.com/gagliardetto/binary" +) + +func ParseAnyEvent(eventData []byte) (any, error) { + decoder := binary.NewBorshDecoder(eventData) + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return nil, fmt.Errorf("failed to peek event discriminator: %w", err) + } + switch discriminator { + case Event_AccessLogged: + value := new(AccessLogged) + err := value.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal event as AccessLogged: %w", err) + } + return value, nil + case Event_DynamicEvent: + value := new(DynamicEvent) + err := value.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal event as DynamicEvent: %w", err) + } + return value, nil + case Event_NoFields: + value := new(NoFields) + err := value.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal event as NoFields: %w", err) + } + return value, nil + default: + return nil, fmt.Errorf("unknown discriminator: %s", binary.FormatDiscriminator(discriminator)) + } +} + +func ParseEvent_AccessLogged(eventData []byte) (*AccessLogged, error) { + decoder := binary.NewBorshDecoder(eventData) + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return nil, fmt.Errorf("failed to peek discriminator: %w", err) + } + if discriminator != Event_AccessLogged { + return nil, fmt.Errorf("expected discriminator %v, got %s", Event_AccessLogged, binary.FormatDiscriminator(discriminator)) + } + event := new(AccessLogged) + err = event.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal event of type AccessLogged: %w", err) + } + return event, nil +} + +func ParseEvent_DynamicEvent(eventData []byte) (*DynamicEvent, error) { + decoder := binary.NewBorshDecoder(eventData) + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return nil, fmt.Errorf("failed to peek discriminator: %w", err) + } + if discriminator != Event_DynamicEvent { + return nil, fmt.Errorf("expected discriminator %v, got %s", Event_DynamicEvent, binary.FormatDiscriminator(discriminator)) + } + event := new(DynamicEvent) + err = event.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal event of type DynamicEvent: %w", err) + } + return event, nil +} + +func ParseEvent_NoFields(eventData []byte) (*NoFields, error) { + decoder := binary.NewBorshDecoder(eventData) + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return nil, fmt.Errorf("failed to peek discriminator: %w", err) + } + if discriminator != Event_NoFields { + return nil, fmt.Errorf("expected discriminator %v, got %s", Event_NoFields, binary.FormatDiscriminator(discriminator)) + } + event := new(NoFields) + err = event.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal event of type NoFields: %w", err) + } + return event, nil +} diff --git a/cmd/generate-bindings/solana/testdata/data_storage/fetchers.go b/cmd/generate-bindings/solana/testdata/data_storage/fetchers.go new file mode 100644 index 00000000..606a2030 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/fetchers.go @@ -0,0 +1,4 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains fetcher functions. + +package data_storage diff --git a/cmd/generate-bindings/solana/testdata/data_storage/instructions.go b/cmd/generate-bindings/solana/testdata/data_storage/instructions.go new file mode 100644 index 00000000..1a88b7ab --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/instructions.go @@ -0,0 +1,1204 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains instructions and instruction parsers. + +package data_storage + +import ( + "bytes" + "fmt" + errors "github.com/gagliardetto/anchor-go/errors" + binary "github.com/gagliardetto/binary" + solanago "github.com/gagliardetto/solana-go" +) + +// Builds a "get_multiple_reserves" instruction. +func NewGetMultipleReservesInstruction() (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_GetMultipleReserves[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + accounts__ := solanago.AccountMetaSlice{} + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "get_reserves" instruction. +func NewGetReservesInstruction() (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_GetReserves[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + accounts__ := solanago.AccountMetaSlice{} + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "get_tuple_reserves" instruction. +func NewGetTupleReservesInstruction() (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_GetTupleReserves[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + accounts__ := solanago.AccountMetaSlice{} + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "initialize_data_account" instruction. +func NewInitializeDataAccountInstruction( + // Params: + inputParam UserData, + + // Accounts: + dataAccountAccount solanago.PublicKey, + userAccount solanago.PublicKey, + systemProgramAccount solanago.PublicKey, +) (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_InitializeDataAccount[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + { + // Serialize `inputParam`: + err = enc__.Encode(inputParam) + if err != nil { + return nil, errors.NewField("inputParam", err) + } + } + accounts__ := solanago.AccountMetaSlice{} + + // Add the accounts to the instruction. + { + // Account 0 "data_account": Writable, Non-signer, Required + accounts__.Append(solanago.NewAccountMeta(dataAccountAccount, true, false)) + // Account 1 "user": Writable, Signer, Required + accounts__.Append(solanago.NewAccountMeta(userAccount, true, true)) + // Account 2 "system_program": Read-only, Non-signer, Required + accounts__.Append(solanago.NewAccountMeta(systemProgramAccount, false, false)) + } + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "log_access" instruction. +func NewLogAccessInstruction( + // Params: + messageParam string, + + // Accounts: + userAccount solanago.PublicKey, +) (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_LogAccess[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + { + // Serialize `messageParam`: + err = enc__.Encode(messageParam) + if err != nil { + return nil, errors.NewField("messageParam", err) + } + } + accounts__ := solanago.AccountMetaSlice{} + + // Add the accounts to the instruction. + { + // Account 0 "user": Read-only, Signer, Required + accounts__.Append(solanago.NewAccountMeta(userAccount, false, true)) + } + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "on_report" instruction. +func NewOnReportInstruction( + // Params: + metadataParam []byte, + payloadParam []byte, + + // Accounts: + userAccount solanago.PublicKey, + dataAccountAccount solanago.PublicKey, + systemProgramAccount solanago.PublicKey, +) (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_OnReport[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + { + // Serialize `metadataParam`: + err = enc__.Encode(metadataParam) + if err != nil { + return nil, errors.NewField("metadataParam", err) + } + // Serialize `payloadParam`: + err = enc__.Encode(payloadParam) + if err != nil { + return nil, errors.NewField("payloadParam", err) + } + } + accounts__ := solanago.AccountMetaSlice{} + + // Add the accounts to the instruction. + { + // Account 0 "user": Writable, Signer, Required + accounts__.Append(solanago.NewAccountMeta(userAccount, true, true)) + // Account 1 "data_account": Writable, Non-signer, Required + accounts__.Append(solanago.NewAccountMeta(dataAccountAccount, true, false)) + // Account 2 "system_program": Read-only, Non-signer, Required + accounts__.Append(solanago.NewAccountMeta(systemProgramAccount, false, false)) + } + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "update_key_value_data" instruction. +func NewUpdateKeyValueDataInstruction( + // Params: + keyParam string, + valueParam string, + + // Accounts: + userAccount solanago.PublicKey, + dataAccountAccount solanago.PublicKey, +) (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_UpdateKeyValueData[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + { + // Serialize `keyParam`: + err = enc__.Encode(keyParam) + if err != nil { + return nil, errors.NewField("keyParam", err) + } + // Serialize `valueParam`: + err = enc__.Encode(valueParam) + if err != nil { + return nil, errors.NewField("valueParam", err) + } + } + accounts__ := solanago.AccountMetaSlice{} + + // Add the accounts to the instruction. + { + // Account 0 "user": Writable, Signer, Required + accounts__.Append(solanago.NewAccountMeta(userAccount, true, true)) + // Account 1 "data_account": Writable, Non-signer, Required + accounts__.Append(solanago.NewAccountMeta(dataAccountAccount, true, false)) + } + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +// Builds a "update_user_data" instruction. +func NewUpdateUserDataInstruction( + // Params: + inputParam UserData, + + // Accounts: + userAccount solanago.PublicKey, + dataAccountAccount solanago.PublicKey, +) (solanago.Instruction, error) { + buf__ := new(bytes.Buffer) + enc__ := binary.NewBorshEncoder(buf__) + + // Encode the instruction discriminator. + err := enc__.WriteBytes(Instruction_UpdateUserData[:], false) + if err != nil { + return nil, fmt.Errorf("failed to write instruction discriminator: %w", err) + } + { + // Serialize `inputParam`: + err = enc__.Encode(inputParam) + if err != nil { + return nil, errors.NewField("inputParam", err) + } + } + accounts__ := solanago.AccountMetaSlice{} + + // Add the accounts to the instruction. + { + // Account 0 "user": Writable, Signer, Required + accounts__.Append(solanago.NewAccountMeta(userAccount, true, true)) + // Account 1 "data_account": Writable, Non-signer, Required + accounts__.Append(solanago.NewAccountMeta(dataAccountAccount, true, false)) + } + + // Create the instruction. + return solanago.NewInstruction( + ProgramID, + accounts__, + buf__.Bytes(), + ), nil +} + +type GetMultipleReservesInstruction struct{} + +func (obj *GetMultipleReservesInstruction) GetDiscriminator() []byte { + return Instruction_GetMultipleReserves[:] +} + +// UnmarshalWithDecoder unmarshals the GetMultipleReservesInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *GetMultipleReservesInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "GetMultipleReservesInstruction", err) + } + if discriminator != Instruction_GetMultipleReserves { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "GetMultipleReservesInstruction", Instruction_GetMultipleReserves, discriminator) + } + return nil +} + +func (obj *GetMultipleReservesInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + return []uint8{}, nil +} + +func (obj *GetMultipleReservesInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + return nil +} + +func (obj *GetMultipleReservesInstruction) GetAccountKeys() []solanago.PublicKey { + return []solanago.PublicKey{} +} + +// Unmarshal unmarshals the GetMultipleReservesInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *GetMultipleReservesInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling GetMultipleReservesInstruction: %w", err) + } + return nil +} + +// UnmarshalGetMultipleReservesInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalGetMultipleReservesInstruction(buf []byte) (*GetMultipleReservesInstruction, error) { + obj := new(GetMultipleReservesInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type GetReservesInstruction struct{} + +func (obj *GetReservesInstruction) GetDiscriminator() []byte { + return Instruction_GetReserves[:] +} + +// UnmarshalWithDecoder unmarshals the GetReservesInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *GetReservesInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "GetReservesInstruction", err) + } + if discriminator != Instruction_GetReserves { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "GetReservesInstruction", Instruction_GetReserves, discriminator) + } + return nil +} + +func (obj *GetReservesInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + return []uint8{}, nil +} + +func (obj *GetReservesInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + return nil +} + +func (obj *GetReservesInstruction) GetAccountKeys() []solanago.PublicKey { + return []solanago.PublicKey{} +} + +// Unmarshal unmarshals the GetReservesInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *GetReservesInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling GetReservesInstruction: %w", err) + } + return nil +} + +// UnmarshalGetReservesInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalGetReservesInstruction(buf []byte) (*GetReservesInstruction, error) { + obj := new(GetReservesInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type GetTupleReservesInstruction struct{} + +func (obj *GetTupleReservesInstruction) GetDiscriminator() []byte { + return Instruction_GetTupleReserves[:] +} + +// UnmarshalWithDecoder unmarshals the GetTupleReservesInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *GetTupleReservesInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "GetTupleReservesInstruction", err) + } + if discriminator != Instruction_GetTupleReserves { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "GetTupleReservesInstruction", Instruction_GetTupleReserves, discriminator) + } + return nil +} + +func (obj *GetTupleReservesInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + return []uint8{}, nil +} + +func (obj *GetTupleReservesInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + return nil +} + +func (obj *GetTupleReservesInstruction) GetAccountKeys() []solanago.PublicKey { + return []solanago.PublicKey{} +} + +// Unmarshal unmarshals the GetTupleReservesInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *GetTupleReservesInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling GetTupleReservesInstruction: %w", err) + } + return nil +} + +// UnmarshalGetTupleReservesInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalGetTupleReservesInstruction(buf []byte) (*GetTupleReservesInstruction, error) { + obj := new(GetTupleReservesInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type InitializeDataAccountInstruction struct { + Input UserData `json:"input"` + + // Accounts: + DataAccount solanago.PublicKey `json:"data_account"` + DataAccountWritable bool `json:"data_account_writable"` + User solanago.PublicKey `json:"user"` + UserWritable bool `json:"user_writable"` + UserSigner bool `json:"user_signer"` + SystemProgram solanago.PublicKey `json:"system_program"` +} + +func (obj *InitializeDataAccountInstruction) GetDiscriminator() []byte { + return Instruction_InitializeDataAccount[:] +} + +// UnmarshalWithDecoder unmarshals the InitializeDataAccountInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *InitializeDataAccountInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + var err error + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "InitializeDataAccountInstruction", err) + } + if discriminator != Instruction_InitializeDataAccount { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "InitializeDataAccountInstruction", Instruction_InitializeDataAccount, discriminator) + } + // Deserialize `Input`: + err = decoder.Decode(&obj.Input) + if err != nil { + return err + } + return nil +} + +func (obj *InitializeDataAccountInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + // UnmarshalAccountIndices decodes account indices from Borsh-encoded bytes + decoder := binary.NewBorshDecoder(buf) + indices := make([]uint8, 0) + index := uint8(0) + var err error + // Decode from data_account account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "data_account", err) + } + indices = append(indices, index) + // Decode from user account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "user", err) + } + indices = append(indices, index) + // Decode from system_program account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "system_program", err) + } + indices = append(indices, index) + return indices, nil +} + +func (obj *InitializeDataAccountInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + // PopulateFromAccountIndices sets account public keys from indices and account keys array + if len(indices) != 3 { + return fmt.Errorf("mismatch between expected accounts (%d) and provided indices (%d)", 3, len(indices)) + } + indexOffset := 0 + // Set data_account account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "data_account", len(accountKeys)-1) + } + obj.DataAccount = accountKeys[indices[indexOffset]] + indexOffset++ + // Set user account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "user", len(accountKeys)-1) + } + obj.User = accountKeys[indices[indexOffset]] + indexOffset++ + // Set system_program account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "system_program", len(accountKeys)-1) + } + obj.SystemProgram = accountKeys[indices[indexOffset]] + indexOffset++ + return nil +} + +func (obj *InitializeDataAccountInstruction) GetAccountKeys() []solanago.PublicKey { + keys := make([]solanago.PublicKey, 0) + keys = append(keys, obj.DataAccount) + keys = append(keys, obj.User) + keys = append(keys, obj.SystemProgram) + return keys +} + +// Unmarshal unmarshals the InitializeDataAccountInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *InitializeDataAccountInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling InitializeDataAccountInstruction: %w", err) + } + return nil +} + +// UnmarshalInitializeDataAccountInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalInitializeDataAccountInstruction(buf []byte) (*InitializeDataAccountInstruction, error) { + obj := new(InitializeDataAccountInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type LogAccessInstruction struct { + Message string `json:"message"` + + // Accounts: + User solanago.PublicKey `json:"user"` + UserSigner bool `json:"user_signer"` +} + +func (obj *LogAccessInstruction) GetDiscriminator() []byte { + return Instruction_LogAccess[:] +} + +// UnmarshalWithDecoder unmarshals the LogAccessInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *LogAccessInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + var err error + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "LogAccessInstruction", err) + } + if discriminator != Instruction_LogAccess { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "LogAccessInstruction", Instruction_LogAccess, discriminator) + } + // Deserialize `Message`: + err = decoder.Decode(&obj.Message) + if err != nil { + return err + } + return nil +} + +func (obj *LogAccessInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + // UnmarshalAccountIndices decodes account indices from Borsh-encoded bytes + decoder := binary.NewBorshDecoder(buf) + indices := make([]uint8, 0) + index := uint8(0) + var err error + // Decode from user account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "user", err) + } + indices = append(indices, index) + return indices, nil +} + +func (obj *LogAccessInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + // PopulateFromAccountIndices sets account public keys from indices and account keys array + if len(indices) != 1 { + return fmt.Errorf("mismatch between expected accounts (%d) and provided indices (%d)", 1, len(indices)) + } + indexOffset := 0 + // Set user account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "user", len(accountKeys)-1) + } + obj.User = accountKeys[indices[indexOffset]] + indexOffset++ + return nil +} + +func (obj *LogAccessInstruction) GetAccountKeys() []solanago.PublicKey { + keys := make([]solanago.PublicKey, 0) + keys = append(keys, obj.User) + return keys +} + +// Unmarshal unmarshals the LogAccessInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *LogAccessInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling LogAccessInstruction: %w", err) + } + return nil +} + +// UnmarshalLogAccessInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalLogAccessInstruction(buf []byte) (*LogAccessInstruction, error) { + obj := new(LogAccessInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type OnReportInstruction struct { + Metadata []byte `json:"_metadata"` + Payload []byte `json:"payload"` + + // Accounts: + User solanago.PublicKey `json:"user"` + UserWritable bool `json:"user_writable"` + UserSigner bool `json:"user_signer"` + DataAccount solanago.PublicKey `json:"data_account"` + DataAccountWritable bool `json:"data_account_writable"` + SystemProgram solanago.PublicKey `json:"system_program"` +} + +func (obj *OnReportInstruction) GetDiscriminator() []byte { + return Instruction_OnReport[:] +} + +// UnmarshalWithDecoder unmarshals the OnReportInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *OnReportInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + var err error + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "OnReportInstruction", err) + } + if discriminator != Instruction_OnReport { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "OnReportInstruction", Instruction_OnReport, discriminator) + } + // Deserialize `Metadata`: + err = decoder.Decode(&obj.Metadata) + if err != nil { + return err + } + // Deserialize `Payload`: + err = decoder.Decode(&obj.Payload) + if err != nil { + return err + } + return nil +} + +func (obj *OnReportInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + // UnmarshalAccountIndices decodes account indices from Borsh-encoded bytes + decoder := binary.NewBorshDecoder(buf) + indices := make([]uint8, 0) + index := uint8(0) + var err error + // Decode from user account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "user", err) + } + indices = append(indices, index) + // Decode from data_account account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "data_account", err) + } + indices = append(indices, index) + // Decode from system_program account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "system_program", err) + } + indices = append(indices, index) + return indices, nil +} + +func (obj *OnReportInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + // PopulateFromAccountIndices sets account public keys from indices and account keys array + if len(indices) != 3 { + return fmt.Errorf("mismatch between expected accounts (%d) and provided indices (%d)", 3, len(indices)) + } + indexOffset := 0 + // Set user account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "user", len(accountKeys)-1) + } + obj.User = accountKeys[indices[indexOffset]] + indexOffset++ + // Set data_account account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "data_account", len(accountKeys)-1) + } + obj.DataAccount = accountKeys[indices[indexOffset]] + indexOffset++ + // Set system_program account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "system_program", len(accountKeys)-1) + } + obj.SystemProgram = accountKeys[indices[indexOffset]] + indexOffset++ + return nil +} + +func (obj *OnReportInstruction) GetAccountKeys() []solanago.PublicKey { + keys := make([]solanago.PublicKey, 0) + keys = append(keys, obj.User) + keys = append(keys, obj.DataAccount) + keys = append(keys, obj.SystemProgram) + return keys +} + +// Unmarshal unmarshals the OnReportInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *OnReportInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling OnReportInstruction: %w", err) + } + return nil +} + +// UnmarshalOnReportInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalOnReportInstruction(buf []byte) (*OnReportInstruction, error) { + obj := new(OnReportInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type UpdateKeyValueDataInstruction struct { + Key string `json:"key"` + Value string `json:"value"` + + // Accounts: + User solanago.PublicKey `json:"user"` + UserWritable bool `json:"user_writable"` + UserSigner bool `json:"user_signer"` + DataAccount solanago.PublicKey `json:"data_account"` + DataAccountWritable bool `json:"data_account_writable"` +} + +func (obj *UpdateKeyValueDataInstruction) GetDiscriminator() []byte { + return Instruction_UpdateKeyValueData[:] +} + +// UnmarshalWithDecoder unmarshals the UpdateKeyValueDataInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *UpdateKeyValueDataInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + var err error + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "UpdateKeyValueDataInstruction", err) + } + if discriminator != Instruction_UpdateKeyValueData { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "UpdateKeyValueDataInstruction", Instruction_UpdateKeyValueData, discriminator) + } + // Deserialize `Key`: + err = decoder.Decode(&obj.Key) + if err != nil { + return err + } + // Deserialize `Value`: + err = decoder.Decode(&obj.Value) + if err != nil { + return err + } + return nil +} + +func (obj *UpdateKeyValueDataInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + // UnmarshalAccountIndices decodes account indices from Borsh-encoded bytes + decoder := binary.NewBorshDecoder(buf) + indices := make([]uint8, 0) + index := uint8(0) + var err error + // Decode from user account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "user", err) + } + indices = append(indices, index) + // Decode from data_account account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "data_account", err) + } + indices = append(indices, index) + return indices, nil +} + +func (obj *UpdateKeyValueDataInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + // PopulateFromAccountIndices sets account public keys from indices and account keys array + if len(indices) != 2 { + return fmt.Errorf("mismatch between expected accounts (%d) and provided indices (%d)", 2, len(indices)) + } + indexOffset := 0 + // Set user account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "user", len(accountKeys)-1) + } + obj.User = accountKeys[indices[indexOffset]] + indexOffset++ + // Set data_account account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "data_account", len(accountKeys)-1) + } + obj.DataAccount = accountKeys[indices[indexOffset]] + indexOffset++ + return nil +} + +func (obj *UpdateKeyValueDataInstruction) GetAccountKeys() []solanago.PublicKey { + keys := make([]solanago.PublicKey, 0) + keys = append(keys, obj.User) + keys = append(keys, obj.DataAccount) + return keys +} + +// Unmarshal unmarshals the UpdateKeyValueDataInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *UpdateKeyValueDataInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling UpdateKeyValueDataInstruction: %w", err) + } + return nil +} + +// UnmarshalUpdateKeyValueDataInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalUpdateKeyValueDataInstruction(buf []byte) (*UpdateKeyValueDataInstruction, error) { + obj := new(UpdateKeyValueDataInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +type UpdateUserDataInstruction struct { + Input UserData `json:"input"` + + // Accounts: + User solanago.PublicKey `json:"user"` + UserWritable bool `json:"user_writable"` + UserSigner bool `json:"user_signer"` + DataAccount solanago.PublicKey `json:"data_account"` + DataAccountWritable bool `json:"data_account_writable"` +} + +func (obj *UpdateUserDataInstruction) GetDiscriminator() []byte { + return Instruction_UpdateUserData[:] +} + +// UnmarshalWithDecoder unmarshals the UpdateUserDataInstruction from Borsh-encoded bytes prefixed with its discriminator. +func (obj *UpdateUserDataInstruction) UnmarshalWithDecoder(decoder *binary.Decoder) error { + var err error + // Read the discriminator and check it against the expected value: + discriminator, err := decoder.ReadDiscriminator() + if err != nil { + return fmt.Errorf("failed to read instruction discriminator for %s: %w", "UpdateUserDataInstruction", err) + } + if discriminator != Instruction_UpdateUserData { + return fmt.Errorf("instruction discriminator mismatch for %s: expected %s, got %s", "UpdateUserDataInstruction", Instruction_UpdateUserData, discriminator) + } + // Deserialize `Input`: + err = decoder.Decode(&obj.Input) + if err != nil { + return err + } + return nil +} + +func (obj *UpdateUserDataInstruction) UnmarshalAccountIndices(buf []byte) ([]uint8, error) { + // UnmarshalAccountIndices decodes account indices from Borsh-encoded bytes + decoder := binary.NewBorshDecoder(buf) + indices := make([]uint8, 0) + index := uint8(0) + var err error + // Decode from user account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "user", err) + } + indices = append(indices, index) + // Decode from data_account account index + index = uint8(0) + err = decoder.Decode(&index) + if err != nil { + return nil, fmt.Errorf("failed to decode %s account index: %w", "data_account", err) + } + indices = append(indices, index) + return indices, nil +} + +func (obj *UpdateUserDataInstruction) PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error { + // PopulateFromAccountIndices sets account public keys from indices and account keys array + if len(indices) != 2 { + return fmt.Errorf("mismatch between expected accounts (%d) and provided indices (%d)", 2, len(indices)) + } + indexOffset := 0 + // Set user account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "user", len(accountKeys)-1) + } + obj.User = accountKeys[indices[indexOffset]] + indexOffset++ + // Set data_account account from index + if indices[indexOffset] >= uint8(len(accountKeys)) { + return fmt.Errorf("account index %d for %s is out of bounds (max: %d)", indices[indexOffset], "data_account", len(accountKeys)-1) + } + obj.DataAccount = accountKeys[indices[indexOffset]] + indexOffset++ + return nil +} + +func (obj *UpdateUserDataInstruction) GetAccountKeys() []solanago.PublicKey { + keys := make([]solanago.PublicKey, 0) + keys = append(keys, obj.User) + keys = append(keys, obj.DataAccount) + return keys +} + +// Unmarshal unmarshals the UpdateUserDataInstruction from Borsh-encoded bytes prefixed with the discriminator. +func (obj *UpdateUserDataInstruction) Unmarshal(buf []byte) error { + var err error + err = obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling UpdateUserDataInstruction: %w", err) + } + return nil +} + +// UnmarshalUpdateUserDataInstruction unmarshals the instruction from Borsh-encoded bytes prefixed with the discriminator. +func UnmarshalUpdateUserDataInstruction(buf []byte) (*UpdateUserDataInstruction, error) { + obj := new(UpdateUserDataInstruction) + var err error + err = obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +// Instruction interface defines common methods for all instruction types +type Instruction interface { + GetDiscriminator() []byte + + UnmarshalWithDecoder(decoder *binary.Decoder) error + + UnmarshalAccountIndices(buf []byte) ([]uint8, error) + + PopulateFromAccountIndices(indices []uint8, accountKeys []solanago.PublicKey) error + + GetAccountKeys() []solanago.PublicKey +} + +// ParseInstruction parses instruction data and optionally populates accounts +// If accountIndicesData is nil or empty, accounts will not be populated +func ParseInstruction(instructionData []byte, accountIndicesData []byte, accountKeys []solanago.PublicKey) (Instruction, error) { + // Validate inputs + if len(instructionData) < 8 { + return nil, fmt.Errorf("instruction data too short: expected at least 8 bytes, got %d", len(instructionData)) + } + // Extract discriminator (TypeID for consistent equality with generated constants) + discriminator := binary.TypeIDFromBytes(instructionData[0:8]) + // Parse based on discriminator + switch discriminator { + case Instruction_GetMultipleReserves: + instruction := new(GetMultipleReservesInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as GetMultipleReservesInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_GetReserves: + instruction := new(GetReservesInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as GetReservesInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_GetTupleReserves: + instruction := new(GetTupleReservesInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as GetTupleReservesInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_InitializeDataAccount: + instruction := new(InitializeDataAccountInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as InitializeDataAccountInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_LogAccess: + instruction := new(LogAccessInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as LogAccessInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_OnReport: + instruction := new(OnReportInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as OnReportInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_UpdateKeyValueData: + instruction := new(UpdateKeyValueDataInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as UpdateKeyValueDataInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + case Instruction_UpdateUserData: + instruction := new(UpdateUserDataInstruction) + decoder := binary.NewBorshDecoder(instructionData) + err := instruction.UnmarshalWithDecoder(decoder) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instruction as UpdateUserDataInstruction: %w", err) + } + if accountIndicesData != nil && len(accountIndicesData) > 0 { + indices, err := instruction.UnmarshalAccountIndices(accountIndicesData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal account indices: %w", err) + } + err = instruction.PopulateFromAccountIndices(indices, accountKeys) + if err != nil { + return nil, fmt.Errorf("failed to populate accounts: %w", err) + } + } + return instruction, nil + default: + return nil, fmt.Errorf("unknown instruction discriminator: %s", binary.FormatDiscriminator(discriminator)) + } +} + +// ParseInstructionTyped parses instruction data and returns a specific instruction type // T must implement the Instruction interface +func ParseInstructionTyped[T Instruction](instructionData []byte, accountIndicesData []byte, accountKeys []solanago.PublicKey) (T, error) { + instruction, err := ParseInstruction(instructionData, accountIndicesData, accountKeys) + if err != nil { + return *new(T), err + } + typed, ok := instruction.(T) + if !ok { + return *new(T), fmt.Errorf("instruction is not of expected type") + } + return typed, nil +} + +// ParseInstructionWithoutAccounts parses instruction data without account information +func ParseInstructionWithoutAccounts(instructionData []byte) (Instruction, error) { + return ParseInstruction(instructionData, nil, []solanago.PublicKey{}) +} + +// ParseInstructionWithAccounts parses instruction data with account information +func ParseInstructionWithAccounts(instructionData []byte, accountIndicesData []byte, accountKeys []solanago.PublicKey) (Instruction, error) { + return ParseInstruction(instructionData, accountIndicesData, accountKeys) +} diff --git a/cmd/generate-bindings/solana/testdata/data_storage/program_id.go b/cmd/generate-bindings/solana/testdata/data_storage/program_id.go new file mode 100644 index 00000000..1e0b7950 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/program_id.go @@ -0,0 +1,8 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains the program ID. + +package data_storage + +import solanago "github.com/gagliardetto/solana-go" + +var ProgramID = solanago.MustPublicKeyFromBase58("ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL") diff --git a/cmd/generate-bindings/solana/testdata/data_storage/tests_test.go b/cmd/generate-bindings/solana/testdata/data_storage/tests_test.go new file mode 100644 index 00000000..704cda06 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/tests_test.go @@ -0,0 +1,4 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains tests. + +package data_storage diff --git a/cmd/generate-bindings/solana/testdata/data_storage/types.go b/cmd/generate-bindings/solana/testdata/data_storage/types.go new file mode 100644 index 00000000..157f5199 --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/data_storage/types.go @@ -0,0 +1,763 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. +// This file contains parsers for the types defined in the IDL. + +package data_storage + +import ( + "bytes" + "fmt" + errors "github.com/gagliardetto/anchor-go/errors" + binary "github.com/gagliardetto/binary" + solanago "github.com/gagliardetto/solana-go" + sdk "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + bindings "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/bindings" + cre "github.com/smartcontractkit/cre-sdk-go/cre" +) + +type AccessLogged struct { + Caller solanago.PublicKey `json:"caller"` + Message string `json:"message"` +} + +func (obj AccessLogged) MarshalWithEncoder(encoder *binary.Encoder) (err error) { + // Serialize `Caller`: + err = encoder.Encode(obj.Caller) + if err != nil { + return errors.NewField("Caller", err) + } + // Serialize `Message`: + err = encoder.Encode(obj.Message) + if err != nil { + return errors.NewField("Message", err) + } + return nil +} + +func (obj AccessLogged) Marshal() ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := binary.NewBorshEncoder(buf) + err := obj.MarshalWithEncoder(encoder) + if err != nil { + return nil, fmt.Errorf("error while encoding AccessLogged: %w", err) + } + return buf.Bytes(), nil +} + +func (obj *AccessLogged) UnmarshalWithDecoder(decoder *binary.Decoder) (err error) { + // Deserialize `Caller`: + err = decoder.Decode(&obj.Caller) + if err != nil { + return errors.NewField("Caller", err) + } + // Deserialize `Message`: + err = decoder.Decode(&obj.Message) + if err != nil { + return errors.NewField("Message", err) + } + return nil +} + +func (obj *AccessLogged) Unmarshal(buf []byte) error { + err := obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling AccessLogged: %w", err) + } + return nil +} + +func UnmarshalAccessLogged(buf []byte) (*AccessLogged, error) { + obj := new(AccessLogged) + err := obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Codec) EncodeAccessLoggedStruct(in AccessLogged) ([]byte, error) { + return in.Marshal() +} + +// WriteReportFromAccessLogged encodes the input struct, hashes the provided accounts, // generates a signed report, and submits it via WriteReport. // // remainingAccounts must follow the keystone-forwarder account layout: // - Index 0: forwarderState – the forwarder program's state account. // - Index 1: forwarderAuthority – PDA derived from seeds // ["forwarder", forwarderState, receiverProgram] under the forwarder program ID. // - Index 2+: receiver-specific accounts required by the target program. // // The full slice is hashed (via CalculateAccountsHash) into the report and forwarded // as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1 // before CPI-ing into the receiver, so they must be present and correctly ordered. +func (c *DataStorage) WriteReportFromAccessLogged( + runtime cre.Runtime, + input AccessLogged, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + encodedInput, err := c.Codec.EncodeAccessLoggedStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: encodedInput, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +func (c *DataStorage) WriteReportFromAccessLoggeds( + runtime cre.Runtime, + inputs []AccessLogged, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + elements := make([][]byte, len(inputs)) + for i, input := range inputs { + encoded, err := c.Codec.EncodeAccessLoggedStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + elements[i] = encoded + } + return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +} + +type DataAccount struct { + Sender string `json:"sender"` + Key string `json:"key"` + Value string `json:"value"` +} + +func (obj DataAccount) MarshalWithEncoder(encoder *binary.Encoder) (err error) { + // Serialize `Sender`: + err = encoder.Encode(obj.Sender) + if err != nil { + return errors.NewField("Sender", err) + } + // Serialize `Key`: + err = encoder.Encode(obj.Key) + if err != nil { + return errors.NewField("Key", err) + } + // Serialize `Value`: + err = encoder.Encode(obj.Value) + if err != nil { + return errors.NewField("Value", err) + } + return nil +} + +func (obj DataAccount) Marshal() ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := binary.NewBorshEncoder(buf) + err := obj.MarshalWithEncoder(encoder) + if err != nil { + return nil, fmt.Errorf("error while encoding DataAccount: %w", err) + } + return buf.Bytes(), nil +} + +func (obj *DataAccount) UnmarshalWithDecoder(decoder *binary.Decoder) (err error) { + // Deserialize `Sender`: + err = decoder.Decode(&obj.Sender) + if err != nil { + return errors.NewField("Sender", err) + } + // Deserialize `Key`: + err = decoder.Decode(&obj.Key) + if err != nil { + return errors.NewField("Key", err) + } + // Deserialize `Value`: + err = decoder.Decode(&obj.Value) + if err != nil { + return errors.NewField("Value", err) + } + return nil +} + +func (obj *DataAccount) Unmarshal(buf []byte) error { + err := obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling DataAccount: %w", err) + } + return nil +} + +func UnmarshalDataAccount(buf []byte) (*DataAccount, error) { + obj := new(DataAccount) + err := obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Codec) EncodeDataAccountStruct(in DataAccount) ([]byte, error) { + return in.Marshal() +} + +// WriteReportFromDataAccount encodes the input struct, hashes the provided accounts, // generates a signed report, and submits it via WriteReport. // // remainingAccounts must follow the keystone-forwarder account layout: // - Index 0: forwarderState – the forwarder program's state account. // - Index 1: forwarderAuthority – PDA derived from seeds // ["forwarder", forwarderState, receiverProgram] under the forwarder program ID. // - Index 2+: receiver-specific accounts required by the target program. // // The full slice is hashed (via CalculateAccountsHash) into the report and forwarded // as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1 // before CPI-ing into the receiver, so they must be present and correctly ordered. +func (c *DataStorage) WriteReportFromDataAccount( + runtime cre.Runtime, + input DataAccount, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + encodedInput, err := c.Codec.EncodeDataAccountStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: encodedInput, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +func (c *DataStorage) WriteReportFromDataAccounts( + runtime cre.Runtime, + inputs []DataAccount, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + elements := make([][]byte, len(inputs)) + for i, input := range inputs { + encoded, err := c.Codec.EncodeDataAccountStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + elements[i] = encoded + } + return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +} + +type DynamicEvent struct { + Key string `json:"key"` + UserData UserData `json:"user_data"` + Sender string `json:"sender"` + Metadata []byte `json:"metadata"` + MetadataArray [][]byte `json:"metadata_array"` +} + +func (obj DynamicEvent) MarshalWithEncoder(encoder *binary.Encoder) (err error) { + // Serialize `Key`: + err = encoder.Encode(obj.Key) + if err != nil { + return errors.NewField("Key", err) + } + // Serialize `UserData`: + err = encoder.Encode(obj.UserData) + if err != nil { + return errors.NewField("UserData", err) + } + // Serialize `Sender`: + err = encoder.Encode(obj.Sender) + if err != nil { + return errors.NewField("Sender", err) + } + // Serialize `Metadata`: + err = encoder.Encode(obj.Metadata) + if err != nil { + return errors.NewField("Metadata", err) + } + // Serialize `MetadataArray`: + err = encoder.Encode(obj.MetadataArray) + if err != nil { + return errors.NewField("MetadataArray", err) + } + return nil +} + +func (obj DynamicEvent) Marshal() ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := binary.NewBorshEncoder(buf) + err := obj.MarshalWithEncoder(encoder) + if err != nil { + return nil, fmt.Errorf("error while encoding DynamicEvent: %w", err) + } + return buf.Bytes(), nil +} + +func (obj *DynamicEvent) UnmarshalWithDecoder(decoder *binary.Decoder) (err error) { + // Deserialize `Key`: + err = decoder.Decode(&obj.Key) + if err != nil { + return errors.NewField("Key", err) + } + // Deserialize `UserData`: + err = decoder.Decode(&obj.UserData) + if err != nil { + return errors.NewField("UserData", err) + } + // Deserialize `Sender`: + err = decoder.Decode(&obj.Sender) + if err != nil { + return errors.NewField("Sender", err) + } + // Deserialize `Metadata`: + err = decoder.Decode(&obj.Metadata) + if err != nil { + return errors.NewField("Metadata", err) + } + // Deserialize `MetadataArray`: + err = decoder.Decode(&obj.MetadataArray) + if err != nil { + return errors.NewField("MetadataArray", err) + } + return nil +} + +func (obj *DynamicEvent) Unmarshal(buf []byte) error { + err := obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling DynamicEvent: %w", err) + } + return nil +} + +func UnmarshalDynamicEvent(buf []byte) (*DynamicEvent, error) { + obj := new(DynamicEvent) + err := obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Codec) EncodeDynamicEventStruct(in DynamicEvent) ([]byte, error) { + return in.Marshal() +} + +// WriteReportFromDynamicEvent encodes the input struct, hashes the provided accounts, // generates a signed report, and submits it via WriteReport. // // remainingAccounts must follow the keystone-forwarder account layout: // - Index 0: forwarderState – the forwarder program's state account. // - Index 1: forwarderAuthority – PDA derived from seeds // ["forwarder", forwarderState, receiverProgram] under the forwarder program ID. // - Index 2+: receiver-specific accounts required by the target program. // // The full slice is hashed (via CalculateAccountsHash) into the report and forwarded // as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1 // before CPI-ing into the receiver, so they must be present and correctly ordered. +func (c *DataStorage) WriteReportFromDynamicEvent( + runtime cre.Runtime, + input DynamicEvent, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + encodedInput, err := c.Codec.EncodeDynamicEventStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: encodedInput, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +func (c *DataStorage) WriteReportFromDynamicEvents( + runtime cre.Runtime, + inputs []DynamicEvent, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + elements := make([][]byte, len(inputs)) + for i, input := range inputs { + encoded, err := c.Codec.EncodeDynamicEventStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + elements[i] = encoded + } + return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +} + +type NoFields struct{} + +func (obj NoFields) MarshalWithEncoder(encoder *binary.Encoder) (err error) { + return nil +} + +func (obj NoFields) Marshal() ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := binary.NewBorshEncoder(buf) + err := obj.MarshalWithEncoder(encoder) + if err != nil { + return nil, fmt.Errorf("error while encoding NoFields: %w", err) + } + return buf.Bytes(), nil +} + +func (obj *NoFields) UnmarshalWithDecoder(decoder *binary.Decoder) (err error) { + return nil +} + +func (obj *NoFields) Unmarshal(buf []byte) error { + err := obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling NoFields: %w", err) + } + return nil +} + +func UnmarshalNoFields(buf []byte) (*NoFields, error) { + obj := new(NoFields) + err := obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Codec) EncodeNoFieldsStruct(in NoFields) ([]byte, error) { + return in.Marshal() +} + +// WriteReportFromNoFields encodes the input struct, hashes the provided accounts, // generates a signed report, and submits it via WriteReport. // // remainingAccounts must follow the keystone-forwarder account layout: // - Index 0: forwarderState – the forwarder program's state account. // - Index 1: forwarderAuthority – PDA derived from seeds // ["forwarder", forwarderState, receiverProgram] under the forwarder program ID. // - Index 2+: receiver-specific accounts required by the target program. // // The full slice is hashed (via CalculateAccountsHash) into the report and forwarded // as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1 // before CPI-ing into the receiver, so they must be present and correctly ordered. +func (c *DataStorage) WriteReportFromNoFields( + runtime cre.Runtime, + input NoFields, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + encodedInput, err := c.Codec.EncodeNoFieldsStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: encodedInput, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +func (c *DataStorage) WriteReportFromNoFieldss( + runtime cre.Runtime, + inputs []NoFields, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + elements := make([][]byte, len(inputs)) + for i, input := range inputs { + encoded, err := c.Codec.EncodeNoFieldsStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + elements[i] = encoded + } + return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +} + +type UpdateReserves struct { + TotalMinted uint64 `json:"total_minted"` + TotalReserve uint64 `json:"total_reserve"` +} + +func (obj UpdateReserves) MarshalWithEncoder(encoder *binary.Encoder) (err error) { + // Serialize `TotalMinted`: + err = encoder.Encode(obj.TotalMinted) + if err != nil { + return errors.NewField("TotalMinted", err) + } + // Serialize `TotalReserve`: + err = encoder.Encode(obj.TotalReserve) + if err != nil { + return errors.NewField("TotalReserve", err) + } + return nil +} + +func (obj UpdateReserves) Marshal() ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := binary.NewBorshEncoder(buf) + err := obj.MarshalWithEncoder(encoder) + if err != nil { + return nil, fmt.Errorf("error while encoding UpdateReserves: %w", err) + } + return buf.Bytes(), nil +} + +func (obj *UpdateReserves) UnmarshalWithDecoder(decoder *binary.Decoder) (err error) { + // Deserialize `TotalMinted`: + err = decoder.Decode(&obj.TotalMinted) + if err != nil { + return errors.NewField("TotalMinted", err) + } + // Deserialize `TotalReserve`: + err = decoder.Decode(&obj.TotalReserve) + if err != nil { + return errors.NewField("TotalReserve", err) + } + return nil +} + +func (obj *UpdateReserves) Unmarshal(buf []byte) error { + err := obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling UpdateReserves: %w", err) + } + return nil +} + +func UnmarshalUpdateReserves(buf []byte) (*UpdateReserves, error) { + obj := new(UpdateReserves) + err := obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Codec) EncodeUpdateReservesStruct(in UpdateReserves) ([]byte, error) { + return in.Marshal() +} + +// WriteReportFromUpdateReserves encodes the input struct, hashes the provided accounts, // generates a signed report, and submits it via WriteReport. // // remainingAccounts must follow the keystone-forwarder account layout: // - Index 0: forwarderState – the forwarder program's state account. // - Index 1: forwarderAuthority – PDA derived from seeds // ["forwarder", forwarderState, receiverProgram] under the forwarder program ID. // - Index 2+: receiver-specific accounts required by the target program. // // The full slice is hashed (via CalculateAccountsHash) into the report and forwarded // as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1 // before CPI-ing into the receiver, so they must be present and correctly ordered. +func (c *DataStorage) WriteReportFromUpdateReserves( + runtime cre.Runtime, + input UpdateReserves, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + encodedInput, err := c.Codec.EncodeUpdateReservesStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: encodedInput, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +func (c *DataStorage) WriteReportFromUpdateReservess( + runtime cre.Runtime, + inputs []UpdateReserves, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + elements := make([][]byte, len(inputs)) + for i, input := range inputs { + encoded, err := c.Codec.EncodeUpdateReservesStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + elements[i] = encoded + } + return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +} + +type UserData struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func (obj UserData) MarshalWithEncoder(encoder *binary.Encoder) (err error) { + // Serialize `Key`: + err = encoder.Encode(obj.Key) + if err != nil { + return errors.NewField("Key", err) + } + // Serialize `Value`: + err = encoder.Encode(obj.Value) + if err != nil { + return errors.NewField("Value", err) + } + return nil +} + +func (obj UserData) Marshal() ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := binary.NewBorshEncoder(buf) + err := obj.MarshalWithEncoder(encoder) + if err != nil { + return nil, fmt.Errorf("error while encoding UserData: %w", err) + } + return buf.Bytes(), nil +} + +func (obj *UserData) UnmarshalWithDecoder(decoder *binary.Decoder) (err error) { + // Deserialize `Key`: + err = decoder.Decode(&obj.Key) + if err != nil { + return errors.NewField("Key", err) + } + // Deserialize `Value`: + err = decoder.Decode(&obj.Value) + if err != nil { + return errors.NewField("Value", err) + } + return nil +} + +func (obj *UserData) Unmarshal(buf []byte) error { + err := obj.UnmarshalWithDecoder(binary.NewBorshDecoder(buf)) + if err != nil { + return fmt.Errorf("error while unmarshaling UserData: %w", err) + } + return nil +} + +func UnmarshalUserData(buf []byte) (*UserData, error) { + obj := new(UserData) + err := obj.Unmarshal(buf) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Codec) EncodeUserDataStruct(in UserData) ([]byte, error) { + return in.Marshal() +} + +// WriteReportFromUserData encodes the input struct, hashes the provided accounts, // generates a signed report, and submits it via WriteReport. // // remainingAccounts must follow the keystone-forwarder account layout: // - Index 0: forwarderState – the forwarder program's state account. // - Index 1: forwarderAuthority – PDA derived from seeds // ["forwarder", forwarderState, receiverProgram] under the forwarder program ID. // - Index 2+: receiver-specific accounts required by the target program. // // The full slice is hashed (via CalculateAccountsHash) into the report and forwarded // as WriteCreReportRequest.RemainingAccounts. The on-chain forwarder strips indices 0 and 1 // before CPI-ing into the receiver, so they must be present and correctly ordered. +func (c *DataStorage) WriteReportFromUserData( + runtime cre.Runtime, + input UserData, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + encodedInput, err := c.Codec.EncodeUserDataStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + encodedAccountList := bindings.CalculateAccountsHash(remainingAccounts) + + fwdReport := bindings.ForwarderReport{ + AccountHash: encodedAccountList, + Payload: encodedInput, + } + encodedFwdReport, err := fwdReport.Marshal() + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + + promise := runtime.GenerateReport(&sdk.ReportRequest{ + EncodedPayload: encodedFwdReport, + EncoderName: "solana", + HashingAlgo: "keccak256", + SigningAlgo: "ecdsa", + }) + + return cre.ThenPromise(promise, func(report *cre.Report) cre.Promise[*solana.WriteReportReply] { + return c.client.WriteReport(runtime, &solana.WriteCreReportRequest{ + ComputeConfig: computeConfig, + Receiver: ProgramID.Bytes(), + RemainingAccounts: remainingAccounts, + Report: report, + }) + }) +} + +func (c *DataStorage) WriteReportFromUserDatas( + runtime cre.Runtime, + inputs []UserData, + remainingAccounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { + elements := make([][]byte, len(inputs)) + for i, input := range inputs { + encoded, err := c.Codec.EncodeUserDataStruct(input) + if err != nil { + return cre.PromiseFromResult[*solana.WriteReportReply](nil, err) + } + elements[i] = encoded + } + return c.WriteReportFromBorshEncodedVec(runtime, elements, remainingAccounts, computeConfig) +} diff --git a/cmd/generate-bindings/solana/testdata/gen/main.go b/cmd/generate-bindings/solana/testdata/gen/main.go new file mode 100644 index 00000000..30dc025e --- /dev/null +++ b/cmd/generate-bindings/solana/testdata/gen/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + + "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/solana" +) + +func main() { + if err := solana.GenerateBindings( + "./testdata/contracts/idl/data_storage.json", + "data_storage", + "./testdata/data_storage", + ); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/root.go b/cmd/root.go index 88191efe..bc397aa1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -539,6 +539,8 @@ func isLoadSettings(cmd *cobra.Command) bool { "cre account list-key": {}, "cre init": {}, "cre generate-bindings": {}, + "cre generate-bindings evm": {}, + "cre generate-bindings solana": {}, "cre completion bash": {}, "cre completion fish": {}, "cre completion powershell": {}, @@ -570,28 +572,30 @@ func isLoadSettings(cmd *cobra.Command) bool { func isLoadCredentials(cmd *cobra.Command) bool { // It is not expected to have the credentials loaded when running the following commands var excludedCommands = map[string]struct{}{ - "cre version": {}, - "cre login": {}, - "cre logout": {}, - "cre completion bash": {}, - "cre completion fish": {}, - "cre completion powershell": {}, - "cre completion zsh": {}, - "cre help": {}, - "cre generate-bindings": {}, - "cre update": {}, - "cre workflow": {}, - "cre workflow limits": {}, - "cre workflow limits export": {}, - "cre account": {}, - "cre secrets": {}, - "cre workflow build": {}, - "cre workflow hash": {}, - "cre templates": {}, - "cre templates list": {}, - "cre templates add": {}, - "cre templates remove": {}, - "cre": {}, + "cre version": {}, + "cre login": {}, + "cre logout": {}, + "cre completion bash": {}, + "cre completion fish": {}, + "cre completion powershell": {}, + "cre completion zsh": {}, + "cre help": {}, + "cre generate-bindings": {}, + "cre generate-bindings evm": {}, + "cre generate-bindings solana": {}, + "cre update": {}, + "cre workflow": {}, + "cre workflow limits": {}, + "cre workflow limits export": {}, + "cre account": {}, + "cre secrets": {}, + "cre workflow build": {}, + "cre workflow hash": {}, + "cre templates": {}, + "cre templates list": {}, + "cre templates add": {}, + "cre templates remove": {}, + "cre": {}, } _, exists := excludedCommands[cmd.CommandPath()] diff --git a/docs/cre.md b/docs/cre.md index 3f77ed01..81092ba9 100644 --- a/docs/cre.md +++ b/docs/cre.md @@ -26,7 +26,7 @@ cre [optional flags] ### SEE ALSO * [cre account](cre_account.md) - Manage account and request deploy access -* [cre generate-bindings](cre_generate-bindings.md) - Generate bindings from contract ABI +* [cre generate-bindings](cre_generate-bindings.md) - Generate bindings for contracts * [cre init](cre_init.md) - Initialize a new cre project (recommended starting point) * [cre login](cre_login.md) - Start authentication flow * [cre logout](cre_logout.md) - Revoke authentication tokens and remove local credentials diff --git a/docs/cre_generate-bindings.md b/docs/cre_generate-bindings.md index 9b6bacf9..d1f5befc 100644 --- a/docs/cre_generate-bindings.md +++ b/docs/cre_generate-bindings.md @@ -1,37 +1,15 @@ ## cre generate-bindings -Generate bindings from contract ABI +Generate bindings for contracts ### Synopsis -This command generates bindings from contract ABI files. -Supports EVM chain family with Go and TypeScript languages. -The target language is auto-detected from project files, or can be -specified explicitly with --language. -Each contract gets its own package subdirectory to avoid naming conflicts. -For example, IERC20.abi generates bindings in generated/ierc20/ package. - -Both raw ABI files (*.abi) and JSON artifact files (*.json) are supported. -For JSON files the ABI is read from the top-level "abi" field. - -``` -cre generate-bindings [optional flags] -``` - -### Examples - -``` - cre generate-bindings evm -``` +The generate-bindings command allows you to generate bindings for contracts. ### Options ``` - -a, --abi string Path to ABI directory (defaults to contracts/{chain-family}/src/abi/). Supports *.abi and *.json files - -h, --help help for generate-bindings - -l, --language string Target language: go, typescript (auto-detected from project files when omitted) - -k, --pkg string Base package name (each contract gets its own subdirectory) (default "bindings") - -p, --project-root string Path to project root directory (defaults to current directory) + -h, --help help for generate-bindings ``` ### Options inherited from parent commands @@ -40,6 +18,7 @@ cre generate-bindings [optional flags] --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) -e, --env string Path to .env file which contains sensitive info --non-interactive Fail instead of prompting; requires all inputs via flags + -R, --project-root string Path to the project root -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config -T, --target string Use target settings from YAML config -v, --verbose Run command in VERBOSE mode @@ -48,4 +27,6 @@ cre generate-bindings [optional flags] ### SEE ALSO * [cre](cre.md) - CRE CLI tool +* [cre generate-bindings evm](cre_generate-bindings_evm.md) - Generate bindings from contract ABI +* [cre generate-bindings solana](cre_generate-bindings_solana.md) - Generate bindings from contract IDL diff --git a/docs/cre_generate-bindings_evm.md b/docs/cre_generate-bindings_evm.md new file mode 100644 index 00000000..bbe4558f --- /dev/null +++ b/docs/cre_generate-bindings_evm.md @@ -0,0 +1,51 @@ +## cre generate-bindings evm + +Generate bindings from contract ABI + +### Synopsis + +This command generates bindings from contract ABI files. +Supports EVM chain family with Go and TypeScript languages. +The target language is auto-detected from project files, or can be +specified explicitly with --language. +Each contract gets its own package subdirectory to avoid naming conflicts. +For example, IERC20.abi generates bindings in generated/ierc20/ package. + +Both raw ABI files (*.abi) and JSON artifact files (*.json) are supported. +For JSON files the ABI is read from the top-level "abi" field. + +``` +cre generate-bindings evm [optional flags] +``` + +### Examples + +``` + cre generate-bindings evm +``` + +### Options + +``` + -a, --abi string Path to ABI directory (defaults to contracts/evm/src/abi/). Supports *.abi and *.json files + -h, --help help for evm + -l, --language string Target language: go, typescript (auto-detected from project files when omitted) + -k, --pkg string Base package name (each contract gets its own subdirectory) (default "bindings") + -p, --project-root string Path to project root directory (defaults to current directory) +``` + +### Options inherited from parent commands + +``` + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode +``` + +### SEE ALSO + +* [cre generate-bindings](cre_generate-bindings.md) - Generate bindings for contracts + diff --git a/docs/cre_generate-bindings_solana.md b/docs/cre_generate-bindings_solana.md new file mode 100644 index 00000000..b95b9d83 --- /dev/null +++ b/docs/cre_generate-bindings_solana.md @@ -0,0 +1,46 @@ +## cre generate-bindings solana + +Generate bindings from contract IDL + +### Synopsis + +This command generates bindings from contract IDL files. +Supports Solana chain family and Go language. +Each contract gets its own package subdirectory to avoid naming conflicts. +For example, data_storage.json generates bindings in generated/data_storage/ package. + +``` +cre generate-bindings solana [optional flags] +``` + +### Examples + +``` + cre generate-bindings-solana +``` + +### Options + +``` + -h, --help help for solana + -i, --idl string Path to IDL directory (defaults to contracts/solana/src/idl/) + -l, --language string Target language (go) (default "go") + -o, --out string Path to output directory (defaults to contracts/solana/src/generated/) + -p, --project-root string Path to project root directory (defaults to current directory) +``` + +### Options inherited from parent commands + +``` + --allow-unknown-chains Skip chain-name validation against the chain-selectors registry (for experimental chains) + -e, --env string Path to .env file which contains sensitive info + --non-interactive Fail instead of prompting; requires all inputs via flags + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode +``` + +### SEE ALSO + +* [cre generate-bindings](cre_generate-bindings.md) - Generate bindings for contracts + diff --git a/go.mod b/go.mod index f2b862ff..a1b4b70d 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,13 @@ require ( github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/huh v0.8.0 github.com/charmbracelet/lipgloss v1.1.0 + github.com/dave/jennifer v1.7.1 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/denisbrodbeck/machineid v1.0.1 github.com/ethereum/go-ethereum v1.17.3 + github.com/gagliardetto/anchor-go v1.0.0 + github.com/gagliardetto/binary v0.8.0 + github.com/gagliardetto/solana-go v1.13.0 github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/validator/v10 v10.30.2 @@ -27,13 +32,14 @@ require ( github.com/smartcontractkit/chainlink-common v0.11.2-0.20260520194751-11a4f360f4e2 github.com/smartcontractkit/chainlink-common/keystore v1.1.0 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6 - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260522145417-85c85baa73cf github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8 github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8 github.com/smartcontractkit/cre-sdk-go v1.11.0 github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12 + github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana v0.1.0-beta.1 github.com/smartcontractkit/mcms v0.45.0 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c github.com/spf13/cobra v1.10.2 @@ -42,6 +48,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/test-go/testify v1.1.4 go.uber.org/zap v1.28.0 + golang.org/x/mod v0.36.0 golang.org/x/term v0.43.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v2 v2.4.0 @@ -135,7 +142,6 @@ require ( github.com/creachadair/jrpc2 v1.2.0 // indirect github.com/creachadair/mds v0.13.4 // indirect github.com/danieljoos/wincred v1.2.1 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dchest/siphash v1.2.3 // indirect github.com/deckarep/golang-set/v2 v2.9.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect @@ -159,8 +165,6 @@ require ( github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect - github.com/gagliardetto/binary v0.8.0 // indirect - github.com/gagliardetto/solana-go v1.13.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/gin-contrib/sessions v0.0.5 // indirect @@ -347,6 +351,7 @@ require ( github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/tklauser/go-sysconf v0.4.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -397,7 +402,6 @@ require ( golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.51.0 // indirect golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect - golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect diff --git a/go.sum b/go.sum index 85e83cf5..798e43aa 100644 --- a/go.sum +++ b/go.sum @@ -339,6 +339,8 @@ github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8 github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= +github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= +github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -429,6 +431,8 @@ github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gagliardetto/anchor-go v1.0.0 h1:YNt9I/9NOrNzz5uuzfzByAcbp39Ft07w63iPqC/wi34= +github.com/gagliardetto/anchor-go v1.0.0/go.mod h1:X6c9bx9JnmwNiyy8hmV5pAsq1c/zzPvkdzeq9/qmlCg= github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= @@ -1192,8 +1196,8 @@ github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0. github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-discovery v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:ATjAPIVJibHRcIfiG47rEQkUIOoYa6KDvWj3zwCAw6g= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d h1:AJy55QJ/pBhXkZjc7N+ATnWfxrcjq9BI9DmdtdjwDUQ= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:5JdppgngCOUS76p61zCinSCgOhPeYQ+OcDUuome5THQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42 h1:tyEgGOaYa7PBsmDIIvctOQPUc5c4Jtd/p4X1i96K+yo= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260521152427-d3f6dc93de42/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260522145417-85c85baa73cf h1:9nKluBQ0GBgnOokB8FCU1dmgZXDh22u9UPPMWFdKaYE= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260522145417-85c85baa73cf/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 h1:SG+wAsNyAcA6Kk19ljuxi3HK9Ll2lpHik8OKoY4x7A0= github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36/go.mod h1:vL1bDgPSJjV0EqHYs4dDlR+EEE0cJchgvGLYXhwIjXY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= @@ -1238,6 +1242,8 @@ github.com/smartcontractkit/cre-sdk-go v1.11.0 h1:E3MG0j8O9qDv6lDz71HPD3/WRKh/PX github.com/smartcontractkit/cre-sdk-go v1.11.0/go.mod h1:8SDE/e+eDAFpbRjRyKnIalUkQk9BcNbo2aLnda9BM48= github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12 h1:cNUnDjtg88OXcmNQRbGgpRZ5/ZU5SqLp0wrHCxHn4M8= github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12/go.mod h1:UNizO1xcv154THYM2hufcrWAp2rfuErC7Yeq8oJUrbk= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana v0.1.0-beta.1 h1:VTpFjEhGpFJKnl+B5vH/obnWUjRKeo1dSISP+FIL/eY= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana v0.1.0-beta.1/go.mod h1:IHCd8wnWeL9yLVHkOTB2MxIuFE+Zil8Gw2jrrfPPU9Y= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgHxTHuzJIF3Vj6LSMOnjhqKgRqYW+0MV2SExtCYL1Q= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= @@ -1322,6 +1328,7 @@ github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a h1:YuO+afVc3eqrj github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a/go.mod h1:/sfW47zCZp9FrtGcWyo1VjbgDaodxX9ovZvgLb/MxaA= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1329,6 +1336,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 065446e4..ef954482 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -58,10 +58,11 @@ const ( WorkflowLanguageWasm = "wasm" // SDK dependency versions (used by generate-bindings and go module init) - SdkVersion = "v1.11.0" - EVMCapabilitiesVersion = "v1.0.0-beta.12" - HTTPCapabilitiesVersion = "v1.3.0" - CronCapabilitiesVersion = "v1.3.0" + SdkVersion = "v1.11.0" + EVMCapabilitiesVersion = "v1.0.0-beta.12" + HTTPCapabilitiesVersion = "v1.3.0" + CronCapabilitiesVersion = "v1.3.0" + SolanaCapabilitiesVersion = "v0.1.0-beta.1" TestAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" TestAddress2 = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" From 705e6461b811dc7cddbbe5520031bb39f9d7861e Mon Sep 17 00:00:00 2001 From: Michael Fletcher <36506122+Fletch153@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:31:32 +0100 Subject: [PATCH 27/29] PLEX-3005 Run PR CI for capabilities-development branch (#467) PLEX-3005 Run PR CI for capabilities-development PRs targeting capabilities-development never ran the required ci-lint / ci-test-* checks (the workflow only triggered for main and releases), so they could never satisfy branch protection or enter the merge queue. Add capabilities-development to the pull_request branch filter. --- .github/workflows/pull-request-main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-request-main.yml b/.github/workflows/pull-request-main.yml index f735cca6..9b33355b 100644 --- a/.github/workflows/pull-request-main.yml +++ b/.github/workflows/pull-request-main.yml @@ -6,6 +6,7 @@ on: branches: - main - "releases/**" + - capabilities-development env: # Ensure that a cached go version is used: From 82b3441f5d8db209f3f12ce4114d0060ff7e82a6 Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:33:09 +0100 Subject: [PATCH 28/29] Extract internal/creconfig for CLI config paths (#463) * Make the user context a hard requirement * e2e tests * Extract internal/creconfig for CLI config paths * Lint * Fix test * Fix test * feedback --- cmd/login/login_test.go | 11 +- cmd/logout/logout.go | 15 +- cmd/logout/logout_test.go | 15 +- cmd/root.go | 5 +- cmd/secrets/common/browser_flow.go | 4 +- cmd/secrets/common/gateway/resolver.go | 2 +- cmd/templates/add/add.go | 3 +- cmd/templates/remove/remove.go | 3 +- cmd/workflow/hash/hash.go | 2 +- docs/cre_templates_add.md | 2 +- docs/cre_templates_remove.md | 2 +- internal/creconfig/creconfig.go | 57 +++++++ internal/creconfig/creconfig_test.go | 95 +++++++++++ internal/credentials/credentials.go | 14 +- internal/credentials/credentials_test.go | 5 +- internal/logger/logger_test.go | 6 +- internal/telemetry/collector.go | 9 +- internal/templateconfig/templateconfig.go | 35 ++-- .../templateconfig/templateconfig_test.go | 11 +- internal/templaterepo/cache.go | 13 +- internal/tenantctx/tenantctx.go | 20 +-- internal/update/update.go | 20 +-- .../workflow_private_registry.go | 150 ++++++++---------- 23 files changed, 316 insertions(+), 183 deletions(-) create mode 100644 internal/creconfig/creconfig.go create mode 100644 internal/creconfig/creconfig_test.go diff --git a/cmd/login/login_test.go b/cmd/login/login_test.go index a5abfedb..0435ba87 100644 --- a/cmd/login/login_test.go +++ b/cmd/login/login_test.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" "gopkg.in/yaml.v3" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/oauth" @@ -57,7 +58,10 @@ func TestFetchTenantConfig_GQLError_ReturnsError(t *testing.T) { t.Errorf("expected fetch user context error, got: %v", err) } - contextPath := filepath.Join(tmp, credentials.ConfigDir, tenantctx.ContextFile) + contextPath, err := creconfig.FilePath(tenantctx.ContextFile) + if err != nil { + t.Fatalf("failed to resolve context path: %v", err) + } if _, statErr := os.Stat(contextPath); statErr == nil { t.Errorf("expected %s not to be written on fetch failure", tenantctx.ContextFile) } @@ -103,7 +107,10 @@ func TestSaveCredentials_WritesYAML(t *testing.T) { t.Fatalf("saveCredentials error: %v", err) } - path := filepath.Join(tmp, credentials.ConfigDir, credentials.ConfigFile) + path, err := creconfig.FilePath(credentials.ConfigFile) + if err != nil { + t.Fatalf("failed to resolve config path: %v", err) + } data, err := os.ReadFile(path) if err != nil { t.Fatalf("cannot read config file: %v", err) diff --git a/cmd/logout/logout.go b/cmd/logout/logout.go index 5411c8bc..5b005e39 100644 --- a/cmd/logout/logout.go +++ b/cmd/logout/logout.go @@ -5,12 +5,12 @@ import ( "net/http" "net/url" "os" - "path/filepath" "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/smartcontractkit/cre-cli/internal/constants" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/runtime" @@ -49,11 +49,10 @@ func newHandler(ctx *runtime.Context) *handler { } func (h *handler) execute() error { - home, err := os.UserHomeDir() + credPath, err := creconfig.FilePath(credentials.ConfigFile) if err != nil { - return fmt.Errorf("could not determine home directory: %w", err) + return fmt.Errorf("could not determine config path: %w", err) } - credPath := filepath.Join(home, credentials.ConfigDir, credentials.ConfigFile) // Load credentials directly (logout is excluded from global credential loading) creds, err := credentials.New(h.log) @@ -90,11 +89,13 @@ func (h *handler) execute() error { if err := credentials.SecureRemove(credPath); err != nil { spinner.Stop() - return fmt.Errorf("failed to delete credentials file: %w", err) + return fmt.Errorf("failed to delete credentials file %s: %w", credPath, err) } - contextPath := filepath.Join(home, credentials.ConfigDir, tenantctx.ContextFile) - if err := os.Remove(contextPath); err != nil && !os.IsNotExist(err) { + contextPath, err := creconfig.FilePath(tenantctx.ContextFile) + if err != nil { + h.log.Warn().Err(err).Msgf("failed to resolve %s path", tenantctx.ContextFile) + } else if err := os.Remove(contextPath); err != nil && !os.IsNotExist(err) { h.log.Warn().Err(err).Msgf("failed to delete %s", tenantctx.ContextFile) } diff --git a/cmd/logout/logout_test.go b/cmd/logout/logout_test.go index 187991f2..3f5f4432 100644 --- a/cmd/logout/logout_test.go +++ b/cmd/logout/logout_test.go @@ -9,6 +9,7 @@ import ( "gopkg.in/yaml.v3" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/runtime" @@ -17,8 +18,8 @@ import ( func setupCredentialFile(t *testing.T, home string, token string) { t.Helper() - dir := filepath.Join(home, credentials.ConfigDir) - if err := os.MkdirAll(dir, 0o700); err != nil { + dir, err := creconfig.EnsureDir() + if err != nil { t.Fatalf("failed to create config dir: %v", err) } path := filepath.Join(dir, credentials.ConfigFile) @@ -112,7 +113,10 @@ func TestExecute_SuccessRevocationAndRemoval(t *testing.T) { t.Error("expected revocation request, but none received") } - credPath := filepath.Join(tDir, credentials.ConfigDir, credentials.ConfigFile) + credPath, err := creconfig.FilePath(credentials.ConfigFile) + if err != nil { + t.Fatalf("failed to resolve credentials path: %v", err) + } if _, err := os.Stat(credPath); !os.IsNotExist(err) { t.Errorf("expected credentials file to be removed, but it exists") } @@ -152,7 +156,10 @@ func TestExecute_RevocationFails_StillRemovesFile(t *testing.T) { t.Fatalf("expected no error despite revocation failure, got %v", err) } - credPath := filepath.Join(tDir, credentials.ConfigDir, credentials.ConfigFile) + credPath, err := creconfig.FilePath(credentials.ConfigFile) + if err != nil { + t.Fatalf("failed to resolve credentials path: %v", err) + } if _, err := os.Stat(credPath); !os.IsNotExist(err) { t.Errorf("expected credentials file to be removed, but it exists") } diff --git a/cmd/root.go b/cmd/root.go index bc397aa1..d0bcbccc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,6 +29,7 @@ import ( "github.com/smartcontractkit/cre-cli/cmd/workflow" "github.com/smartcontractkit/cre-cli/internal/constants" "github.com/smartcontractkit/cre-cli/internal/context" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/logger" "github.com/smartcontractkit/cre-cli/internal/runtime" @@ -242,7 +243,7 @@ func newRootCommand() *cobra.Command { } ui.ErrorWithSuggestions("Failed to load user context", []string{ "Run `cre login` to fetch your user context", - fmt.Sprintf("Ensure ~/.cre/%s exists and is readable", tenantctx.ContextFile), + fmt.Sprintf("Ensure %s exists and is readable", creconfig.FilePathHint(tenantctx.ContextFile)), }) return fmt.Errorf("user context required: %w", err) } @@ -308,7 +309,7 @@ func newRootCommand() *cobra.Command { } ui.ErrorWithSuggestions("Failed to load user context", []string{ "Run `cre login` to fetch your user context", - fmt.Sprintf("Ensure ~/.cre/%s exists and is readable", tenantctx.ContextFile), + fmt.Sprintf("Ensure %s exists and is readable", creconfig.FilePathHint(tenantctx.ContextFile)), }) return fmt.Errorf("user context required: %w", err) } diff --git a/cmd/secrets/common/browser_flow.go b/cmd/secrets/common/browser_flow.go index 8fe264af..f98fc166 100644 --- a/cmd/secrets/common/browser_flow.go +++ b/cmd/secrets/common/browser_flow.go @@ -61,7 +61,7 @@ func digestHexString(digest [32]byte) string { // executeBrowserUpsert handles secrets create/update when the user signs in with their organization account. // It encrypts the payload, binds a digest, requests a platform authorization URL, completes OAuth in the browser, // exchanges the code for a short-lived vault JWT, and POSTs the same JSON-RPC body to the gateway with Bearer auth. -// Login tokens in ~/.cre/cre.yaml are not modified; that session stays separate from this vault-only token. +// Login tokens in the CLI credentials file are not modified; that session stays separate from this vault-only token. func (h *Handler) executeBrowserUpsert(ctx context.Context, inputs UpsertSecretsInputs, method string) error { if h.Credentials.AuthType == credentials.AuthTypeApiKey { return fmt.Errorf("this sign-in flow requires an interactive login; API keys are not supported") @@ -224,7 +224,7 @@ func (h *Handler) ExecuteBrowserVaultAuthorization(ctx context.Context, method s }) var exchangeResp struct { ExchangeAuthCodeToToken struct { - AccessToken string `json:"accessToken"` + AccessToken string `json:"accessToken"` // #nosec G117 -- matches OAuth token exchange response field ExpiresIn int `json:"expiresIn"` } `json:"exchangeAuthCodeToToken"` } diff --git a/cmd/secrets/common/gateway/resolver.go b/cmd/secrets/common/gateway/resolver.go index c57782a4..2463a1b0 100644 --- a/cmd/secrets/common/gateway/resolver.go +++ b/cmd/secrets/common/gateway/resolver.go @@ -9,7 +9,7 @@ import ( ) // ResolveVaultGatewayURL returns the vault gateway URL for secrets operations. -// Precedence: CRE_VAULT_DON_GATEWAY_URL env var, then context.yaml vault_gateway_url, +// Precedence: CRE_VAULT_DON_GATEWAY_URL env var, then the user context file vault_gateway_url, // then the embedded default from EnvironmentSet. func ResolveVaultGatewayURL(tenantCtx *tenantctx.EnvironmentContext, envSet *environments.EnvironmentSet) string { if os.Getenv(environments.EnvVarVaultGatewayURL) != "" && envSet != nil { diff --git a/cmd/templates/add/add.go b/cmd/templates/add/add.go index f531a6c6..1abc0720 100644 --- a/cmd/templates/add/add.go +++ b/cmd/templates/add/add.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/templateconfig" "github.com/smartcontractkit/cre-cli/internal/templaterepo" @@ -20,7 +21,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { return &cobra.Command{ Use: "add ...", Short: "Adds a template repository source", - Long: `Adds one or more template repository sources to ~/.cre/template.yaml. These repositories are used by cre init to discover available templates.`, + Long: fmt.Sprintf("Adds one or more template repository sources to your home directory (%s/%s). These repositories are used by cre init to discover available templates.", creconfig.Dir, templateconfig.TemplateConfigFile), Args: cobra.MinimumNArgs(1), Example: "cre templates add smartcontractkit/cre-templates@main myorg/my-templates", RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/templates/remove/remove.go b/cmd/templates/remove/remove.go index a8b36787..c8b0e996 100644 --- a/cmd/templates/remove/remove.go +++ b/cmd/templates/remove/remove.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/templateconfig" "github.com/smartcontractkit/cre-cli/internal/templaterepo" @@ -20,7 +21,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { return &cobra.Command{ Use: "remove ...", Short: "Removes a template repository source", - Long: `Removes one or more template repository sources from ~/.cre/template.yaml. The ref portion is optional and ignored during matching.`, + Long: fmt.Sprintf("Removes one or more template repository sources from your home directory (%s/%s). The ref portion is optional and ignored during matching.", creconfig.Dir, templateconfig.TemplateConfigFile), Args: cobra.MinimumNArgs(1), Example: "cre templates remove smartcontractkit/cre-templates myorg/my-templates", RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/workflow/hash/hash.go b/cmd/workflow/hash/hash.go index 7efdb434..2f7fa09e 100644 --- a/cmd/workflow/hash/hash.go +++ b/cmd/workflow/hash/hash.go @@ -24,7 +24,7 @@ type Inputs struct { WorkflowName string WorkflowPath string OwnerFromSettings string - PrivateKey string + PrivateKey string // #nosec G117 -- CLI flag for optional signing key input SkipTypeChecks bool RegistryType settings.RegistryType DerivedOwner string diff --git a/docs/cre_templates_add.md b/docs/cre_templates_add.md index adaa6303..3af5ce5c 100644 --- a/docs/cre_templates_add.md +++ b/docs/cre_templates_add.md @@ -4,7 +4,7 @@ Adds a template repository source ### Synopsis -Adds one or more template repository sources to ~/.cre/template.yaml. These repositories are used by cre init to discover available templates. +Adds one or more template repository sources to your home directory (.cre/template.yaml). These repositories are used by cre init to discover available templates. ``` cre templates add ... [flags] diff --git a/docs/cre_templates_remove.md b/docs/cre_templates_remove.md index 23730215..d17742f6 100644 --- a/docs/cre_templates_remove.md +++ b/docs/cre_templates_remove.md @@ -4,7 +4,7 @@ Removes a template repository source ### Synopsis -Removes one or more template repository sources from ~/.cre/template.yaml. The ref portion is optional and ignored during matching. +Removes one or more template repository sources from your home directory (.cre/template.yaml). The ref portion is optional and ignored during matching. ``` cre templates remove ... [optional flags] diff --git a/internal/creconfig/creconfig.go b/internal/creconfig/creconfig.go new file mode 100644 index 00000000..b9bd846c --- /dev/null +++ b/internal/creconfig/creconfig.go @@ -0,0 +1,57 @@ +package creconfig + +import ( + "fmt" + "os" + "path/filepath" +) + +const Dir = ".cre" + +// DirPath returns the absolute path to the CLI config directory. +func DirPath() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("get home dir: %w", err) + } + return filepath.Join(home, Dir), nil +} + +// EnsureDir creates the CLI config directory with 0700 permissions if missing. +func EnsureDir() (string, error) { + dir, err := DirPath() + if err != nil { + return "", err + } + if err := os.MkdirAll(dir, 0o700); err != nil { + return "", fmt.Errorf("create config dir: %w", err) + } + return dir, nil +} + +// FilePath returns the absolute path to a file directly under the CLI config directory. +func FilePath(name string) (string, error) { + dir, err := DirPath() + if err != nil { + return "", err + } + return filepath.Join(dir, name), nil +} + +// FilePathHint returns the absolute config file path for user-facing messages, +// or a doc-style path (Dir/name) if the home directory cannot be resolved. +func FilePathHint(name string) string { + if path, err := FilePath(name); err == nil { + return path + } + return filepath.Join(Dir, name) +} + +// JoinPath returns an absolute path under the CLI config directory. +func JoinPath(elem ...string) (string, error) { + dir, err := DirPath() + if err != nil { + return "", err + } + return filepath.Join(append([]string{dir}, elem...)...), nil +} diff --git a/internal/creconfig/creconfig_test.go b/internal/creconfig/creconfig_test.go new file mode 100644 index 00000000..adfc5ec1 --- /dev/null +++ b/internal/creconfig/creconfig_test.go @@ -0,0 +1,95 @@ +package creconfig + +import ( + "os" + "path/filepath" + "testing" +) + +func TestDirPath(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + got, err := DirPath() + if err != nil { + t.Fatalf("DirPath() error: %v", err) + } + want := filepath.Join(home, Dir) + if got != want { + t.Fatalf("DirPath() = %q, want %q", got, want) + } +} + +func TestFilePath(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + got, err := FilePath("context.yaml") + if err != nil { + t.Fatalf("FilePath() error: %v", err) + } + want := filepath.Join(home, Dir, "context.yaml") + if got != want { + t.Fatalf("FilePath() = %q, want %q", got, want) + } +} + +func TestJoinPath(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + got, err := JoinPath("template-cache", "list.json") + if err != nil { + t.Fatalf("JoinPath() error: %v", err) + } + want := filepath.Join(home, Dir, "template-cache", "list.json") + if got != want { + t.Fatalf("JoinPath() = %q, want %q", got, want) + } +} + +func TestFilePathHint(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + got := FilePathHint("context.yaml") + want := filepath.Join(home, Dir, "context.yaml") + if got != want { + t.Fatalf("FilePathHint() = %q, want %q", got, want) + } +} + +func TestFilePathHint_FallsBackToRelPath(t *testing.T) { + t.Setenv("HOME", "") + + got := FilePathHint("context.yaml") + want := filepath.Join(Dir, "context.yaml") + if got != want { + t.Fatalf("FilePathHint() = %q, want %q", got, want) + } +} + +func TestEnsureDir(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + dir, err := EnsureDir() + if err != nil { + t.Fatalf("EnsureDir() error: %v", err) + } + want := filepath.Join(home, Dir) + if dir != want { + t.Fatalf("EnsureDir() = %q, want %q", dir, want) + } + + info, err := os.Stat(dir) + if err != nil { + t.Fatalf("stat config dir: %v", err) + } + if !info.IsDir() { + t.Fatal("expected directory") + } + if info.Mode().Perm()&0o777 != 0o700 { + t.Fatalf("dir mode = %o, want 0700", info.Mode().Perm()&0o777) + } +} diff --git a/internal/credentials/credentials.go b/internal/credentials/credentials.go index f0b3ca5d..2f9e2dc2 100644 --- a/internal/credentials/credentials.go +++ b/internal/credentials/credentials.go @@ -11,6 +11,8 @@ import ( "github.com/rs/zerolog" "gopkg.in/yaml.v2" + + "github.com/smartcontractkit/cre-cli/internal/creconfig" ) type CreLoginTokenSet struct { @@ -34,7 +36,6 @@ const ( CreApiKeyVar = "CRE_API_KEY" AuthTypeApiKey = "api-key" AuthTypeBearer = "bearer" - ConfigDir = ".cre" ConfigFile = "cre.yaml" // DeploymentAccessStatusFullAccess indicates the organization has full deployment access @@ -61,11 +62,10 @@ func New(logger *zerolog.Logger) (*Credentials, error) { return cfg, nil } - home, err := os.UserHomeDir() + path, err := creconfig.FilePath(ConfigFile) if err != nil { return cfg, nil } - path := filepath.Join(home, ConfigDir, ConfigFile) data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("you are not logged in, run cre login and try again") @@ -81,13 +81,9 @@ func New(logger *zerolog.Logger) (*Credentials, error) { } func SaveCredentials(tokenSet *CreLoginTokenSet) error { - home, err := os.UserHomeDir() + dir, err := creconfig.EnsureDir() if err != nil { - return fmt.Errorf("get home dir: %w", err) - } - dir := filepath.Join(home, ConfigDir) - if err := os.MkdirAll(dir, 0o700); err != nil { - return fmt.Errorf("create config dir: %w", err) + return err } path := filepath.Join(dir, ConfigFile) diff --git a/internal/credentials/credentials_test.go b/internal/credentials/credentials_test.go index af594c24..b679c12b 100644 --- a/internal/credentials/credentials_test.go +++ b/internal/credentials/credentials_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/testutil" "github.com/smartcontractkit/cre-cli/internal/testutil/testjwt" ) @@ -42,8 +43,8 @@ func TestNew_WithConfigFile(t *testing.T) { tDir := t.TempDir() t.Setenv("HOME", tDir) - dir := filepath.Join(tDir, ConfigDir) - if err := os.MkdirAll(dir, 0o755); err != nil { + dir, err := creconfig.EnsureDir() + if err != nil { t.Fatalf("failed to create config dir: %v", err) } file := filepath.Join(dir, ConfigFile) diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go index 97207091..da38138d 100644 --- a/internal/logger/logger_test.go +++ b/internal/logger/logger_test.go @@ -60,8 +60,10 @@ func TestLogger(t *testing.T) { log.Info().Msg("pretty message") output := buf.String() - // Pretty logging typically includes colors (ANSI escape codes) - assert.Contains(t, output, "\x1b[") + // ConsoleWriter uses human-readable format instead of JSON; ANSI colors depend on TTY detection. + assert.Contains(t, output, "pretty message") + assert.Contains(t, output, "INF") + assert.NotContains(t, output, `"level"`) }) t.Run("Logger with fields", func(t *testing.T) { diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go index 05405eca..1d47dd53 100644 --- a/internal/telemetry/collector.go +++ b/internal/telemetry/collector.go @@ -10,8 +10,12 @@ import ( "github.com/denisbrodbeck/machineid" "github.com/spf13/cobra" "github.com/spf13/pflag" + + "github.com/smartcontractkit/cre-cli/internal/creconfig" ) +const MachineIDFile = "machine_id" + // CollectMachineInfo gathers information about the machine running the CLI func CollectMachineInfo() MachineInfo { return MachineInfo{ @@ -42,10 +46,7 @@ func CollectWorkflowInfo(settings interface{}) *WorkflowInfo { // getOrCreateMachineID retrieves or generates a stable machine ID for telemetry func getOrCreateMachineID() (string, error) { - // Try to read existing machine ID from config (for backwards compatibility) - home, err := os.UserHomeDir() - if err == nil { - idFile := fmt.Sprintf("%s/.cre/machine_id", home) + if idFile, err := creconfig.FilePath(MachineIDFile); err == nil { if data, err := os.ReadFile(idFile); err == nil && len(data) > 0 { return strings.TrimSpace(string(data)), nil } diff --git a/internal/templateconfig/templateconfig.go b/internal/templateconfig/templateconfig.go index e048b752..6b1e49ad 100644 --- a/internal/templateconfig/templateconfig.go +++ b/internal/templateconfig/templateconfig.go @@ -9,13 +9,11 @@ import ( "github.com/rs/zerolog" "gopkg.in/yaml.v3" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/templaterepo" ) -const ( - configDirName = ".cre" - configFileName = "template.yaml" -) +const TemplateConfigFile = "template.yaml" // DefaultSources are the default template repositories. var DefaultSources = []templaterepo.RepoSource{ @@ -31,7 +29,7 @@ var DefaultSources = []templaterepo.RepoSource{ }, } -// Config represents the CLI template configuration file at ~/.cre/template.yaml. +// Config represents the CLI template configuration file (TemplateConfigFile in the config directory). type Config struct { TemplateRepositories []TemplateRepo `yaml:"templateRepositories"` } @@ -43,7 +41,7 @@ type TemplateRepo struct { Ref string `yaml:"ref"` } -// LoadTemplateSources returns the list of template sources from ~/.cre/template.yaml, +// LoadTemplateSources returns template sources from the CLI config file, // falling back to the default source if the file doesn't exist. func LoadTemplateSources(logger *zerolog.Logger) []templaterepo.RepoSource { cfg, err := loadConfigFile(logger) @@ -62,16 +60,11 @@ func LoadTemplateSources(logger *zerolog.Logger) []templaterepo.RepoSource { return DefaultSources } -// SaveTemplateSources writes the given sources to ~/.cre/template.yaml. +// SaveTemplateSources writes the given sources to the CLI template config file. func SaveTemplateSources(sources []templaterepo.RepoSource) error { - homeDir, err := os.UserHomeDir() + dir, err := creconfig.EnsureDir() if err != nil { - return fmt.Errorf("get home directory: %w", err) - } - - dir := filepath.Join(homeDir, configDirName) - if err := os.MkdirAll(dir, 0750); err != nil { - return fmt.Errorf("create config directory: %w", err) + return err } var repos []TemplateRepo @@ -89,7 +82,7 @@ func SaveTemplateSources(sources []templaterepo.RepoSource) error { return fmt.Errorf("marshal config: %w", err) } - configPath := filepath.Join(dir, configFileName) + configPath := filepath.Join(dir, TemplateConfigFile) tmp := configPath + ".tmp" if err := os.WriteFile(tmp, data, 0600); err != nil { return fmt.Errorf("write temp file: %w", err) @@ -102,15 +95,13 @@ func SaveTemplateSources(sources []templaterepo.RepoSource) error { return nil } -// EnsureDefaultConfig creates ~/.cre/template.yaml with the default source +// EnsureDefaultConfig creates the CLI template config file with the default source // if the file does not already exist. func EnsureDefaultConfig(logger *zerolog.Logger) error { - homeDir, err := os.UserHomeDir() + configPath, err := creconfig.FilePath(TemplateConfigFile) if err != nil { - return fmt.Errorf("get home directory: %w", err) + return err } - - configPath := filepath.Join(homeDir, configDirName, configFileName) if _, err := os.Stat(configPath); err == nil { return nil // file already exists } @@ -143,12 +134,10 @@ func ParseRepoString(s string) (templaterepo.RepoSource, error) { } func loadConfigFile(logger *zerolog.Logger) (*Config, error) { - homeDir, err := os.UserHomeDir() + configPath, err := creconfig.FilePath(TemplateConfigFile) if err != nil { return nil, err } - - configPath := filepath.Join(homeDir, configDirName, configFileName) data, err := os.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { diff --git a/internal/templateconfig/templateconfig_test.go b/internal/templateconfig/templateconfig_test.go index 7ef4d947..2b5a61c4 100644 --- a/internal/templateconfig/templateconfig_test.go +++ b/internal/templateconfig/templateconfig_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/templaterepo" "github.com/smartcontractkit/cre-cli/internal/testutil" ) @@ -59,7 +60,8 @@ func TestLoadTemplateSourcesFromConfigFile(t *testing.T) { homeDir := t.TempDir() t.Setenv("HOME", homeDir) - configDir := filepath.Join(homeDir, ".cre") + configDir, err := creconfig.DirPath() + require.NoError(t, err) require.NoError(t, os.MkdirAll(configDir, 0750)) configContent := `templateRepositories: @@ -68,7 +70,7 @@ func TestLoadTemplateSourcesFromConfigFile(t *testing.T) { ref: release ` require.NoError(t, os.WriteFile( - filepath.Join(configDir, "template.yaml"), + filepath.Join(configDir, TemplateConfigFile), []byte(configContent), 0600, )) @@ -94,8 +96,9 @@ func TestSaveTemplateSources(t *testing.T) { require.NoError(t, SaveTemplateSources(sources)) // Verify file exists - configPath := filepath.Join(homeDir, ".cre", "template.yaml") - _, err := os.Stat(configPath) + configPath, err := creconfig.FilePath(TemplateConfigFile) + require.NoError(t, err) + _, err = os.Stat(configPath) require.NoError(t, err) // Verify content by loading back diff --git a/internal/templaterepo/cache.go b/internal/templaterepo/cache.go index 0640cd8a..bd79ade1 100644 --- a/internal/templaterepo/cache.go +++ b/internal/templaterepo/cache.go @@ -9,16 +9,17 @@ import ( "time" "github.com/rs/zerolog" + + "github.com/smartcontractkit/cre-cli/internal/creconfig" ) const ( templateListCacheDuration = 1 * time.Hour tarballCacheDuration = 24 * time.Hour - cacheDirName = "template-cache" - creDirName = ".cre" + TemplateCacheDir = "template-cache" ) -// Cache manages template list and tarball caching at ~/.cre/template-cache/. +// Cache manages template list and tarball caching under the CLI config directory. type Cache struct { logger *zerolog.Logger cacheDir string @@ -33,12 +34,10 @@ type templateListCache struct { // NewCache creates a new Cache instance. func NewCache(logger *zerolog.Logger) (*Cache, error) { - homeDir, err := os.UserHomeDir() + cacheDir, err := creconfig.JoinPath(TemplateCacheDir) if err != nil { - return nil, fmt.Errorf("failed to get home directory: %w", err) + return nil, fmt.Errorf("failed to get cache directory: %w", err) } - - cacheDir := filepath.Join(homeDir, creDirName, cacheDirName) if err := os.MkdirAll(cacheDir, 0750); err != nil { return nil, fmt.Errorf("failed to create cache directory: %w", err) } diff --git a/internal/tenantctx/tenantctx.go b/internal/tenantctx/tenantctx.go index ec3f2064..530613a1 100644 --- a/internal/tenantctx/tenantctx.go +++ b/internal/tenantctx/tenantctx.go @@ -15,6 +15,7 @@ import ( "gopkg.in/yaml.v2" "github.com/smartcontractkit/cre-cli/internal/client/graphqlclient" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" ) @@ -90,7 +91,7 @@ const getTenantConfigQuery = `query GetTenantConfig { }` // FetchAndWriteContext fetches the user context from the service -// and writes the registry manifest to ~/.cre/. +// and writes the registry manifest to the CLI config directory. func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client, envName string, log *zerolog.Logger) error { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() @@ -208,14 +209,14 @@ func parseChainSelectorJSON(raw []byte) (uint64, error) { return 0, fmt.Errorf("chain selector must be a decimal string or integer JSON value: %s", string(raw)) } -// LoadContext reads the registry manifest from ~/.cre/ +// LoadContext reads the registry manifest from the CLI config directory // and returns the EnvironmentContext for the given environment name. func LoadContext(envName string) (*EnvironmentContext, error) { - home, err := os.UserHomeDir() + path, err := creconfig.FilePath(ContextFile) if err != nil { - return nil, fmt.Errorf("get home dir: %w", err) + return nil, err } - return LoadContextFromPath(filepath.Join(home, credentials.ConfigDir, ContextFile), envName) + return LoadContextFromPath(path, envName) } // LoadContextFromPath reads the registry manifest at the given path @@ -263,14 +264,9 @@ func EnsureContext(ctx context.Context, creds *credentials.Credentials, envSet * } func writeContextFile(data map[string]*EnvironmentContext, log *zerolog.Logger) error { - home, err := os.UserHomeDir() + dir, err := creconfig.EnsureDir() if err != nil { - return fmt.Errorf("get home dir: %w", err) - } - - dir := filepath.Join(home, credentials.ConfigDir) - if err := os.MkdirAll(dir, 0o700); err != nil { - return fmt.Errorf("create config dir: %w", err) + return err } out, err := yaml.Marshal(data) diff --git a/internal/update/update.go b/internal/update/update.go index d61e0dd1..4e92e542 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -12,15 +12,16 @@ import ( "github.com/Masterminds/semver/v3" "github.com/rs/zerolog" + + "github.com/smartcontractkit/cre-cli/internal/creconfig" ) const ( - githubAPIURL = "https://api.github.com/repos/smartcontractkit/cre-cli/releases/latest" - repoURL = "https://github.com/smartcontractkit/cre-cli/releases" - timeout = 6 * time.Second - cacheDuration = 24 * time.Hour - cacheFileName = "update.json" - cacheDirName = ".cre" + githubAPIURL = "https://api.github.com/repos/smartcontractkit/cre-cli/releases/latest" + repoURL = "https://github.com/smartcontractkit/cre-cli/releases" + timeout = 6 * time.Second + cacheDuration = 24 * time.Hour + UpdateCacheFile = "update.json" ) // githubRelease is a minimal struct to parse the JSON response @@ -36,12 +37,11 @@ type cacheState struct { } func getCachePath(logger *zerolog.Logger) (string, error) { - homeDir, err := os.UserHomeDir() + path, err := creconfig.FilePath(UpdateCacheFile) if err != nil { - logger.Debug().Msgf("Failed to get user home directory: %v", err) - return "", err + logger.Debug().Msgf("Failed to get update cache path: %v", err) } - return filepath.Join(homeDir, cacheDirName, cacheFileName), nil + return path, err } func loadCache(path string, logger *zerolog.Logger) (*cacheState, error) { diff --git a/test/multi_command_flows/workflow_private_registry.go b/test/multi_command_flows/workflow_private_registry.go index 92720350..b2fb7a47 100644 --- a/test/multi_command_flows/workflow_private_registry.go +++ b/test/multi_command_flows/workflow_private_registry.go @@ -19,6 +19,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/authvalidation" "github.com/smartcontractkit/cre-cli/internal/constants" + "github.com/smartcontractkit/cre-cli/internal/creconfig" "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/ethkeys" @@ -55,13 +56,13 @@ func mockGetCreOrganizationInfoGraphQLPayload() map[string]any { } } -// CreateTestBearerCredentialsHome writes JWT bearer credentials under HOME/.cre for subprocess CLI tests. +// CreateTestBearerCredentialsHome writes JWT bearer credentials under the CLI config directory for subprocess CLI tests. func CreateTestBearerCredentialsHome(t *testing.T) string { t.Helper() homeDir := t.TempDir() - creDir := filepath.Join(homeDir, ".cre") - require.NoError(t, os.MkdirAll(creDir, 0o700), "failed to create .cre dir") + creDir := filepath.Join(homeDir, creconfig.Dir) + require.NoError(t, os.MkdirAll(creDir, 0o700), "failed to create config dir") jwt := createTestJWT("test-org-id") creConfig := "AccessToken: " + jwt + "\n" + @@ -70,11 +71,64 @@ func CreateTestBearerCredentialsHome(t *testing.T) string { "ExpiresIn: 3600\n" + "TokenType: Bearer\n" - require.NoError(t, os.WriteFile(filepath.Join(creDir, "cre.yaml"), []byte(creConfig), 0o600), "failed to write test credentials") + require.NoError(t, os.WriteFile(filepath.Join(creDir, credentials.ConfigFile), []byte(creConfig), 0o600), "failed to write test credentials") return homeDir } +// realGoCacheEnv returns GOPATH and GOMODCACHE locations outside t.TempDir()-backed HOME dirs. +// Overriding HOME makes Go default GOPATH to $HOME/go; module files are read-only and break TempDir cleanup. +func realGoCacheEnv(t *testing.T) (gopath, gomodcache string) { + t.Helper() + + realHome, err := os.UserHomeDir() + require.NoError(t, err, "failed to get real home dir") + + gopath = os.Getenv("GOPATH") + if gopath == "" { + gopath = filepath.Join(realHome, "go") + } + + gomodcache = os.Getenv("GOMODCACHE") + if gomodcache == "" { + gomodcache = filepath.Join(gopath, "pkg", "mod") + } + + return gopath, gomodcache +} + +// pinGoCacheForTestHome keeps module cache out of temp HOME directories in the test process. +func pinGoCacheForTestHome(t *testing.T) { + t.Helper() + gopath, gomodcache := realGoCacheEnv(t) + t.Setenv("GOPATH", gopath) + t.Setenv("GOMODCACHE", gomodcache) +} + +// cliChildEnv builds subprocess env with isolated HOME for credentials and pinned Go cache paths. +func cliChildEnv(t *testing.T, testHome string) []string { + t.Helper() + gopath, gomodcache := realGoCacheEnv(t) + + childEnv := make([]string, 0, len(os.Environ())+4) + for _, entry := range os.Environ() { + if strings.HasPrefix(entry, "HOME=") || + strings.HasPrefix(entry, "USERPROFILE=") || + strings.HasPrefix(entry, "GOPATH=") || + strings.HasPrefix(entry, "GOMODCACHE=") { + continue + } + childEnv = append(childEnv, entry) + } + childEnv = append(childEnv, + "HOME="+testHome, + "USERPROFILE="+testHome, + "GOPATH="+gopath, + "GOMODCACHE="+gomodcache, + ) + return childEnv +} + func createTestJWT(orgID string) string { return testjwt.CreateTestJWT(orgID) } @@ -239,29 +293,7 @@ func workflowDeployPrivateRegistry(t *testing.T, tc TestConfig) string { cmd := exec.Command(CLIPath, args...) testHome := CreateTestBearerCredentialsHome(t) - - realHome, err := os.UserHomeDir() - require.NoError(t, err, "failed to get real home dir") - - childEnv := make([]string, 0, len(os.Environ())+3) - hasGOPATH := false - for _, entry := range os.Environ() { - if strings.HasPrefix(entry, "HOME=") || strings.HasPrefix(entry, "USERPROFILE=") { - continue - } - if strings.HasPrefix(entry, "GOPATH=") { - hasGOPATH = true - } - childEnv = append(childEnv, entry) - } - childEnv = append(childEnv, "HOME="+testHome, "USERPROFILE="+testHome) - // When HOME is overridden, Go defaults GOPATH to $HOME/go which lands - // inside t.TempDir(). Go modules are read-only, so TempDir cleanup - // fails and marks the test as failed. Pin GOPATH to the real home. - if !hasGOPATH { - childEnv = append(childEnv, "GOPATH="+filepath.Join(realHome, "go")) - } - cmd.Env = childEnv + cmd.Env = cliChildEnv(t, testHome) var stdout, stderr bytes.Buffer cmd.Stdout, cmd.Stderr = &stdout, &stderr @@ -416,26 +448,7 @@ func workflowPausePrivateRegistry(t *testing.T, tc TestConfig) string { cmd := exec.Command(CLIPath, args...) testHome := CreateTestBearerCredentialsHome(t) - - realHome, err := os.UserHomeDir() - require.NoError(t, err, "failed to get real home dir") - - childEnv := make([]string, 0, len(os.Environ())+3) - hasGOPATH := false - for _, entry := range os.Environ() { - if strings.HasPrefix(entry, "HOME=") || strings.HasPrefix(entry, "USERPROFILE=") { - continue - } - if strings.HasPrefix(entry, "GOPATH=") { - hasGOPATH = true - } - childEnv = append(childEnv, entry) - } - childEnv = append(childEnv, "HOME="+testHome, "USERPROFILE="+testHome) - if !hasGOPATH { - childEnv = append(childEnv, "GOPATH="+filepath.Join(realHome, "go")) - } - cmd.Env = childEnv + cmd.Env = cliChildEnv(t, testHome) var stdout, stderr bytes.Buffer cmd.Stdout, cmd.Stderr = &stdout, &stderr @@ -586,26 +599,7 @@ func workflowActivatePrivateRegistry(t *testing.T, tc TestConfig) string { cmd := exec.Command(CLIPath, args...) testHome := CreateTestBearerCredentialsHome(t) - - realHome, err := os.UserHomeDir() - require.NoError(t, err, "failed to get real home dir") - - childEnv := make([]string, 0, len(os.Environ())+3) - hasGOPATH := false - for _, entry := range os.Environ() { - if strings.HasPrefix(entry, "HOME=") || strings.HasPrefix(entry, "USERPROFILE=") { - continue - } - if strings.HasPrefix(entry, "GOPATH=") { - hasGOPATH = true - } - childEnv = append(childEnv, entry) - } - childEnv = append(childEnv, "HOME="+testHome, "USERPROFILE="+testHome) - if !hasGOPATH { - childEnv = append(childEnv, "GOPATH="+filepath.Join(realHome, "go")) - } - cmd.Env = childEnv + cmd.Env = cliChildEnv(t, testHome) var stdout, stderr bytes.Buffer cmd.Stdout, cmd.Stderr = &stdout, &stderr @@ -744,26 +738,7 @@ func workflowDeletePrivateRegistry(t *testing.T, tc TestConfig) string { cmd := exec.Command(CLIPath, args...) testHome := CreateTestBearerCredentialsHome(t) - - realHome, err := os.UserHomeDir() - require.NoError(t, err, "failed to get real home dir") - - childEnv := make([]string, 0, len(os.Environ())+3) - hasGOPATH := false - for _, entry := range os.Environ() { - if strings.HasPrefix(entry, "HOME=") || strings.HasPrefix(entry, "USERPROFILE=") { - continue - } - if strings.HasPrefix(entry, "GOPATH=") { - hasGOPATH = true - } - childEnv = append(childEnv, entry) - } - childEnv = append(childEnv, "HOME="+testHome, "USERPROFILE="+testHome) - if !hasGOPATH { - childEnv = append(childEnv, "GOPATH="+filepath.Join(realHome, "go")) - } - cmd.Env = childEnv + cmd.Env = cliChildEnv(t, testHome) var stdout, stderr bytes.Buffer cmd.Stdout, cmd.Stderr = &stdout, &stderr @@ -855,6 +830,7 @@ func RunPrivateRegistryAuthAndSettingsFinalize(t *testing.T, envPath, blankWorkf bearerHome := CreateTestBearerCredentialsHome(t) t.Setenv("HOME", bearerHome) t.Setenv("USERPROFILE", bearerHome) + pinGoCacheForTestHome(t) logger := testutil.NewTestLogger() creds, err := credentials.New(logger) From 71ac78ff2ee672f254c7d9deda62f4887dfd176c Mon Sep 17 00:00:00 2001 From: Timothy Funnell <137069066+timothyF95@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:30:06 +0100 Subject: [PATCH 29/29] Add capability registry address to user context (#461) * Fix context propagation in execute operations * Fix unit test * Fix second unit test * Refactor of ctx prop * Use test handler to avoid ctx nil panics * lint * Remove test handler * Add capability registry address to user context * Lint * fix test --- internal/tenantctx/tenantctx.go | 52 +++++++++++++++---- internal/tenantctx/tenantctx_test.go | 24 +++++++++ internal/testutil/graphql_mock.go | 4 ++ .../workflow_private_registry.go | 16 ++++++ 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/internal/tenantctx/tenantctx.go b/internal/tenantctx/tenantctx.go index 530613a1..bdb4ec1c 100644 --- a/internal/tenantctx/tenantctx.go +++ b/internal/tenantctx/tenantctx.go @@ -39,13 +39,20 @@ type Forwarder struct { Address string `yaml:"address" json:"address"` } +// OnChainContract is a chain selector and contract address pair. +type OnChainContract struct { + ChainSelector uint64 `yaml:"chain_selector" json:"chainSelector"` + Address string `yaml:"address" json:"address"` +} + // EnvironmentContext holds user context for a single CLI environment. type EnvironmentContext struct { - TenantID string `yaml:"tenant_id"` - DefaultDonFamily string `yaml:"default_don_family"` - VaultGatewayURL string `yaml:"vault_gateway_url"` - Registries []*Registry `yaml:"registries"` - Forwarders []Forwarder `yaml:"forwarders,omitempty"` + TenantID string `yaml:"tenant_id"` + DefaultDonFamily string `yaml:"default_don_family"` + VaultGatewayURL string `yaml:"vault_gateway_url"` + CapabilitiesRegistry *OnChainContract `yaml:"capabilities_registry,omitempty"` + Registries []*Registry `yaml:"registries"` + Forwarders []Forwarder `yaml:"forwarders,omitempty"` } type gqlForwarder struct { @@ -53,12 +60,18 @@ type gqlForwarder struct { Address string `json:"address"` } +type gqlOnChainContract struct { + ChainSelector json.RawMessage `json:"chainSelector"` + Address string `json:"address"` +} + type getTenantConfigResponse struct { GetTenantConfig struct { - TenantID string `json:"tenantId"` - DefaultDonFamily string `json:"defaultDonFamily"` - VaultGatewayURL string `json:"vaultGatewayUrl"` - Registries []struct { + TenantID string `json:"tenantId"` + DefaultDonFamily string `json:"defaultDonFamily"` + VaultGatewayURL string `json:"vaultGatewayUrl"` + CapabilitiesRegistry gqlOnChainContract `json:"capabilitiesRegistry"` + Registries []struct { ID string `json:"id"` Label string `json:"label"` Type string `json:"type"` @@ -75,6 +88,10 @@ const getTenantConfigQuery = `query GetTenantConfig { tenantId defaultDonFamily vaultGatewayUrl + capabilitiesRegistry { + chainSelector + address + } registries { id label @@ -143,12 +160,25 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client, forwarders = append(forwarders, Forwarder{ChainSelector: sel, Address: addr}) } + capRegSel, err := parseChainSelectorJSON(tc.CapabilitiesRegistry.ChainSelector) + if err != nil { + return fmt.Errorf("invalid capabilitiesRegistry chainSelector: %w", err) + } + capRegAddr := strings.TrimSpace(tc.CapabilitiesRegistry.Address) + if capRegAddr == "" { + return fmt.Errorf("capabilitiesRegistry address is empty") + } + envCtx := &EnvironmentContext{ TenantID: tc.TenantID, DefaultDonFamily: tc.DefaultDonFamily, VaultGatewayURL: tc.VaultGatewayURL, - Registries: registries, - Forwarders: forwarders, + CapabilitiesRegistry: &OnChainContract{ + ChainSelector: capRegSel, + Address: capRegAddr, + }, + Registries: registries, + Forwarders: forwarders, } contextMap := map[string]*EnvironmentContext{ diff --git a/internal/tenantctx/tenantctx_test.go b/internal/tenantctx/tenantctx_test.go index 683c2f20..62f11480 100644 --- a/internal/tenantctx/tenantctx_test.go +++ b/internal/tenantctx/tenantctx_test.go @@ -43,6 +43,10 @@ func gqlResponseOnChainAndPrivate() map[string]any { "tenantId": "42", "defaultDonFamily": "zone-a", "vaultGatewayUrl": "https://gateway.example.com/", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "16015286601757825753", + "address": "0x7f3191EaF73429177bAB3bAc5c36Ed2D5E39985f", + }, "registries": []any{ map[string]any{ "id": "ethereum-testnet-sepolia", @@ -77,6 +81,10 @@ func gqlResponsePrivateOnly() map[string]any { "tenantId": "99", "defaultDonFamily": "zone-b", "vaultGatewayUrl": "https://gateway-private.example.com/", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "5009297550715157269", + "address": "0x76c9cf548b4179F8901cda1f8623568b58215E62", + }, "registries": []any{ map[string]any{ "id": "private", @@ -177,6 +185,16 @@ func TestFetchAndWriteContext_OnChainAndPrivate(t *testing.T) { if f.Address != "0x15fC6ae953E024d975e77382eEeC56A9101f9F88" { t.Errorf("forwarder address = %q, want Sepolia mock forwarder", f.Address) } + + if envCtx.CapabilitiesRegistry == nil { + t.Fatal("expected capabilitiesRegistry to be populated") + } + if envCtx.CapabilitiesRegistry.ChainSelector != 16015286601757825753 { + t.Errorf("capabilitiesRegistry chain selector = %d, want %d", envCtx.CapabilitiesRegistry.ChainSelector, uint64(16015286601757825753)) + } + if envCtx.CapabilitiesRegistry.Address != "0x7f3191EaF73429177bAB3bAc5c36Ed2D5E39985f" { + t.Errorf("capabilitiesRegistry address = %q, want staging mainline cap reg", envCtx.CapabilitiesRegistry.Address) + } } func TestFetchAndWriteContext_PrivateOnly(t *testing.T) { @@ -205,6 +223,12 @@ func TestFetchAndWriteContext_PrivateOnly(t *testing.T) { if len(envCtx.Forwarders) != 0 { t.Errorf("expected 0 forwarders, got %d", len(envCtx.Forwarders)) } + if envCtx.CapabilitiesRegistry == nil { + t.Fatal("expected capabilitiesRegistry to be populated") + } + if envCtx.CapabilitiesRegistry.ChainSelector != 5009297550715157269 { + t.Errorf("capabilitiesRegistry chain selector = %d, want %d", envCtx.CapabilitiesRegistry.ChainSelector, uint64(5009297550715157269)) + } } func TestParseChainSelectorJSON(t *testing.T) { diff --git a/internal/testutil/graphql_mock.go b/internal/testutil/graphql_mock.go index 888f6f7e..c4740869 100644 --- a/internal/testutil/graphql_mock.go +++ b/internal/testutil/graphql_mock.go @@ -36,6 +36,10 @@ func MockGetTenantConfigGraphQLPayload() map[string]any { "tenantId": "test-tenant-id", "defaultDonFamily": "test-don", "vaultGatewayUrl": "https://vault.example.test", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "6433500567565415381", + "address": "0x76c9cf548b4179F8901cda1f8623568b58215E62", + }, "registries": []map[string]any{ { "id": "anvil-devnet", diff --git a/test/multi_command_flows/workflow_private_registry.go b/test/multi_command_flows/workflow_private_registry.go index b2fb7a47..a24a394c 100644 --- a/test/multi_command_flows/workflow_private_registry.go +++ b/test/multi_command_flows/workflow_private_registry.go @@ -164,6 +164,10 @@ func workflowDeployPrivateRegistry(t *testing.T, tc TestConfig) string { "tenantId": "42", "defaultDonFamily": "test-don", "vaultGatewayUrl": "https://vault.example.test", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "6433500567565415381", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + }, "registries": []map[string]any{ { "id": "reg-test", @@ -357,6 +361,10 @@ func workflowPausePrivateRegistry(t *testing.T, tc TestConfig) string { "tenantId": "42", "defaultDonFamily": "test-don", "vaultGatewayUrl": "https://vault.example.test", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "6433500567565415381", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + }, "registries": []map[string]any{ { "id": "reg-test", @@ -508,6 +516,10 @@ func workflowActivatePrivateRegistry(t *testing.T, tc TestConfig) string { "tenantId": "42", "defaultDonFamily": "test-don", "vaultGatewayUrl": "https://vault.example.test", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "6433500567565415381", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + }, "registries": []map[string]any{ { "id": "reg-test", @@ -659,6 +671,10 @@ func workflowDeletePrivateRegistry(t *testing.T, tc TestConfig) string { "tenantId": "42", "defaultDonFamily": "test-don", "vaultGatewayUrl": "https://vault.example.test", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "6433500567565415381", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + }, "registries": []map[string]any{ { "id": "reg-test",