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: 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/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/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 340465e7..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:") @@ -118,7 +119,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.9.0"}} `), 0600)) install := exec.Command("bun", "install") install.Dir = dir @@ -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/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/login/login.go b/cmd/login/login.go index 045a1c3e..1c0ce616 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,8 +126,9 @@ func (h *handler) execute() error { } h.spinner.Update("Fetching user context...") - if err := h.fetchTenantConfig(tokenSet); err != nil { - h.log.Debug().Err(err).Msgf("failed to fetch user context — %s not written", tenantctx.ContextFile) + if err := h.fetchTenantConfig(ctx, tokenSet); err != nil { + h.spinner.StopAll() + return fmt.Errorf("failed to fetch user context: %w", err) } // Stop spinner before final output @@ -295,7 +296,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 +308,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/login/login_test.go b/cmd/login/login_test.go index 33bdb2fb..0435ba87 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" @@ -13,12 +15,58 @@ 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" + "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, 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) + } +} + 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. @@ -59,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 ee0f86f1..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) @@ -88,13 +87,15 @@ 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) + 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 8c24d6e5..d0bcbccc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,11 +29,13 @@ 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" "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" ) @@ -221,7 +223,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) } @@ -236,7 +238,14 @@ 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") + if showSpinner { + spinner.Stop() + } + ui.ErrorWithSuggestions("Failed to load user context", []string{ + "Run `cre login` to fetch your user context", + fmt.Sprintf("Ensure %s exists and is readable", creconfig.FilePathHint(tenantctx.ContextFile)), + }) + return fmt.Errorf("user context required: %w", err) } // Check if organization is ungated for commands that require it @@ -280,8 +289,45 @@ 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 { + if showSpinner { + spinner.Stop() + } + ui.ErrorWithSuggestions("Failed to load user context", []string{ + "Run `cre login` to fetch your user context", + fmt.Sprintf("Ensure %s exists and is readable", creconfig.FilePathHint(tenantctx.ContextFile)), + }) + return fmt.Errorf("user context required: %w", err) + } + } + } + + 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) { @@ -415,6 +461,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) @@ -484,6 +540,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": {}, @@ -515,28 +573,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/cmd/secrets/common/browser_flow.go b/cmd/secrets/common/browser_flow.go index 7cc5022c..f98fc166 100644 --- a/cmd/secrets/common/browser_flow.go +++ b/cmd/secrets/common/browser_flow.go @@ -61,19 +61,20 @@ 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") } - 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) @@ -223,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.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..2463a1b0 --- /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 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 { + 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 d0ba25f2..b30352c1 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" @@ -31,10 +30,12 @@ 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" "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" @@ -48,32 +49,42 @@ 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"` } 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 + GatewayURL string + Gw gateway.Client + 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 != "" { @@ -87,19 +98,22 @@ 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, - } - h.Gw = &HTTPClient{URL: h.EnvironmentSet.GatewayURL, Client: &http.Client{Timeout: 90 * time.Second}} + 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, + execCtx: execCtx, + } + 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() + wrc, err := h.ClientFactory.NewWorkflowRegistryV2Client(execCtx) if err != nil { return nil, fmt.Errorf("failed to create workflow registry client: %w", err) } @@ -161,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", }) @@ -277,99 +292,55 @@ 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) } 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; -// owner-key auth uses ResolveEffectiveOwner() (workflow address unless CRE_CLI_SECRETS_ORG_OWNED). +// 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, 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. -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) + if h.Credentials == nil { + return "", fmt.Errorf("organization information is missing from your session; sign in again or use --secrets-auth=onchain") } - - pubKeyHex, err := h.fetchVaultMasterPublicKeyHex() - if err != nil { - return nil, err + if h.Credentials.AuthType == credentials.AuthTypeApiKey { + return "", fmt.Errorf("this sign-in flow requires an interactive login; API keys are not supported") } - - 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, - }) + owner := strings.TrimSpace(h.DerivedWorkflowOwner) + if owner == "" { + return "", fmt.Errorf("derived workflow owner is not available; sign in again with cre login") } - return encryptedSecrets, nil + 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. +// 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 { 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) + 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) } secID := &vault.SecretIdentifier{ Key: item.ID, Namespace: item.Namespace, - Owner: orgID, + Owner: owner, } encryptedSecrets = append(encryptedSecrets, &vault.EncryptedSecret{ Id: secID, @@ -380,7 +351,7 @@ func (h *Handler) EncryptSecretsForBrowserOrg(rawSecrets UpsertSecretsInputs, or } // 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 { @@ -390,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) } @@ -402,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 @@ -447,13 +418,17 @@ 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 { + defer ZeroUpsertSecretValues(inputs) + + 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 { @@ -461,12 +436,17 @@ func (h *Handler) Execute( } ui.Dim("Verifying ownership...") - if err := h.EnsureOwnerLinkedOrFail(); err != nil { + if err := h.EnsureOwnerLinkedOrFail(ctx); err != nil { + 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) } @@ -516,15 +496,15 @@ 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) + 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) } } @@ -731,13 +711,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 fb547b62..e1400980 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" @@ -21,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 { @@ -39,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" @@ -67,11 +91,11 @@ 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) + enc, err := h.EncryptSecrets(raw, "0xabc") require.NoError(t, err) require.Len(t, enc, 2) @@ -91,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) { @@ -100,7 +128,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: []byte("v"), Namespace: "n"}}, "0xabc") require.Error(t, err) require.Nil(t, enc) require.Contains(t, err.Error(), "gateway POST failed") @@ -126,7 +154,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: []byte("v"), Namespace: "n"}}, "0xabc") require.Error(t, err) require.Nil(t, enc) require.Contains(t, err.Error(), "vault public key fetch error") @@ -134,70 +162,44 @@ 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 derived workflow owner from session", func(t *testing.T) { h, _, _ := newMockHandler(t) - h.OwnerAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - h.EnvironmentSet.SecretsOrgOwned = false 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) { @@ -210,20 +212,19 @@ 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("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 +232,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 +248,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: []byte("val1"), Namespace: "main"}, + }, "0xabc") + 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) { @@ -359,7 +343,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") @@ -367,19 +351,57 @@ 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") }) } + +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) + }) +} 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..31f1a50d 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 } @@ -66,12 +66,13 @@ 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 } - 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 039b9b62..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 } @@ -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 { @@ -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 ca6a3c54..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 } @@ -135,18 +135,18 @@ 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) - 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..de776004 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 } @@ -67,12 +67,13 @@ 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 } - return h.Execute(inputs, vaulttypes.MethodSecretsUpdate, duration, secretsAuth) + return h.Execute(cmd.Context(), inputs, vaulttypes.MethodSecretsUpdate, duration, secretsAuth) }, } 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/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_onchain.go b/cmd/workflow/activate/registry_activate_strategy_onchain.go index ea9df4dd..6a9f9919 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" @@ -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,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(h.execCtx, a.wrc, ownerAddr, workflowName) if err != nil { return fmt.Errorf("failed to get workflow list: %w", err) } @@ -81,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/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/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/common/workflow_list.go b/cmd/workflow/common/workflow_list.go new file mode 100644 index 00000000..848945b0 --- /dev/null +++ b/cmd/workflow/common/workflow_list.go @@ -0,0 +1,50 @@ +package common + +import ( + "context" + "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(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, +) ([]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(ctx, 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/convert/convert_test.go b/cmd/workflow/convert/convert_test.go index 2bd0bb53..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.6.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/delete/delete.go b/cmd/workflow/delete/delete.go index 928a1546..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,7 +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_onchain.go b/cmd/workflow/delete/registry_delete_strategy_onchain.go index 1dd7335b..b2c2911c 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" @@ -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 := a.wrc.GetWorkflowListByOwnerAndName(workflowOwner, workflowName, big.NewInt(0), big.NewInt(100)) + allWorkflows, err := workflowcommon.FetchAllWorkflowsByOwnerAndName(h.execCtx, a.wrc, workflowOwner, workflowName) if err != nil { return nil, fmt.Errorf("failed to get workflow list: %w", err) } @@ -82,8 +82,11 @@ func (a *onchainRegistryDeleteStrategy) DeleteWorkflows(workflows []WorkflowToDe h := a.h var errs []error for _, wf := range workflows { - workflowID := wf.RawID.([32]byte) - txOut, err := a.wrc.DeleteWorkflow(workflowID) + 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(). Err(err). diff --git a/cmd/workflow/delete/registry_delete_strategy_private.go b/cmd/workflow/delete/registry_delete_strategy_private.go index 22f832bf..5001dd42 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) } @@ -54,8 +54,11 @@ func (a *privateRegistryDeleteStrategy) DeleteWorkflows(workflows []WorkflowToDe h := a.h for _, wf := range workflows { - workflowID := wf.RawID.(string) - deletedID, err := a.prc.DeleteWorkflowInRegistry(workflowID) + 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(). Err(err). 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 2ee140bf..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(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(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(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 ba6c37c6..11952c21 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) @@ -214,29 +218,36 @@ 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, 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 @@ -248,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) } @@ -262,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, @@ -274,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) } } @@ -291,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/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/private_registry_test.go b/cmd/workflow/deploy/private_registry_test.go index 7ba1047b..098b669b 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" @@ -232,7 +233,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 +255,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", @@ -314,12 +315,15 @@ func TestCheckWorkflowExists_PrivateRegistry(t *testing.T) { h.environmentSet.GraphQLURL = gqlServer.URL 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 != "" { 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/register.go b/cmd/workflow/deploy/register.go index 6c47e237..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(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 da3b0241..60e296ea 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 @@ -55,17 +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 - 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 434b62de..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" @@ -11,6 +12,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. @@ -18,21 +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. - CheckWorkflowExists(workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) + // When the existing workflow ID matches workflowID, exists is true and + // errWorkflowUnchanged is returned to block redeployment of identical artifacts. + 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 ea6f1583..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() + 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,13 +89,14 @@ func (a *onchainRegistryDeployStrategy) RunPreDeployChecks() error { return nil } -func (a *onchainRegistryDeployStrategy) CheckWorkflowExists(workflowOwner, workflowName, workflowTag, workflowID string) (bool, *uint8, error) { - workflow, err := a.wrc.GetWorkflow(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 } 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 @@ -87,10 +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( + ctx, a.wrc, a.wrc, common.HexToAddress(h.inputs.WorkflowOwner), @@ -104,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 dc14b21c..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,17 +28,17 @@ 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(workflowName) + workflow, err := a.prc.GetWorkflowByName(ctx, 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 } @@ -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(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 99cc311c..2f7fa09e 100644 --- a/cmd/workflow/hash/hash.go +++ b/cmd/workflow/hash/hash.go @@ -1,8 +1,10 @@ package hash import ( + "context" "fmt" "os" + "strings" "github.com/spf13/cobra" @@ -22,8 +24,10 @@ 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 } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -41,6 +45,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,16 +59,19 @@ 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) + return Execute(cmd.Context(), inputs) }, } 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") @@ -71,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 } @@ -82,12 +93,18 @@ 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 } - 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,11 +144,58 @@ 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 loadBinary(wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) ([]byte, error) { +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(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) } @@ -158,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, }) @@ -172,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 6587ba2e..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" @@ -15,6 +16,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 +26,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", "", "") @@ -76,7 +81,7 @@ func TestExecute_WithForUser(t *testing.T) { WorkflowName: "test-workflow", } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } @@ -90,7 +95,7 @@ func TestExecute_WithoutForUser_UsesPrivateKey(t *testing.T) { PrivateKey: testPrivateKey, } - err := Execute(inputs) + err := Execute(context.Background(), inputs) require.NoError(t, err) } @@ -103,11 +108,39 @@ 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") +} + +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) @@ -141,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) } @@ -155,7 +188,53 @@ func TestExecute_EmptyConfig(t *testing.T) { WorkflowName: "test-workflow", } - err := Execute(inputs) + err := Execute(context.Background(), inputs) + 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(context.Background(), 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(context.Background(), 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(context.Background(), inputs) require.NoError(t, err) } @@ -217,8 +296,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 +309,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/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_onchain.go b/cmd/workflow/pause/registry_pause_strategy_onchain.go index f038d6aa..9e403dd8 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" @@ -36,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 @@ -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(h.execCtx, a.wrc, workflowOwner, workflowName) if err != nil { return fmt.Errorf("failed to list workflows: %w", err) } @@ -84,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) } @@ -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 -} 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/cmd/workflow/simulate/chain/evm/supported_chains.go b/cmd/workflow/simulate/chain/evm/supported_chains.go index 96bef7ff..dde4d4fe 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,11 @@ var SupportedChains = []chain.ChainConfig{ // DTCC {Selector: chainselectors.DTCC_TESTNET_ANDESITE.Selector, Forwarder: "0x6E9EE680ef59ef64Aa8C7371279c27E496b5eDc1"}, + + // 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/cmd/workflow/simulate/simulate.go b/cmd/workflow/simulate/simulate.go index 9f374991..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) } @@ -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..4879b8f3 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" @@ -89,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") } @@ -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() 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.md b/docs/cre.md index 6e495250..81092ba9 100644 --- a/docs/cre.md +++ b/docs/cre.md @@ -13,19 +13,20 @@ 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 * [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_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..d1f5befc 100644 --- a/docs/cre_generate-bindings.md +++ b/docs/cre_generate-bindings.md @@ -1,50 +1,32 @@ ## 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 ``` - -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 + -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 * [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/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..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] @@ -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..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] @@ -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 2043c5b0..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 @@ -41,5 +42,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_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 44b2fb13..09d60256 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) ``` @@ -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 a3300433..c813d844 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] @@ -23,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/go.mod b/go.mod index ffb6c346..a1b4b70d 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,23 @@ 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/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.2 + 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.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 @@ -23,26 +28,28 @@ 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/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/chain-selectors v1.0.100 + 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-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-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/mcms v0.41.1 + 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 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/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 gopkg.in/yaml.v3 v3.0.1 @@ -68,38 +75,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 +114,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 +128,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 @@ -135,14 +142,13 @@ 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.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 +156,22 @@ 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 +181,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 +191,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 +199,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 +222,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 +238,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 +273,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 +288,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 +306,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 @@ -358,29 +352,28 @@ require ( 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 +393,27 @@ 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/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 +421,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 6fa0e20d..798e43aa 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,11 +335,12 @@ 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= 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= @@ -399,14 +349,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 +364,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 +401,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 +425,22 @@ 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 +468,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 +529,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 +544,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 +563,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 +588,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 +599,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 +612,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 +621,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 +674,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 +706,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 +721,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 +740,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 +751,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 +792,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 +802,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 +813,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 +820,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 +851,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 +864,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 +874,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 +901,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 +922,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 +948,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 +967,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 +976,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 +992,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 +1040,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 +1058,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 +1065,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 +1096,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 +1113,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 +1123,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 +1132,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= @@ -1305,52 +1142,50 @@ 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/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/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-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= @@ -1361,14 +1196,16 @@ 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-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= 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,42 +1216,44 @@ 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/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/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= +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= 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 +1262,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 +1288,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 +1307,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,19 +1318,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= @@ -1515,12 +1339,11 @@ github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso 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 +1367,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 +1380,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 +1388,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 +1406,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 +1476,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 +1514,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 +1557,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 +1567,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 +1580,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 +1587,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 +1625,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 +1647,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 +1658,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 +1671,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 +1695,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 +1704,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 +1759,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 +1779,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 +1797,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/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 5e8f084b..1c5875fe 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, } } @@ -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) } @@ -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 578fbd06..0587dfbc 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" @@ -85,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", @@ -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) } diff --git a/internal/client/storageclient/storageclient.go b/internal/client/storageclient/storageclient.go index 5eb9a829..9a798684 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, } } @@ -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") 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") diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 29cb7e92..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.7.0" - EVMCapabilitiesVersion = "v1.0.0-beta.9" - 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" 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 3e7f3533..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) @@ -106,6 +102,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..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) @@ -411,3 +412,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") + } + }) +} 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" 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/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 } 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()) } 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 } 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/builtin/hello-world-ts/workflow/package.json b/internal/templaterepo/builtin/hello-world-ts/workflow/package.json index 65487e3b..e0c1970a 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.9.0" }, "devDependencies": { "typescript": "5.9.3" } -} +} \ No newline at end of file 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 c94a62d0..bdb4ec1c 100644 --- a/internal/tenantctx/tenantctx.go +++ b/internal/tenantctx/tenantctx.go @@ -2,16 +2,20 @@ package tenantctx import ( "context" + "encoding/json" "fmt" "os" "path/filepath" + "strconv" "strings" + "time" "github.com/machinebox/graphql" "github.com/rs/zerolog" "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" ) @@ -29,20 +33,45 @@ 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"` +} + +// 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"` + 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 { + ChainSelector json.RawMessage `json:"chainSelector"` + 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"` @@ -50,6 +79,7 @@ type getTenantConfigResponse struct { Address *string `json:"address"` SecretsAuthFlows []string `json:"secretsAuthFlows"` } `json:"registries"` + Forwarders []gqlForwarder `json:"forwarders"` } `json:"getTenantConfig"` } @@ -58,6 +88,10 @@ const getTenantConfigQuery = `query GetTenantConfig { tenantId defaultDonFamily vaultGatewayUrl + capabilitiesRegistry { + chainSelector + address + } registries { id label @@ -66,12 +100,19 @@ const getTenantConfigQuery = `query GetTenantConfig { address secretsAuthFlows } + forwarders { + chainSelector + address + } } }` // 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() + req := graphql.NewRequest(getTenantConfigQuery) var resp getTenantConfigResponse @@ -83,7 +124,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 @@ -104,11 +145,40 @@ 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}) + } + + 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, + CapabilitiesRegistry: &OnChainContract{ + ChainSelector: capRegSel, + Address: capRegAddr, + }, + Registries: registries, + Forwarders: forwarders, } contextMap := map[string]*EnvironmentContext{ @@ -118,14 +188,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" } } @@ -151,14 +222,31 @@ func abbreviateAddress(addr string) string { return addr[:6] + "..." + addr[len(addr)-4:] } -// LoadContext reads the registry manifest from ~/.cre/ +// 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 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 @@ -206,14 +294,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/tenantctx/tenantctx_test.go b/internal/tenantctx/tenantctx_test.go index 225ac59c..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", @@ -59,6 +63,12 @@ func gqlResponseOnChainAndPrivate() map[string]any { "secretsAuthFlows": []any{"BROWSER"}, }, }, + "forwarders": []any{ + map[string]any{ + "chainSelector": "16015286601757825753", + "address": "0x15fC6ae953E024d975e77382eEeC56A9101f9F88", + }, + }, }, }, } @@ -71,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", @@ -79,6 +93,7 @@ func gqlResponsePrivateOnly() map[string]any { "secretsAuthFlows": []any{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, } @@ -159,6 +174,27 @@ 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) + } + + 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) { @@ -184,6 +220,41 @@ 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)) + } + 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) { + 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) { @@ -386,7 +457,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) } } 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/graphql_mock.go b/internal/testutil/graphql_mock.go index 9f5ff73f..c4740869 100644 --- a/internal/testutil/graphql_mock.go +++ b/internal/testutil/graphql_mock.go @@ -10,8 +10,60 @@ 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", + "capabilitiesRegistry": map[string]any{ + "chainSelector": "6433500567565415381", + "address": "0x76c9cf548b4179F8901cda1f8623568b58215E62", + }, + "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 +76,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/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") } 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/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 } diff --git a/test/multi_command_flows/workflow_private_registry.go b/test/multi_command_flows/workflow_private_registry.go index 5ab8c3df..a24a394c 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) } @@ -110,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", @@ -120,6 +178,7 @@ func workflowDeployPrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -238,29 +297,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 @@ -324,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", @@ -334,6 +375,7 @@ func workflowPausePrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -414,26 +456,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 @@ -493,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", @@ -503,6 +530,7 @@ func workflowActivatePrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -583,26 +611,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 @@ -662,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", @@ -672,6 +685,7 @@ func workflowDeletePrivateRegistry(t *testing.T, tc TestConfig) string { "secretsAuthFlows": []string{"BROWSER"}, }, }, + "forwarders": []any{}, }, }, }) @@ -740,26 +754,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 @@ -851,6 +846,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)