Skip to content

Commit 909e6f4

Browse files
authored
feat: decouple rendering from intermediate decoded representation (#541)
- Created a `TextRenderer` as base renderer for the MarkdownRenderer to decouple formatting assumptions from intermediate representations of the decoded proposal. - Separated templates into different files and modularized templates a bit more to decouple more from code. - Renamed `descriptors` to `fields` I think the new name is a bit easier to follow. - Added a `format` flag to the `analyze-proposal` command so users can outputs either a text or markdown format ## AI Summary This pull request refactors how decoded calls and their arguments are represented and rendered in the analyzer package, and introduces support for specifying output formats in the MCMSv2 CLI. The main focus is on replacing the legacy `Descriptor`-based argument types with a more structured `Field`-based approach, improving type safety and clarity for transaction analysis (especially Aptos transactions). It also adds a mechanism to select between markdown and text output formats for proposal analysis. ### Analyzer Data Model Refactoring * Replaced usage of `NamedDescriptor` and `Descriptor` types with new `NamedField` and `FieldValue` types throughout the analyzer codebase, including in `DecodedCall`, test cases, and Aptos transaction analysis. This improves type safety and enables richer field representations such as arrays and addresses. [[1]](diffhunk://#diff-9ced6c2506531bf50bad8bbb81accf530d4e7f6f460223ed4fa3883670865f0dL42-R42) [[2]](diffhunk://#diff-9ced6c2506531bf50bad8bbb81accf530d4e7f6f460223ed4fa3883670865f0dL51-R51) [[3]](diffhunk://#diff-ac76cb897192140b4a87c2aed6f0741de1c49ce96aa22c45385c639ddd408d3aL42-R85) [[4]](diffhunk://#diff-ac76cb897192140b4a87c2aed6f0741de1c49ce96aa22c45385c639ddd408d3aL98-R141) [[5]](diffhunk://#diff-ac76cb897192140b4a87c2aed6f0741de1c49ce96aa22c45385c639ddd408d3aL154-R160) [[6]](diffhunk://#diff-a6767ce2366447881f241f495e4c2472c269db4db906bb64b0d120cd6742f7f4L11-R85) [[7]](diffhunk://#diff-56344cbdb586c128580c2fb36f2b1d40cba330f4e8589ae093ca6d3cd8e19f91R1-R46) * Updated test assertions to compare field types and values using the new field model, ensuring correctness of the new structure. * Removed the legacy `Describe` method from `DecodedCall` and replaced it with a new `String` method that uses the updated field model. The actual rendering is now intended to be handled by dedicated renderer classes. [[1]](diffhunk://#diff-d3b59c27e8c4f64f8897bfc722028bb7ec8c39178c267fd8c87d37cded9b2d52L1-L51) [[2]](diffhunk://#diff-56344cbdb586c128580c2fb36f2b1d40cba330f4e8589ae093ca6d3cd8e19f91R1-R46) ### CLI Output Format Support * Added a `--format` flag to the MCMSv2 CLI `analyze-proposal` command, allowing users to select between markdown and text output formats for proposal analysis. [[1]](diffhunk://#diff-726dd799d05204c24b69b8bda1f4ede5393c5955b360b076db8d11bc01f56a1bR713) [[2]](diffhunk://#diff-726dd799d05204c24b69b8bda1f4ede5393c5955b360b076db8d11bc01f56a1bR766) * Implemented the `createRendererFromFormat` helper function to instantiate the appropriate renderer based on the requested format. * Ensured the selected renderer is set in the proposal context before analysis, enabling flexible output formatting. ### Test and Mock Updates * Updated mock proposal context and related test utilities to support the new renderer and field context APIs, maintaining test coverage and compatibility with the refactored types. --- These changes modernize the analyzer's internal data model, improve output flexibility, and lay the groundwork for future enhancements in transaction decoding and rendering.
1 parent 9572077 commit 909e6f4

55 files changed

Lines changed: 3287 additions & 1644 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/easy-roses-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
refactor proposal analyzer to add a text renderer and move templates to separate files

engine/cld/legacy/cli/commands/durable-pipelines_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,12 +636,16 @@ type mockProposalContext struct {
636636
t *testing.T
637637
}
638638

639+
func (m *mockProposalContext) SetRenderer(r analyzer.Renderer) {
640+
// No-op for mock
641+
}
642+
639643
func (m *mockProposalContext) GetRenderer() analyzer.Renderer {
640644
return analyzer.NewMarkdownRenderer()
641645
}
642646

643-
func (m *mockProposalContext) DescriptorContext(chainSelector uint64) *analyzer.DescriptorContext {
644-
return &analyzer.DescriptorContext{}
647+
func (m *mockProposalContext) FieldsContext(chainSelector uint64) *analyzer.FieldContext {
648+
return &analyzer.FieldContext{}
645649
}
646650
func (m *mockProposalContext) GetSolanaDecoderRegistry() analyzer.SolanaDecoderRegistry {
647651
// Return a mock SolanaDecoderRegistry with a dummy decoder for testing

engine/cld/legacy/cli/mcmsv2/mcms_v2.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ func buildMCMSv2AnalyzeProposalCmd(
711711
lggr logger.Logger, domain cldf_domain.Domain, proposalCtxProvider analyzer.ProposalContextProvider,
712712
) *cobra.Command {
713713
var outputFile string
714+
var format string
714715

715716
cmd := &cobra.Command{
716717
Use: "analyze-proposal",
@@ -731,6 +732,10 @@ func buildMCMSv2AnalyzeProposalCmd(
731732
return errors.New("expected proposal to be have non-nil *TimelockProposal")
732733
}
733734

735+
// Set renderer based on format flag
736+
renderer := createRendererFromFormat(format)
737+
cfgv2.proposalCtx.SetRenderer(renderer)
738+
734739
var analyzedProposal string
735740
if cfgv2.timelockProposal != nil {
736741
analyzedProposal, err = analyzer.DescribeTimelockProposal(cfgv2.proposalCtx, cfgv2.timelockProposal)
@@ -759,6 +764,7 @@ func buildMCMSv2AnalyzeProposalCmd(
759764
})
760765

761766
cmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file to write analyze result")
767+
cmd.Flags().StringVar(&format, "format", "markdown", "Output format: markdown (default), text")
762768

763769
return cmd
764770
}
@@ -1602,3 +1608,17 @@ func addCallProxyOption(
16021608

16031609
return fmt.Errorf("failed to find call proxy contract for timelock %v", timelockAddress)
16041610
}
1611+
1612+
// createRendererFromFormat creates an appropriate renderer based on the format string.
1613+
// Defaults to markdown renderer for unknown formats.
1614+
func createRendererFromFormat(format string) analyzer.Renderer {
1615+
switch format {
1616+
case "text", "txt":
1617+
return analyzer.NewTextRenderer()
1618+
case "markdown", "md":
1619+
return analyzer.NewMarkdownRenderer()
1620+
default:
1621+
// Default to markdown if format is not specified or invalid
1622+
return analyzer.NewMarkdownRenderer()
1623+
}
1624+
}

experimental/analyzer/analyze.go

Lines changed: 0 additions & 51 deletions
This file was deleted.

experimental/analyzer/aptos_analyzer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func AnalyzeAptosTransaction(ctx ProposalContext, decoder *mcmsaptossdk.Decoder,
3939
Method: errStr.Error(),
4040
}, nil
4141
}
42-
namedArgs, err := toNamedDescriptors(decodedOp)
42+
namedArgs, err := toNamedFields(decodedOp)
4343
if err != nil {
4444
return nil, fmt.Errorf("failed to convert decoded operation to named arguments: %w", err)
4545
}
@@ -48,6 +48,6 @@ func AnalyzeAptosTransaction(ctx ProposalContext, decoder *mcmsaptossdk.Decoder,
4848
Address: mcmsTx.To,
4949
Method: decodedOp.MethodName(),
5050
Inputs: namedArgs,
51-
Outputs: []NamedDescriptor{},
51+
Outputs: []NamedField{},
5252
}, nil
5353
}

experimental/analyzer/aptos_analyzer_test.go

Lines changed: 63 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -39,50 +39,50 @@ func TestAnalyzeAptosTransactions(t *testing.T) {
3939
{
4040
Address: aptosTestAddress,
4141
Method: "ccip_onramp::onramp::initialize",
42-
Inputs: []NamedDescriptor{
43-
{Name: "chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
44-
{Name: "fee_aggregator", Value: SimpleDescriptor{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
45-
{Name: "allowlist_admin", Value: SimpleDescriptor{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
46-
{Name: "dest_chain_selectors", Value: SimpleDescriptor{Value: "[]"}},
47-
{Name: "dest_chain_routers", Value: SimpleDescriptor{Value: "[]"}},
48-
{Name: "dest_chain_allowlist_enabled", Value: SimpleDescriptor{Value: "[]"}},
42+
Inputs: []NamedField{
43+
{Name: "chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
44+
{Name: "fee_aggregator", Value: AddressField{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
45+
{Name: "allowlist_admin", Value: AddressField{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
46+
{Name: "dest_chain_selectors", Value: ArrayField{Elements: nil}},
47+
{Name: "dest_chain_routers", Value: ArrayField{Elements: nil}},
48+
{Name: "dest_chain_allowlist_enabled", Value: ArrayField{Elements: nil}},
4949
},
5050
},
5151
{
5252
Address: aptosTestAddress,
5353
Method: "ccip_offramp::offramp::initialize",
54-
Inputs: []NamedDescriptor{
55-
{Name: "chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
56-
{Name: "permissionless_execution_threshold_seconds", Value: SimpleDescriptor{Value: "28800"}},
57-
{Name: "source_chains_selector", Value: SimpleDescriptor{Value: "[11155111]"}},
58-
{Name: "source_chains_is_enabled", Value: SimpleDescriptor{Value: "[true]"}},
59-
{Name: "source_chains_is_rmn_verification_disabled", Value: SimpleDescriptor{Value: "[false]"}},
60-
{Name: "source_chains_on_ramp", Value: SimpleDescriptor{Value: "[0x0bf3de8c5d3e8a2b34d2beeb17abfcebaf363a59]"}},
54+
Inputs: []NamedField{
55+
{Name: "chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
56+
{Name: "permissionless_execution_threshold_seconds", Value: SimpleField{Value: "28800"}},
57+
{Name: "source_chains_selector", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "11155111"}}}},
58+
{Name: "source_chains_is_enabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "true"}}}},
59+
{Name: "source_chains_is_rmn_verification_disabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "false"}}}},
60+
{Name: "source_chains_on_ramp", Value: ArrayField{Elements: []FieldValue{BytesField{Value: []byte{0xb, 0xf3, 0xde, 0x8c, 0x5d, 0x3e, 0x8a, 0x2b, 0x34, 0xd2, 0xbe, 0xeb, 0x17, 0xab, 0xfc, 0xeb, 0xaf, 0x36, 0x3a, 0x59}}}}},
6161
},
6262
},
6363
{
6464
Address: aptosTestAddress,
6565
Method: "ccip::rmn_remote::initialize",
66-
Inputs: []NamedDescriptor{
67-
{Name: "local_chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
66+
Inputs: []NamedField{
67+
{Name: "local_chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
6868
},
6969
},
7070
{
7171
Address: aptosTestAddress,
7272
Method: "ccip_token_pool::token_pool::initialize",
73-
Inputs: []NamedDescriptor{
74-
{Name: "local_token", Value: SimpleDescriptor{Value: "0x0000000000000000000000000000000000000000000000000000000000000003"}},
75-
{Name: "allowlist", Value: SimpleDescriptor{Value: "[0x0000000000000000000000000000000000000000000000000000000000000001,0x0000000000000000000000000000000000000000000000000000000000000002]"}},
73+
Inputs: []NamedField{
74+
{Name: "local_token", Value: AddressField{Value: "0x0000000000000000000000000000000000000000000000000000000000000003"}},
75+
{Name: "allowlist", Value: ArrayField{Elements: []FieldValue{AddressField{Value: "0x0000000000000000000000000000000000000000000000000000000000000001"}, AddressField{Value: "0x0000000000000000000000000000000000000000000000000000000000000002"}}}},
7676
},
7777
},
7878
{
7979
Address: aptosTestAddress,
8080
Method: "ccip_offramp::offramp::apply_source_chain_config_updates",
81-
Inputs: []NamedDescriptor{
82-
{Name: "source_chains_selector", Value: SimpleDescriptor{Value: "[743186221051783445,16015286601757825753]"}},
83-
{Name: "source_chains_is_enabled", Value: SimpleDescriptor{Value: "[true,false]"}},
84-
{Name: "source_chains_is_rmn_verification_disabled", Value: SimpleDescriptor{Value: "[true,true]"}},
85-
{Name: "source_chains_on_ramp", Value: SimpleDescriptor{Value: "[0xc23071a8ae83671f37bda1dadbc745a9780f632a,0x1c179c2c67953478966a6b460ab4873585b2f341]"}},
81+
Inputs: []NamedField{
82+
{Name: "source_chains_selector", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "743186221051783445"}, SimpleField{Value: "16015286601757825753"}}}},
83+
{Name: "source_chains_is_enabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "true"}, SimpleField{Value: "false"}}}},
84+
{Name: "source_chains_is_rmn_verification_disabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "true"}, SimpleField{Value: "true"}}}},
85+
{Name: "source_chains_on_ramp", Value: ArrayField{Elements: []FieldValue{BytesField{Value: []byte{0xc2, 0x30, 0x71, 0xa8, 0xae, 0x83, 0x67, 0x1f, 0x37, 0xbd, 0xa1, 0xda, 0xdb, 0xc7, 0x45, 0xa9, 0x78, 0xf, 0x63, 0x2a}}, BytesField{Value: []byte{0x1c, 0x17, 0x9c, 0x2c, 0x67, 0x95, 0x34, 0x78, 0x96, 0x6a, 0x6b, 0x46, 0xa, 0xb4, 0x87, 0x35, 0x85, 0xb2, 0xf3, 0x41}}}}},
8686
},
8787
},
8888
},
@@ -95,50 +95,50 @@ func TestAnalyzeAptosTransactions(t *testing.T) {
9595
{
9696
Address: aptosTestAddress,
9797
Method: "ccip_onramp::onramp::initialize",
98-
Inputs: []NamedDescriptor{
99-
{Name: "chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
100-
{Name: "fee_aggregator", Value: SimpleDescriptor{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
101-
{Name: "allowlist_admin", Value: SimpleDescriptor{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
102-
{Name: "dest_chain_selectors", Value: SimpleDescriptor{Value: "[]"}},
103-
{Name: "dest_chain_routers", Value: SimpleDescriptor{Value: "[]"}},
104-
{Name: "dest_chain_allowlist_enabled", Value: SimpleDescriptor{Value: "[]"}},
98+
Inputs: []NamedField{
99+
{Name: "chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
100+
{Name: "fee_aggregator", Value: AddressField{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
101+
{Name: "allowlist_admin", Value: AddressField{Value: "0x13a9f1a109368730f2e355d831ba8fbf5942fb82321863d55de54cb4ebe5d18f"}},
102+
{Name: "dest_chain_selectors", Value: ArrayField{Elements: nil}},
103+
{Name: "dest_chain_routers", Value: ArrayField{Elements: nil}},
104+
{Name: "dest_chain_allowlist_enabled", Value: ArrayField{Elements: nil}},
105105
},
106106
},
107107
{
108108
Address: aptosTestAddress,
109109
Method: "ccip_offramp::offramp::initialize",
110-
Inputs: []NamedDescriptor{
111-
{Name: "chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
112-
{Name: "permissionless_execution_threshold_seconds", Value: SimpleDescriptor{Value: "28800"}},
113-
{Name: "source_chains_selector", Value: SimpleDescriptor{Value: "[11155111]"}},
114-
{Name: "source_chains_is_enabled", Value: SimpleDescriptor{Value: "[true]"}},
115-
{Name: "source_chains_is_rmn_verification_disabled", Value: SimpleDescriptor{Value: "[false]"}},
116-
{Name: "source_chains_on_ramp", Value: SimpleDescriptor{Value: "[0x0bf3de8c5d3e8a2b34d2beeb17abfcebaf363a59]"}},
110+
Inputs: []NamedField{
111+
{Name: "chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
112+
{Name: "permissionless_execution_threshold_seconds", Value: SimpleField{Value: "28800"}},
113+
{Name: "source_chains_selector", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "11155111"}}}},
114+
{Name: "source_chains_is_enabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "true"}}}},
115+
{Name: "source_chains_is_rmn_verification_disabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "false"}}}},
116+
{Name: "source_chains_on_ramp", Value: ArrayField{Elements: []FieldValue{BytesField{Value: []byte{0xb, 0xf3, 0xde, 0x8c, 0x5d, 0x3e, 0x8a, 0x2b, 0x34, 0xd2, 0xbe, 0xeb, 0x17, 0xab, 0xfc, 0xeb, 0xaf, 0x36, 0x3a, 0x59}}}}},
117117
},
118118
},
119119
{
120120
Address: aptosTestAddress,
121121
Method: "ccip::rmn_remote::initialize",
122-
Inputs: []NamedDescriptor{
123-
{Name: "local_chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
122+
Inputs: []NamedField{
123+
{Name: "local_chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
124124
},
125125
},
126126
{
127127
Address: aptosTestAddress,
128128
Method: "ccip_token_pool::token_pool::initialize",
129-
Inputs: []NamedDescriptor{
130-
{Name: "local_token", Value: SimpleDescriptor{Value: "0x0000000000000000000000000000000000000000000000000000000000000003"}},
131-
{Name: "allowlist", Value: SimpleDescriptor{Value: "[0x0000000000000000000000000000000000000000000000000000000000000001,0x0000000000000000000000000000000000000000000000000000000000000002]"}},
129+
Inputs: []NamedField{
130+
{Name: "local_token", Value: AddressField{Value: "0x0000000000000000000000000000000000000000000000000000000000000003"}},
131+
{Name: "allowlist", Value: ArrayField{Elements: []FieldValue{AddressField{Value: "0x0000000000000000000000000000000000000000000000000000000000000001"}, AddressField{Value: "0x0000000000000000000000000000000000000000000000000000000000000002"}}}},
132132
},
133133
},
134134
{
135135
Address: aptosTestAddress,
136136
Method: "ccip_offramp::offramp::apply_source_chain_config_updates",
137-
Inputs: []NamedDescriptor{
138-
{Name: "source_chains_selector", Value: SimpleDescriptor{Value: "[743186221051783445,16015286601757825753]"}},
139-
{Name: "source_chains_is_enabled", Value: SimpleDescriptor{Value: "[true,false]"}},
140-
{Name: "source_chains_is_rmn_verification_disabled", Value: SimpleDescriptor{Value: "[true,true]"}},
141-
{Name: "source_chains_on_ramp", Value: SimpleDescriptor{Value: "[0xc23071a8ae83671f37bda1dadbc745a9780f632a,0x1c179c2c67953478966a6b460ab4873585b2f341]"}},
137+
Inputs: []NamedField{
138+
{Name: "source_chains_selector", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "743186221051783445"}, SimpleField{Value: "16015286601757825753"}}}},
139+
{Name: "source_chains_is_enabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "true"}, SimpleField{Value: "false"}}}},
140+
{Name: "source_chains_is_rmn_verification_disabled", Value: ArrayField{Elements: []FieldValue{SimpleField{Value: "true"}, SimpleField{Value: "true"}}}},
141+
{Name: "source_chains_on_ramp", Value: ArrayField{Elements: []FieldValue{BytesField{Value: []byte{0xc2, 0x30, 0x71, 0xa8, 0xae, 0x83, 0x67, 0x1f, 0x37, 0xbd, 0xa1, 0xda, 0xdb, 0xc7, 0x45, 0xa9, 0x78, 0xf, 0x63, 0x2a}}, BytesField{Value: []byte{0x1c, 0x17, 0x9c, 0x2c, 0x67, 0x95, 0x34, 0x78, 0x96, 0x6a, 0x6b, 0x46, 0xa, 0xb4, 0x87, 0x35, 0x85, 0xb2, 0xf3, 0x41}}}}},
142142
},
143143
},
144144
},
@@ -151,13 +151,13 @@ func TestAnalyzeAptosTransactions(t *testing.T) {
151151
{
152152
Address: aptosTestAddress,
153153
Method: "failed to decode Aptos transaction: could not find function info for ccip_offramp::bad_module::initialize",
154-
Inputs: []NamedDescriptor{},
154+
Inputs: []NamedField{},
155155
},
156156
{
157157
Address: aptosTestAddress,
158158
Method: "ccip::rmn_remote::initialize",
159-
Inputs: []NamedDescriptor{
160-
{Name: "local_chain_selector", Value: SimpleDescriptor{Value: "4457093679053095497"}},
159+
Inputs: []NamedField{
160+
{Name: "local_chain_selector", Value: SimpleField{Value: "4457093679053095497"}},
161161
},
162162
},
163163
},
@@ -199,7 +199,18 @@ func TestAnalyzeAptosTransactions(t *testing.T) {
199199
for j, input := range result.Inputs {
200200
expectedInput := expected.Inputs[j]
201201
require.Equal(t, expectedInput.Name, input.Name, "Input name mismatch for call %d, input %d", i, j)
202-
require.Equal(t, expectedInput.Value.Describe(nil), input.Value.Describe(nil), "Input value mismatch for call %d, input %d", i, j)
202+
require.Equal(t, expectedInput.Value.GetType(), input.Value.GetType(), "Input value type mismatch for call %d, input %d", i, j)
203+
204+
switch expectedField := expectedInput.Value.(type) {
205+
case SimpleField:
206+
if actualField, ok := input.Value.(SimpleField); ok {
207+
require.Equal(t, expectedField.GetValue(), actualField.GetValue(), "SimpleField value mismatch for call %d, input %d", i, j)
208+
} else {
209+
t.Errorf("Expected SimpleField but got %T for call %d, input %d", input.Value, i, j)
210+
}
211+
default:
212+
require.Equal(t, expectedInput.Value, input.Value, "Field value mismatch for call %d, input %d", i, j)
213+
}
203214
}
204215
}
205216
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package analyzer
2+
3+
const (
4+
// Magic number constants
5+
MinStructFieldsForPrettyFormat = 2
6+
MinDataLengthForMethodID = 4
7+
DefaultAnalyzersCount = 2
8+
)
9+
10+
type DecodedCall struct {
11+
Address string
12+
Method string
13+
Inputs []NamedField
14+
Outputs []NamedField
15+
}
16+
17+
// String renders a human-readable representation of the decoded call using the default text renderer.
18+
// This method is kept for backwards compatibility but rendering should be done through renderers.
19+
func (d *DecodedCall) String(context *FieldContext) string {
20+
// Use the text renderer to provide proper formatting
21+
renderer := NewTextRenderer()
22+
return renderer.RenderDecodedCall(d, context)
23+
}

0 commit comments

Comments
 (0)