Skip to content

fix yaml unmarshal converted bigInt that overflow uint64 as string#747

Closed
huangzhen1997 wants to merge 11 commits intomainfrom
jh/patch-yaml-unmarshal-for-big-int-and-update-durable-pipeline
Closed

fix yaml unmarshal converted bigInt that overflow uint64 as string#747
huangzhen1997 wants to merge 11 commits intomainfrom
jh/patch-yaml-unmarshal-for-big-int-and-update-durable-pipeline

Conversation

@huangzhen1997
Copy link
Copy Markdown
Contributor

@huangzhen1997 huangzhen1997 commented Feb 18, 2026

Adding helper support that address the YAML sdk issue, which converts big.int that overflow uint64 to string.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 18, 2026

⚠️ No Changeset found

Latest commit: 6242941

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@huangzhen1997 huangzhen1997 marked this pull request as ready for review February 19, 2026 04:50
@huangzhen1997 huangzhen1997 requested a review from a team as a code owner February 19, 2026 04:50
Copilot AI review requested due to automatic review settings February 19, 2026 04:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds helper functionality to address a YAML SDK issue where big integers that overflow uint64 are incorrectly parsed as strings instead of being preserved as numeric types. The solution introduces a new helper package with functions to recursively traverse YAML-unmarshaled data structures and coerce string representations of large numbers back to *big.Int.

Changes:

  • New helper package with YAML coercion utilities for handling big integers
  • Integration of the helper across multiple YAML unmarshaling locations in the codebase
  • Pattern matching to selectively apply coercion only to specific keys to avoid unintended conversions

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
helper/yaml.go New file containing coercion logic for converting string representations of large integers to *big.Int with pattern matching support
operations/operation_test.go Added helper import and coercion call in existing test
experimental/analyzer/renderer_markdown.go Applied coercion after YAML unmarshaling in markdown rendering
engine/cld/legacy/cli/commands/durable_pipeline_yaml.go Applied coercion to durable pipeline YAML parsing
engine/cld/legacy/cli/commands/durable-pipelines.go Applied coercion to durable pipeline file parsing
engine/cld/config/network/metadata.go Applied coercion in metadata decoding
engine/cld/config/network/config.go Applied coercion in network config loading
engine/cld/config/network/config_test.go Applied coercion in test for network config
engine/cld/config/env/config_test.go Applied coercion in test for environment config

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// elements in a list don't have their own key, so we use the parent key with a ".[]" suffix
// to check if we should coerce big ints in this list.
key := currentKey + ".[]"
x[i] = coerceBigIntStrings(x[i], key, matchFunc).(map[string]any)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type assertion on line 40 can panic if coerceBigIntStrings returns a type that isn't map[string]any. While the []map[string]any case should only contain map[string]any elements, if the recursive call encounters an issue or if the YAML structure changes during coercion, this could panic. Consider using the two-value form of type assertion to handle this case more gracefully.

Suggested change
x[i] = coerceBigIntStrings(x[i], key, matchFunc).(map[string]any)
coerced := coerceBigIntStrings(x[i], key, matchFunc)
if coercedMap, ok := coerced.(map[string]any); ok {
x[i] = coercedMap
}

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +186
// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
yamlInput = helper.CoerceBigIntStringsForKeys(yamlInput, matchFunc)

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test Test_Operation_AsUntypedRelaxed_WithYAMLUnmarshaling includes a call to CoerceBigIntStringsForKeys with the comment "Coerce big int strings as YAML parsing may interpret large numbers as strings", but the test YAML data only contains small integers (10 and 20) that don't overflow int64 or uint64. This test doesn't actually verify the big int coercion functionality. The keys ".A" and ".B" also don't match any of the patterns in DefaultMatchKeysToFix, so the coercion would never happen here anyway. Consider either removing the coercion call from this test (since it's not needed for the test's purpose) or adding a specific test that validates the big int coercion with appropriate test data and matching keys.

