diff --git a/engine/cld/changeset/common.go b/engine/cld/changeset/common.go index 9991a6cb..1f53146f 100644 --- a/engine/cld/changeset/common.go +++ b/engine/cld/changeset/common.go @@ -32,8 +32,9 @@ type Configurations struct { type internalChangeSet interface { noop() // unexported function to prevent arbitrary structs from implementing ChangeSet. Apply(env fdeployment.Environment) (fdeployment.ChangesetOutput, error) - applyWithInput(env fdeployment.Environment, inputStr string) (fdeployment.ChangesetOutput, error) Configurations() (Configurations, error) + applyWithInput(env fdeployment.Environment, inputStr string) (fdeployment.ChangesetOutput, error) + configuration() (any, error) } type ChangeSet internalChangeSet @@ -335,6 +336,10 @@ func (ccs ChangeSetImpl[C]) Configurations() (Configurations, error) { }, nil } +func (ccs ChangeSetImpl[C]) configuration() (any, error) { + return ccs.configProvider() +} + // WithPreHooks appends pre-hooks to this changeset. Multiple calls are additive. func (ccs ChangeSetImpl[C]) WithPreHooks(hooks ...PreHook) ConfiguredChangeSet { ccs.preHooks = append(slices.Clone(ccs.preHooks), hooks...) diff --git a/engine/cld/changeset/common_test.go b/engine/cld/changeset/common_test.go index 66b39ea3..0de8457d 100644 --- a/engine/cld/changeset/common_test.go +++ b/engine/cld/changeset/common_test.go @@ -876,6 +876,41 @@ func TestWithJSON_UseNumberForAnyPayload(t *testing.T) { require.NoError(t, err) } +func TestChangeSetImpl_configuration(t *testing.T) { //nolint:paralleltest + configuredChangeset := Configure(MyChangeSet) + + t.Run("With", func(t *testing.T) { //nolint:paralleltest + got, err := configuredChangeset.With("config").configuration() + require.NoError(t, err) + require.Equal(t, "config", got) + }) + + t.Run("WithConfigFrom", func(t *testing.T) { //nolint:paralleltest + got, err := configuredChangeset.WithConfigFrom(func() (string, error) { + return "config", nil + }).configuration() + require.NoError(t, err) + require.Equal(t, "config", got) + }) + + t.Run("WithEnvInput", func(t *testing.T) { //nolint:paralleltest + t.Setenv("DURABLE_PIPELINE_INPUT", `{"payload":"config"}`) + got, err := configuredChangeset.WithEnvInput().configuration() + require.NoError(t, err) + require.Equal(t, "config", got) + }) + + t.Run("WithConfigResolver", func(t *testing.T) { //nolint:paralleltest + t.Setenv("DURABLE_PIPELINE_INPUT", `{"payload":"config"}`) + resolver := func(input string) (string, error) { + return "resolved " + input, nil + } + got, err := configuredChangeset.WithConfigResolver(resolver).configuration() + require.NoError(t, err) + require.Equal(t, "resolved config", got) + }) +} + func TestConfigurations_ConfigResolverInfo(t *testing.T) { t.Parallel() diff --git a/engine/cld/changeset/hooks.go b/engine/cld/changeset/hooks.go index 470ecc3a..8ac9cd15 100644 --- a/engine/cld/changeset/hooks.go +++ b/engine/cld/changeset/hooks.go @@ -79,6 +79,7 @@ type PostProposalHookParams struct { ChangesetKey string Proposal *mcms.TimelockProposal Input any + Config any Reports []MCMSTimelockExecuteReport } diff --git a/engine/cld/changeset/mcms.go b/engine/cld/changeset/mcms.go index 376d82c1..60c5ccb7 100644 --- a/engine/cld/changeset/mcms.go +++ b/engine/cld/changeset/mcms.go @@ -82,7 +82,7 @@ func (*EVMForkContext) ChainFamily() string { // 1. Per-changeset post-proposal-hooks // 2. Global post-proposal-hooks func (r *ChangesetsRegistry) RunProposalHooks( - key string, e fdeployment.Environment, proposal *mcms.TimelockProposal, input any, + key string, e fdeployment.Environment, proposal *mcms.TimelockProposal, input, config any, reports []MCMSTimelockExecuteReport, forkCtx ForkContext, ) error { applySnapshot, err := r.getApplySnapshot(key) @@ -106,6 +106,7 @@ func (r *ChangesetsRegistry) RunProposalHooks( ChangesetKey: key, Proposal: proposal, Input: input, + Config: config, Reports: reports, } diff --git a/engine/cld/changeset/mcms_test.go b/engine/cld/changeset/mcms_test.go index 48091bea..b9ff7e43 100644 --- a/engine/cld/changeset/mcms_test.go +++ b/engine/cld/changeset/mcms_test.go @@ -148,7 +148,7 @@ func Test_RunProposalHooks(t *testing.T) { execLogs := []string{} registry := tt.setup(&execLogs) - err := registry.RunProposalHooks(tt.key, hookTestEnv(t), &mcms.TimelockProposal{}, nil, nil, nil) + err := registry.RunProposalHooks(tt.key, hookTestEnv(t), &mcms.TimelockProposal{}, nil, nil, nil, nil) if tt.wantErr == "" { require.NoError(t, err) @@ -165,6 +165,7 @@ func Test_RunProposalHooks_HookReceivesCorrectParams(t *testing.T) { proposal := &mcms.TimelockProposal{} input := "test-input" + config := "test-config" reports := []MCMSTimelockExecuteReport{{Type: MCMSTimelockExecuteReportType}} var receivedParams PostProposalHookParams @@ -181,7 +182,7 @@ func Test_RunProposalHooks_HookReceivesCorrectParams(t *testing.T) { }}, } - err := r.RunProposalHooks("test-cs", hookTestEnv(t), proposal, input, reports, nil) + err := r.RunProposalHooks("test-cs", hookTestEnv(t), proposal, input, config, reports, nil) require.NoError(t, err) expectedParams := PostProposalHookParams{ @@ -189,6 +190,7 @@ func Test_RunProposalHooks_HookReceivesCorrectParams(t *testing.T) { ChangesetKey: "test-cs", Proposal: proposal, Input: input, + Config: config, Reports: reports, } require.Empty(t, cmp.Diff(expectedParams, receivedParams, diff --git a/engine/cld/changeset/postprocess.go b/engine/cld/changeset/postprocess.go index 797d8884..4f3de851 100644 --- a/engine/cld/changeset/postprocess.go +++ b/engine/cld/changeset/postprocess.go @@ -70,6 +70,10 @@ func (ccs PostProcessingChangeSetImpl[C]) WithPostProposalHooks(hooks ...PostPro return ccs } +func (ccs PostProcessingChangeSetImpl[C]) configuration() (any, error) { + return ccs.changeset.configuration() +} + func (ccs PostProcessingChangeSetImpl[C]) getPreHooks() []PreHook { return ccs.preHooks } func (ccs PostProcessingChangeSetImpl[C]) getPostHooks() []PostHook { return ccs.postHooks } func (ccs PostProcessingChangeSetImpl[C]) getPostProposalHooks() []PostProposalHook { diff --git a/engine/cld/changeset/postprocess_test.go b/engine/cld/changeset/postprocess_test.go index 7311ba33..36eedc44 100644 --- a/engine/cld/changeset/postprocess_test.go +++ b/engine/cld/changeset/postprocess_test.go @@ -45,3 +45,16 @@ func TestChangesets_PostProcess(t *testing.T) { require.NoError(t, err) assert.Nil(t, configs.InputChainOverrides) } + +func TestChangesets_PostProcess_configuration(t *testing.T) { + t.Parallel() + + noopPostProcessor := func(_ fdeployment.Environment, o fdeployment.ChangesetOutput) (fdeployment.ChangesetOutput, error) { + return o, nil + } + + got, err := Configure(MyChangeSet).With("config").ThenWith(noopPostProcessor).configuration() + + require.NoError(t, err) + require.Equal(t, "config", got) +} diff --git a/engine/cld/changeset/registry.go b/engine/cld/changeset/registry.go index a5c50296..639753e4 100644 --- a/engine/cld/changeset/registry.go +++ b/engine/cld/changeset/registry.go @@ -217,6 +217,11 @@ func (r *ChangesetsRegistry) Apply( return fdeployment.ChangesetOutput{}, err } + changesetConfig, err := applySnapshot.registryEntry.changeset.configuration() + if err != nil { + return fdeployment.ChangesetOutput{}, fmt.Errorf("failed to get changeset configuration: %w", err) + } + hookEnv := HookEnv{ Name: e.Name, Logger: e.Logger, @@ -225,6 +230,7 @@ func (r *ChangesetsRegistry) Apply( preParams := PreHookParams{ Env: hookEnv, ChangesetKey: key, + Config: changesetConfig, } for _, h := range applySnapshot.globalPreHooks { @@ -248,6 +254,7 @@ func (r *ChangesetsRegistry) Apply( postParams := PostHookParams{ Env: hookEnv, ChangesetKey: key, + Config: changesetConfig, Output: output, Err: applyErr, } @@ -339,6 +346,16 @@ func (r *ChangesetsRegistry) GetChangesetOptions(key string) (ChangesetConfig, e return entry.options, nil } +// GetConfiguration retrieves the configuration for a changeset. +func (r *ChangesetsRegistry) GetConfiguration(key string) (any, error) { + entry, ok := r.entries[key] + if !ok { + return ChangesetConfig{}, fmt.Errorf("changeset '%s' not found", key) + } + + return entry.changeset.configuration() +} + // GetConfigurations retrieves the configurations for a changeset. func (r *ChangesetsRegistry) GetConfigurations(key string) (Configurations, error) { entry, ok := r.entries[key] diff --git a/engine/cld/changeset/registry_test.go b/engine/cld/changeset/registry_test.go index e65b868a..c33260a0 100644 --- a/engine/cld/changeset/registry_test.go +++ b/engine/cld/changeset/registry_test.go @@ -17,6 +17,7 @@ import ( // Used for testing. type noopChangeset struct { chainOverrides []uint64 + config any } func (noopChangeset) noop() {} @@ -35,9 +36,14 @@ func (n noopChangeset) Configurations() (Configurations, error) { }, nil } +func (n noopChangeset) configuration() (any, error) { + return n.config, nil +} + // recordingChangeset tracks whether Apply was called and returns configurable output/error. type recordingChangeset struct { applyCalled bool + config any output fdeployment.ChangesetOutput err error } @@ -57,6 +63,10 @@ func (*recordingChangeset) Configurations() (Configurations, error) { return Configurations{}, nil } +func (r *recordingChangeset) configuration() (any, error) { + return r.config, nil +} + // orderRecordingChangeset records "apply" calls into its internal order slice for tests. type orderRecordingChangeset struct { order []string @@ -77,6 +87,10 @@ func (*orderRecordingChangeset) Configurations() (Configurations, error) { return Configurations{}, nil } +func (*orderRecordingChangeset) configuration() (any, error) { + return "orderRecordingChangesetConfiguration", nil +} + func hookTestEnv(t *testing.T) fdeployment.Environment { t.Helper() @@ -564,7 +578,58 @@ func Test_Apply_PreHookWarn_ContinuesExecution(t *testing.T) { assert.True(t, cs.applyCalled, "changeset should still run after Warn hook failure") } -func Test_Apply_PostHookReceivesOutputAndErr(t *testing.T) { +func Test_Apply_PreHook_ReceivesParams(t *testing.T) { + t.Parallel() + + changeset := &recordingChangeset{config: "test-config"} + receivedParams := PreHookParams{} + registry := NewChangesetsRegistry() + registry.entries["test-cs"] = registryEntry{ + changeset: changeset, + preHooks: []PreHook{{ + HookDefinition: HookDefinition{Name: "observer", FailurePolicy: Warn}, + Func: func(_ context.Context, params PreHookParams) error { + receivedParams = params + return nil + }, + }}, + } + + _, err := registry.Apply("test-cs", hookTestEnv(t)) + + require.NoError(t, err) + require.Equal(t, "test-cs", receivedParams.ChangesetKey) + require.Equal(t, "test-env", receivedParams.Env.Name) + require.Equal(t, "test-config", receivedParams.Config) +} + +func Test_Apply_PreHook_ConfigurationErrorAbortsPipeline(t *testing.T) { + t.Parallel() + + changeset := Configure(MyChangeSet).WithConfigFrom(func() (string, error) { + return "", errors.New("config unavailable") + }) + registry := NewChangesetsRegistry() + registry.SetValidate(false) + registry.Add("test-cs", changeset) + + preHookRan := false + registry.AddGlobalPreHooks(PreHook{ + HookDefinition: HookDefinition{Name: "pre-hook"}, + Func: func(_ context.Context, _ PreHookParams) error { + preHookRan = true + return nil + }, + }) + + _, err := registry.Apply("test-cs", hookTestEnv(t)) + + require.ErrorContains(t, err, "failed to get changeset configuration") + require.False(t, preHookRan) +} + + +func Test_Apply_PostHook_ReceivesParams(t *testing.T) { t.Parallel() expectedOutput := fdeployment.ChangesetOutput{} @@ -572,6 +637,7 @@ func Test_Apply_PostHookReceivesOutputAndErr(t *testing.T) { cs := &recordingChangeset{ output: expectedOutput, err: expectedErr, + config: "test-config", } var receivedParams PostHookParams @@ -594,6 +660,7 @@ func Test_Apply_PostHookReceivesOutputAndErr(t *testing.T) { assert.Equal(t, expectedErr, receivedParams.Err) assert.Equal(t, "test-cs", receivedParams.ChangesetKey) assert.Equal(t, "test-env", receivedParams.Env.Name) + assert.Equal(t, "test-config", receivedParams.Config) } func Test_Apply_PostHookAbort_AfterSuccessfulApply(t *testing.T) { @@ -919,7 +986,7 @@ func Test_FluentAPI_HooksExtractedByAdd(t *testing.T) { require.Len(t, entry.postProposalHooks, 1, "Add should extract post-proposal-hooks via hookCarrier") require.Equal(t, "proposal-hook", entry.postProposalHooks[0].Name) - err := r.RunProposalHooks("test-cs", hookTestEnv(t), nil, "input", nil, nil) + err := r.RunProposalHooks("test-cs", hookTestEnv(t), nil, "input", "config", nil, nil) require.NoError(t, err) require.Equal(t, []string{"proposal"}, hookExecutions) }) @@ -940,7 +1007,7 @@ func Test_FluentAPI_HooksExtractedByAdd(t *testing.T) { require.Len(t, entry.postProposalHooks, 1) require.Equal(t, "proposal-hook", entry.postProposalHooks[0].Name) - err := r.RunProposalHooks("test-cs", hookTestEnv(t), nil, "input", nil, nil) + err := r.RunProposalHooks("test-cs", hookTestEnv(t), nil, "input", "config", nil, nil) require.NoError(t, err) require.Equal(t, []string{"proposal"}, hookExecutions) }) @@ -1227,6 +1294,71 @@ func Test_WithPostProposalHooks(t *testing.T) { }) } +func Test_Changesets_GetConfiguration(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(*ChangesetsRegistry) + key string + want any + wantErr string + }{ + { + name: "unknown key returns error", + setup: func(r *ChangesetsRegistry) {}, + key: "invalid_key", + wantErr: "changeset 'invalid_key' not found", + }, + { + name: "found key returns config value", + setup: func(r *ChangesetsRegistry) { + r.Add("0001_cap_reg", noopChangeset{config: "my-config"}) + }, + key: "0001_cap_reg", + want: "my-config", + }, + { + name: "found key with nil config returns nil", + setup: func(r *ChangesetsRegistry) { + r.Add("0001_cap_reg", noopChangeset{}) + }, + key: "0001_cap_reg", + want: nil, + }, + { + name: "configuration error is propagated", + setup: func(r *ChangesetsRegistry) { + cs := Configure(MyChangeSet).WithConfigFrom(func() (string, error) { + return "", errors.New("config unavailable") + }) + r.SetValidate(false) + r.Add("0001_cap_reg", cs) + }, + key: "0001_cap_reg", + wantErr: "config unavailable", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + registry := NewChangesetsRegistry() + tt.setup(registry) + + got, err := registry.GetConfiguration(tt.key) + + if tt.wantErr == "" { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} + func Test_Changesets_AddGlobalPostProposalHooks(t *testing.T) { t.Parallel() diff --git a/engine/cld/commands/mcms/cmd_run_hooks.go b/engine/cld/commands/mcms/cmd_run_hooks.go index 43012ee1..4cb7f378 100644 --- a/engine/cld/commands/mcms/cmd_run_hooks.go +++ b/engine/cld/commands/mcms/cmd_run_hooks.go @@ -49,9 +49,10 @@ type proposalMetadata struct { } type changesetMetadata struct { - ID string `json:"id" mapstructure:"id"` - Name string `json:"name" mapstructure:"name"` - Input any `json:"input" mapstructure:"input"` + ID string `json:"id" mapstructure:"id"` + Name string `json:"name" mapstructure:"name"` + Input any `json:"input" mapstructure:"input"` + Config any `json:"config" mapstructure:"config"` } func newRunProposalHooksCmd(cfg Config) *cobra.Command { @@ -144,7 +145,7 @@ func runHooksInternal( }) herr := changesetRegistry.RunProposalHooks(changeset.Name, env, timelockProposal, - changeset.Input, changesetReports, forkCtx) + changeset.Input, changeset.Config, changesetReports, forkCtx) if herr != nil { return fmt.Errorf("proposal hook for changeset %q failed: %w", changeset.Name, herr) } diff --git a/engine/cld/commands/mcms/cmd_run_hooks_test.go b/engine/cld/commands/mcms/cmd_run_hooks_test.go index 1818d0e0..2244cb6d 100644 --- a/engine/cld/commands/mcms/cmd_run_hooks_test.go +++ b/engine/cld/commands/mcms/cmd_run_hooks_test.go @@ -160,10 +160,13 @@ func Test_newRunProposalHooksCmd(t *testing.T) { //nolint:paralleltest require.NoError(t, err) require.Equal(t, 1, testCtx.logs.FilterMessage("test-changeset-post-proposal-hook executed").Len()) require.Equal(t, 1, testCtx.logs.FilterMessage("test-changeset-post-proposal-hook; # reports: 1").Len()) + require.Equal(t, 1, testCtx.logs.FilterMessage("test-changeset-post-proposal-hook; config: map[config-key:config-value]").Len()) require.Equal(t, 0, testCtx.logs.FilterMessage("test-changeset-post-proposal-hook; forkctx family: EVM").Len()) require.Equal(t, 2, testCtx.logs.FilterMessage("test-global-post-proposal-hook executed").Len()) require.Equal(t, 1, testCtx.logs.FilterMessage("test-global-post-proposal-hook; # reports: 1").Len()) require.Equal(t, 1, testCtx.logs.FilterMessage("test-global-post-proposal-hook; # reports: 0").Len()) + require.Equal(t, 1, testCtx.logs.FilterMessage("test-global-post-proposal-hook; config: map[config-key:config-value]").Len()) + require.Equal(t, 1, testCtx.logs.FilterMessage("test-global-post-proposal-hook; config: map[]").Len()) require.Equal(t, 0, testCtx.logs.FilterMessage("test-global-post-proposal-hook; forkctx family: EVM").Len()) }, }, @@ -243,13 +246,17 @@ var testProposalWithChangesetsJSON = []byte(`{ "id": "30377e5d-d431-4c27-ac79-31ced49fffc0", "name": "001_test_changeset", "input": { - "key": "value" + "input-key": "input-value" + }, + "config": { + "config-key": "config-value" } }, { "id": "df1fbe75-5009-4561-b936-d3c1c6762c98", "name": "002_test_changeset", - "input": {} + "input": {}, + "config": {} } ] }, @@ -347,6 +354,7 @@ func loadChangesets(envName string) (*changeset.ChangesetsRegistry, error) { Func: func(ctx context.Context, params changeset.PostProposalHookParams) error { params.Env.Logger.Info("test-changeset-post-proposal-hook executed") params.Env.Logger.Infof("test-changeset-post-proposal-hook; # reports: %d", len(params.Reports)) + params.Env.Logger.Infof("test-changeset-post-proposal-hook; config: %v", params.Config) if params.Env.ForkContext != nil { params.Env.Logger.Infof("test-changeset-post-proposal-hook; forkctx family: %v", params.Env.ForkContext.ChainFamily()) @@ -370,6 +378,7 @@ func loadChangesets(envName string) (*changeset.ChangesetsRegistry, error) { Func: func(ctx context.Context, params changeset.PostProposalHookParams) error { params.Env.Logger.Info("test-global-post-proposal-hook executed") params.Env.Logger.Infof("test-global-post-proposal-hook; # reports: %d", len(params.Reports)) + params.Env.Logger.Infof("test-global-post-proposal-hook; config: %v", params.Config) if params.Env.ForkContext != nil { params.Env.Logger.Infof("test-global-post-proposal-hook; forkctx family: %v", params.Env.ForkContext.ChainFamily()) } diff --git a/engine/cld/commands/pipeline/run.go b/engine/cld/commands/pipeline/run.go index e9791cfa..d757af50 100644 --- a/engine/cld/commands/pipeline/run.go +++ b/engine/cld/commands/pipeline/run.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/cobra" fdeployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/changeset" "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/commands/flags" "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/environment" "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/pipeline/input" @@ -172,7 +173,7 @@ func runRun(cmd *cobra.Command, cfg *Config, f runFlags) error { return saveErr } - err = saveChangesetProposalMetadata(actualChangesetName, out) + err = saveChangesetProposalMetadata(registry, actualChangesetName, out) if err != nil { return fmt.Errorf("failed to save changeset proposal metadata: %w", err) } @@ -202,7 +203,9 @@ func runRun(cmd *cobra.Command, cfg *Config, f runFlags) error { return nil } -func saveChangesetProposalMetadata(changesetName string, out fdeployment.ChangesetOutput) error { +func saveChangesetProposalMetadata( + registry *changeset.ChangesetsRegistry, changesetName string, out fdeployment.ChangesetOutput, +) error { if len(out.MCMSTimelockProposals) == 0 { return nil } @@ -212,6 +215,11 @@ func saveChangesetProposalMetadata(changesetName string, out fdeployment.Changes return errors.New("durable pipeline input is empty or not set") } + changesetConfig, err := registry.GetConfiguration(changesetName) + if err != nil { + return fmt.Errorf("failed to get changeset configuration: %w", err) + } + id := uuid.NewString() for i := range out.MCMSTimelockProposals { @@ -221,13 +229,15 @@ func saveChangesetProposalMetadata(changesetName string, out fdeployment.Changes } proposal.Metadata["changesets"] = []struct { - ID string `json:"id"` - Name string `json:"name"` - Input json.RawMessage `json:"input"` + ID string `json:"id"` + Name string `json:"name"` + Input json.RawMessage `json:"input"` + Config any `json:"config"` }{{ - ID: id, - Name: changesetName, - Input: json.RawMessage(changesetInputJSON), + ID: id, + Name: changesetName, + Input: json.RawMessage(changesetInputJSON), + Config: changesetConfig, }} for j := range proposal.Operations { diff --git a/engine/cld/commands/pipeline/run_test.go b/engine/cld/commands/pipeline/run_test.go index 657ae665..4246327b 100644 --- a/engine/cld/commands/pipeline/run_test.go +++ b/engine/cld/commands/pipeline/run_test.go @@ -593,7 +593,7 @@ changesets: - 0001_test_changeset: payload: chain: optimism_sepolia - value: 100` + value: 12345678901234567890` yamlFileName := "test-input.yaml" require.NoError(t, os.WriteFile(filepath.Join(inputsDir, yamlFileName), []byte(yamlContent), 0o600)) @@ -607,7 +607,7 @@ changesets: rp := ®istryProviderStub{ BaseRegistryProvider: changeset.NewBaseRegistryProvider(), AddAction: func(reg *changeset.ChangesetsRegistry) { - reg.Add(changesetName, changeset.Configure(changesetStub).With(1)) + reg.Add(changesetName, changeset.Configure(changesetStub).With("changeset-config")) }, } if initErr := rp.Init(); initErr != nil { @@ -658,9 +658,10 @@ changesets: "input": map[string]any{ "payload": map[string]any{ "chain": "optimism_sepolia", - "value": json.Number("100"), + "value": json.Number("12345678901234567890"), }, }, + "config": "changeset-config", }, }, }, proposal.Metadata)