Suggested change
// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
yamlInput = helper.CoerceBigIntStringsForKeys(yamlInput, matchFunc)

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 19, 2026 05:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +100
func stringToBigIntIfOverflowInt64(s string) (*big.Int, bool) {
// If it fits int64, keep it as a string (likely user meant a string, or YAML had quotes).
if _, err := strconv.ParseInt(s, 10, 64); err == nil {
return nil, false
} else {
var ne *strconv.NumError
if errors.As(err, &ne) && !errors.Is(ne.Err, strconv.ErrRange) {
// not a range overflow; should be safe to treat as string
return nil, false
}
}

// If it fits uint64, keep it as a string (likely user meant a string, or YAML had quotes).
if _, err := strconv.ParseUint(s, 10, 64); err == nil {
return nil, false
} else {
var ne *strconv.NumError
if errors.As(err, &ne) && !errors.Is(ne.Err, strconv.ErrRange) {
// not a range overflow; should be safe to treat as string
return nil, false
}
}

z, ok := new(big.Int).SetString(s, 10)
if !ok {
return nil, false
}

return z, true
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new YAML coercion logic isn’t covered by a dedicated unit test. Adding helper/yaml_test.go with cases for: (1) an overflow-uint64 numeric string that should become *big.Int, (2) values that fit uint64 that should remain strings, and (3) non-numeric strings that should remain unchanged would prevent regressions.

Copilot uses AI. Check for mistakes.

// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
dpYAML = helper.CoerceBigIntStringsForKeys(dpYAML, matchFunc).(durablePipelineYAML)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoerceBigIntStringsForKeys only walks map[string]any / []any / string; dpYAML is a struct, so this call is effectively a no-op and won’t coerce values inside dpYAML.Changesets. Consider applying coercion to dpYAML.Changesets (or an untyped YAML decode) before further processing; also note this file already deals with map[interface{}]interface{}, which the coercer currently doesn’t handle.

Suggested change
dpYAML = helper.CoerceBigIntStringsForKeys(dpYAML, matchFunc).(durablePipelineYAML)
if dpYAML.Changesets != nil {
dpYAML.Changesets = helper.CoerceBigIntStringsForKeys(dpYAML.Changesets, matchFunc)
}

Copilot uses AI. Check for mistakes.
Comment on lines +309 to +312
// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
dpFile = helper.CoerceBigIntStringsForKeys(dpFile, matchFunc).(durablePipelineFile)

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dpFile is a typed struct whose Changesets field is a yaml.Node; CoerceBigIntStringsForKeys currently doesn’t traverse structs or yaml.Node, so this coercion won’t have any effect. If coercion is needed here, apply it to the resolved untyped payload/map (e.g., after decode) or extend the helper to handle yaml.Node/structs via reflection.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +20
// converts *string* values that look like integers AND overflow int64 into *big.Int.
// It mutates map[string]any and []any in-place.
func CoerceBigIntStrings(v any) any {
return coerceBigIntStrings(v, "", nil)
}

// CoerceBigIntStringsForKeys is the safer variant: only converts numeric strings
// under specific leaf keys (e.g. "maxfeejuelspermsg").
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoerceBigIntStrings/CoerceBigIntStringsForKeys docstring says it converts strings that overflow int64, but stringToBigIntIfOverflowInt64 only returns a *big.Int when the value overflows uint64 as well (values that fit in uint64 are left as strings). Please align the naming/docs with the actual behavior (e.g., rename helper to indicate uint64 overflow, or change the logic to match the comment).

Suggested change
// converts *string* values that look like integers AND overflow int64 into *big.Int.
// It mutates map[string]any and []any in-place.
func CoerceBigIntStrings(v any) any {
return coerceBigIntStrings(v, "", nil)
}
// CoerceBigIntStringsForKeys is the safer variant: only converts numeric strings
// under specific leaf keys (e.g. "maxfeejuelspermsg").
// converts *string* values that look like integers AND overflow uint64 into *big.Int.
// (Values that fit within int64 or uint64 are left as strings.) It mutates map[string]any
// and []any in-place.
func CoerceBigIntStrings(v any) any {
return coerceBigIntStrings(v, "", nil)
}
// CoerceBigIntStringsForKeys is the safer variant: only converts numeric strings that
// overflow uint64 under specific leaf keys (e.g. "maxfeejuelspermsg").

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +68
func coerceBigIntStrings(v any, currentKey string, matchFunc KeyMatchFunc) any {
switch x := v.(type) {
case map[string]any:
for k, vv := range x {
key := currentKey + "." + k
x[k] = coerceBigIntStrings(vv, key, matchFunc)
}

return x

case []map[string]any:
for i := range x {
// elements in a list don't have their own key, so we use the parent key with a ".[]" suffix
// to check if we should coerce big ints in this list.
key := currentKey + ".[]"
x[i] = coerceBigIntStrings(x[i], key, matchFunc).(map[string]any)
}

return x

case []any:
for i := range x {
// elements in a list don't have their own key, so we use the parent key with a ".[]" suffix
// to check if we should coerce big ints in this list.
key := currentKey + ".[]"
x[i] = coerceBigIntStrings(x[i], key, matchFunc)
}

return x

case string:
if matchFunc != nil {
if !matchFunc(currentKey) {
return x
}
}
if bi, ok := stringToBigIntIfOverflowInt64(x); ok {
return bi // IMPORTANT: *big.Int (pointer), not big.Int (value)
}

return x
default:
return v
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coerceBigIntStrings only handles map[string]any, []any, and string. Callers in this PR pass typed structs (e.g., Config, durablePipelineYAML, generic T), so the traversal becomes a no-op and the coercion never reaches nested any/map fields. Consider either (a) applying coercion to the untyped YAML structure before decoding into structs, or (b) extending the walker to handle structs/pointers via reflection so these call sites actually do something.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +131
func DefaultMatchKeysToFix(key string) bool {
patterns := []string{
// Matching examples:
// .[].0006_deploy_evm_chain_contracts.payload.contractparamsperchain.7759470850252068959.feequoterparams.maxfeejuelspermsg
`^.*\.contractparamsperchain\.\d+\.feequoterparams\.maxfeejuelspermsg$`,
// Matching examples:
// .[].0009_deploy_ton_ccip_contracts.payload.chains.13879075125137744094.maxfeejuelspermsg
`^.*\.chains\.\d+\.maxfeejuelspermsg$`,
// Matching examples:
// .[].0016_configure_lanes.payload.lanes.[].chaina.tokenprices.EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99
// .[].0016_configure_lanes.payload.lanes.[].chainb.tokenprices.0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[a-zA-Z0-9]+$`,
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.feequoterdestchainconfig\.gasmultiplierweipereth$`,
}

matched := false
for _, p := range patterns {
var err error
matched, err = regexp.MatchString(p, key)
if err != nil {
break
}
if matched {
break
}
}

return matched
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultMatchKeysToFix recompiles regexes on every call (patterns slice allocation + regexp.MatchString compilation). Since this match function may be invoked for many scalar nodes during a walk, consider precompiling the regexps once at package init (regexp.MustCompile) and then iterating over []*regexp.Regexp for better performance and simpler error handling.

Suggested change
func DefaultMatchKeysToFix(key string) bool {
patterns := []string{
// Matching examples:
// .[].0006_deploy_evm_chain_contracts.payload.contractparamsperchain.7759470850252068959.feequoterparams.maxfeejuelspermsg
`^.*\.contractparamsperchain\.\d+\.feequoterparams\.maxfeejuelspermsg$`,
// Matching examples:
// .[].0009_deploy_ton_ccip_contracts.payload.chains.13879075125137744094.maxfeejuelspermsg
`^.*\.chains\.\d+\.maxfeejuelspermsg$`,
// Matching examples:
// .[].0016_configure_lanes.payload.lanes.[].chaina.tokenprices.EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99
// .[].0016_configure_lanes.payload.lanes.[].chainb.tokenprices.0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[a-zA-Z0-9]+$`,
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.feequoterdestchainconfig\.gasmultiplierweipereth$`,
}
matched := false
for _, p := range patterns {
var err error
matched, err = regexp.MatchString(p, key)
if err != nil {
break
}
if matched {
break
}
}
return matched
var defaultMatchKeyRegexps = []*regexp.Regexp{
// Matching examples:
// .[].0006_deploy_evm_chain_contracts.payload.contractparamsperchain.7759470850252068959.feequoterparams.maxfeejuelspermsg
regexp.MustCompile(`^.*\.contractparamsperchain\.\d+\.feequoterparams\.maxfeejuelspermsg$`),
// Matching examples:
// .[].0009_deploy_ton_ccip_contracts.payload.chains.13879075125137744094.maxfeejuelspermsg
regexp.MustCompile(`^.*\.chains\.\d+\.maxfeejuelspermsg$`),
// Matching examples:
// .[].0016_configure_lanes.payload.lanes.[].chaina.tokenprices.EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99
// .[].0016_configure_lanes.payload.lanes.[].chainb.tokenprices.0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
regexp.MustCompile(`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[a-zA-Z0-9]+$`),
regexp.MustCompile(`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.feequoterdestchainconfig\.gasmultiplierweipereth$`),
}
func DefaultMatchKeysToFix(key string) bool {
for _, re := range defaultMatchKeyRegexps {
if re.MatchString(key) {
return true
}
}
return false

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +186
// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
yamlInput = helper.CoerceBigIntStringsForKeys(yamlInput, matchFunc)

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this test YAML, the keys are A/B so DefaultMatchKeysToFix won’t match and the coercion call will be a no-op. If the intent is to exercise the overflow-uint64 YAML-string fix, adjust the test YAML to include a key path that matches the matchFunc and a value > uint64, and assert it becomes *big.Int (or call CoerceBigIntStrings without key filtering for this test).

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 19, 2026 06:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
dpFile = helper.CoerceBigIntStringsForKeys(dpFile, matchFunc).(durablePipelineFile)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type assertion can panic if CoerceBigIntStringsForKeys doesn't return a durablePipelineFile. The helper function recursively processes the data structure and returns interface{}, so there's no compile-time guarantee that the returned value will be of the expected type. If the YAML structure changes or contains unexpected types, this will panic at runtime. Consider using the two-value form: if result, ok := helper.CoerceBigIntStringsForKeys(dpFile, matchFunc).(durablePipelineFile); ok { dpFile = result } else { return fmt.Errorf("failed to coerce big int strings") }.

Suggested change
dpFile = helper.CoerceBigIntStringsForKeys(dpFile, matchFunc).(durablePipelineFile)
coerced := helper.CoerceBigIntStringsForKeys(dpFile, matchFunc)
result, ok := coerced.(durablePipelineFile)
if !ok {
return fmt.Errorf("failed to coerce big int strings to durablePipelineFile, got %T", coerced)
}
dpFile = result

Copilot uses AI. Check for mistakes.
// Matching examples:
// .[].0016_configure_lanes.payload.lanes.[].chaina.tokenprices.EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99
// .[].0016_configure_lanes.payload.lanes.[].chainb.tokenprices.0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[a-zA-Z0-9]+$`,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern uses [a-zA-Z0-9]+ which only matches alphanumeric characters, but the test case shows token addresses like "0xb1D4538B4571d411F07960EF2838Ce337FE1E80E" which contain lowercase 'x' after '0x'. While this particular address will match, TON addresses like "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99" from the comment on line 113 will also match. However, if token addresses can contain other characters (like underscores, hyphens, or other special characters), this pattern may be too restrictive. Consider whether the pattern should be more permissive, e.g., [a-zA-Z0-9_-]+ or [^\\.]+.

Suggested change
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[a-zA-Z0-9]+$`,
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[^.]+$`,

Copilot uses AI. Check for mistakes.

// CoerceBigIntStrings walks a yaml.Unmarshal result (maps/slices/scalars) and
// converts *string* values that look like integers AND overflow int64 into *big.Int.
// It mutates map[string]any and []any in-place.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function documentation states "It mutates map[string]any and []any in-place" (line 14), but callers are reassigning the result back to the original variable (e.g., line 186 in operations/operation_test.go: yamlInput = helper.CoerceBigIntStringsForKeys(yamlInput, matchFunc)). This is correct since for scalars (strings, ints, etc.) the function returns a new value rather than mutating. However, the documentation could be clearer about when mutation occurs vs when a new value is returned. Consider clarifying: "It mutates map[string]any and []any in-place, but returns new values for scalars."

Suggested change
// It mutates map[string]any and []any in-place.
// It mutates map[string]any and []any in-place, but returns new values for scalar values.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +90
func TestCoerceBigIntStrings(t *testing.T) {
bigVal := "18446744073709551616" // overflows uint64
expectedBig, _ := new(big.Int).SetString(bigVal, 10)

t.Run("nil and scalars pass through", func(t *testing.T) {
assert.Nil(t, CoerceBigIntStrings(nil))
assert.Equal(t, 42, CoerceBigIntStrings(42))
assert.Equal(t, "12345", CoerceBigIntStrings("12345"))
})

t.Run("overflow string converted to big.Int", func(t *testing.T) {
result := CoerceBigIntStrings(bigVal)
bi, ok := result.(*big.Int)
require.True(t, ok, "expected *big.Int, got %T", result)
assert.Equal(t, expectedBig, bi)
})

t.Run("map with mixed values", func(t *testing.T) {
input := map[string]any{
"name": "test",
"value": bigVal,
"count": 5,
}
result := CoerceBigIntStrings(input).(map[string]any)
assert.Equal(t, "test", result["name"])
assert.Equal(t, expectedBig, result["value"])
assert.Equal(t, 5, result["count"])
})

t.Run("nested maps and slices", func(t *testing.T) {
input := map[string]any{
"level1": map[string]any{
"level2": []any{
map[string]any{"level3": bigVal},
},
},
}
result := CoerceBigIntStrings(input).(map[string]any)
l1 := result["level1"].(map[string]any)
l2 := l1["level2"].([]any)
l3 := l2[0].(map[string]any)
assert.Equal(t, expectedBig, l3["level3"])
})

t.Run("slice of typed maps", func(t *testing.T) {
input := []map[string]any{
{"key": bigVal},
{"key": "small"},
}
result := CoerceBigIntStrings(input).([]map[string]any)
assert.Equal(t, expectedBig, result[0]["key"])
assert.Equal(t, "small", result[1]["key"])
})
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test doesn't demonstrate the actual YAML parsing issue this PR aims to fix. The test uses hardcoded values like "18446744073709551616" as strings, but doesn't show a case where YAML unmarshaling converts a large number to a string (which is the problem described in the PR). Consider adding a test that unmarshals actual YAML containing large numbers without quotes and verifies they are correctly converted to big.Int after applying the helper function.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +133
result := CoerceBigIntStringsForKeys(input, func(string) bool { return true }).(map[string]any)
assert.Equal(t, expectedBig, result["target"])

// matchNone keeps strings
input = map[string]any{"target": bigVal}
result = CoerceBigIntStringsForKeys(input, func(string) bool { return false }).(map[string]any)
assert.Equal(t, bigVal, result["target"])
})

t.Run("selective key matching", func(t *testing.T) {
matchTarget := func(key string) bool { return key == ".target" }
input := map[string]any{"target": bigVal, "other": bigVal}
result := CoerceBigIntStringsForKeys(input, matchTarget).(map[string]any)
assert.Equal(t, expectedBig, result["target"])
assert.Equal(t, bigVal, result["other"])
})

t.Run("nested and list key paths", func(t *testing.T) {
matchNested := func(key string) bool { return key == ".outer.inner" }
input := map[string]any{
"outer": map[string]any{"inner": bigVal, "skip": bigVal},
}
result := CoerceBigIntStringsForKeys(input, matchNested).(map[string]any)
inner := result["outer"].(map[string]any)
assert.Equal(t, expectedBig, inner["inner"])
assert.Equal(t, bigVal, inner["skip"])

matchList := func(key string) bool { return key == ".items.[].val" }
input2 := map[string]any{
"items": []map[string]any{{"val": bigVal}},
}
result2 := CoerceBigIntStringsForKeys(input2, matchList).(map[string]any)
items := result2["items"].([]map[string]any)
assert.Equal(t, expectedBig, items[0]["val"])
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test uses unsafe type assertions (lines 100, 105, 112, 122, 123, 131, 132) that will panic if the returned type is incorrect. While these are in test code and panic is acceptable for test failures, it would be more descriptive to use require.IsType or the two-value form of type assertion with an explicit assertion, so test output clearly indicates what went wrong.

Copilot uses AI. Check for mistakes.
}

func stringToBigIntIfOverflowInt64(s string) (*big.Int, bool) {
// If it fits int64, keep it as a string (likely user meant a string, or YAML had quotes).
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions "int64" but should refer to "uint64" since the function checks for values that overflow uint64, not int64. The function only converts strings to big.Int if they overflow both int64 AND uint64.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 19, 2026 06:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +12 to +14
// CoerceBigIntStrings walks a yaml.Unmarshal result (maps/slices/scalars) and
// converts *string* values that look like integers AND overflow int64 into *big.Int.
// It mutates map[string]any and []any in-place.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says this converts strings that overflow int64, but stringToBigIntIfOverflowInt64 only converts when the value overflows uint64 (values that fit uint64 but not int64 are intentionally left as strings). Please update the comments (and/or rename the helper) so behavior and naming match what the function actually does.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +34
return x

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coerceBigIntStrings does not handle map[interface{}]interface{} (a common result from YAML decoding in parts of this repo, e.g. the JSON-safe conversion helpers). That means large numeric strings nested under those maps will not be coerced. Consider adding support for map[any]any/map[interface{}]interface{} (and converting keys to string for path matching) so coercion works consistently.

Suggested change
return x
return x
case map[interface{}]interface{}:
for k, vv := range x {
ks, ok := k.(string)
if !ok {
// Skip non-string keys since path matching is string-based.
continue
}
key := currentKey + "." + ks
x[k] = coerceBigIntStrings(vv, key, matchFunc)
}
return x

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +129
func DefaultMatchKeysToFix(key string) bool {
patterns := []string{
// Matching examples:
// .[].0006_deploy_evm_chain_contracts.payload.contractparamsperchain.7759470850252068959.feequoterparams.maxfeejuelspermsg
`^.*\.contractparamsperchain\.\d+\.feequoterparams\.maxfeejuelspermsg$`,
// Matching examples:
// .[].0009_deploy_ton_ccip_contracts.payload.chains.13879075125137744094.maxfeejuelspermsg
`^.*\.chains\.\d+\.maxfeejuelspermsg$`,
// Matching examples:
// .[].0016_configure_lanes.payload.lanes.[].chaina.tokenprices.EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99
// .[].0016_configure_lanes.payload.lanes.[].chainb.tokenprices.0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.tokenprices\.[a-zA-Z0-9]+$`,
`^.*\.lanes\.\[\]\.(?:chaina|chainb)\.feequoterdestchainconfig\.gasmultiplierweipereth$`,
}

matched := false
for _, p := range patterns {
var err error
matched, err = regexp.MatchString(p, key)
if err != nil {
break
}
if matched {
break
}
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultMatchKeysToFix recompiles regex patterns on every call via regexp.MatchString and re-allocates the patterns slice. Since this function may be called for every leaf during a traversal, this can become a hot path. Precompile the regexps once at package init (e.g., var defaultKeyPatterns = []*regexp.Regexp{...}) and just loop over re.MatchString(key).

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +22
func TestStringToBigIntIfOverflowInt64(t *testing.T) {
tests := []struct {
name string
input string
wantOK bool
want string
}{
{"fits int64", "12345", false, ""},
{"fits uint64 but not int64", "18446744073709551615", false, ""},
{"overflows uint64", "18446744073709551616", true, "18446744073709551616"},
{"very large number", "115792089237316195423570985008687907853269984665640564039457584007913129639935", true, "115792089237316195423570985008687907853269984665640564039457584007913129639935"},
{"non-numeric string", "hello", false, ""},
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name TestStringToBigIntIfOverflowInt64 (and the helper name) suggests int64 overflow, but the behavior under test only converts values that overflow uint64. Renaming the test (and potentially the helper) would make intent clearer and prevent confusion about which values get coerced.

Copilot uses AI. Check for mistakes.
@cl-sonarqube-production
Copy link
Copy Markdown

@graham-chainlink
Copy link
Copy Markdown
Collaborator

Hey Joe, you might be interested in https://github.com/smartcontractkit/chainlink-deployments/pull/10610 as a workaround

Copilot AI review requested due to automatic review settings February 19, 2026 17:02
@huangzhen1997 huangzhen1997 marked this pull request as draft February 19, 2026 17:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +12 to +16
// CoerceBigIntStrings walks a yaml.Unmarshal result (maps/slices/scalars) and
// converts *string* values that look like integers AND overflow int64 into *big.Int.
// It mutates map[string]any and []any in-place.
func CoerceBigIntStrings(v any) any {
return coerceBigIntStrings(v, "", nil)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coercion helper docs/naming are inconsistent with the actual behavior: CoerceBigIntStrings/stringToBigIntIfOverflowInt64 only convert when the numeric string overflows uint64 (values that fit uint64 but not int64 are intentionally left as strings per tests). Please rename/update the function and comment to reflect the real criterion (overflow uint64), or adjust the logic to match the stated int64-overflow behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +76
// Coerce big int strings as YAML parsing may interpret large numbers as strings
matchFunc := helper.DefaultMatchKeysToFix
target = helper.CoerceBigIntStringsForKeys(target, matchFunc).(T)

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoerceBigIntStringsForKeys does not traverse structs, so calling it on the decoded target (type parameter T) will typically return the value unchanged. Also, DefaultMatchKeysToFix is tailored to durable-pipeline key paths and is unlikely to match metadata keys. If metadata needs this fix, run coercion on the untyped map/slice form (before unmarshalling into T) and/or allow callers to provide an appropriate match function.

Copilot uses AI. Check for mistakes.
@huangzhen1997
Copy link
Copy Markdown
Contributor Author

close as we have merged this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants