From d83f75cd50b5a639d86de2757e595ca41f7df923 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Fri, 27 Feb 2026 15:50:13 +0000 Subject: [PATCH 01/22] Add utility function to attempt to decode nested errors from reverts Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 95 +++++++++++++++++++++++++ pkg/abi/abi_test.go | 165 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 9aaff0fb..ffb44302 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -349,6 +349,101 @@ func (a ABI) ErrorStringCtx(ctx context.Context, revertData []byte) (strError st return strError, ok } +const maxNestedRevertDepth = 10 + +// UnwrapErrorStringCtx is like ErrorStringCtx but handles nested errors caused by +// Solidity contracts that catch a revert and re-throw using string(reason). This +// embeds raw ABI-encoded error data (including null bytes from ABI padding) inside +// the new Error(string). The function scans for all known error selectors, +// recursively decodes nested Error(string) chains, and formats known custom errors. +// Any undecoded binary data is hex-encoded. +func (a ABI) UnwrapErrorStringCtx(ctx context.Context, revertData []byte) (string, bool) { + e, cv, ok := a.ParseErrorCtx(ctx, revertData) + if !ok { + return "", false + } + // For Error(string), unwrap any nested errors inside the decoded string + if e.Name == "Error" && len(cv.Children) == 1 { + if strVal, ok := cv.Children[0].Value.(string); ok { + return unwrapNestedRevertReasons(ctx, a, strVal, 0), true + } + } + // For other error types, format directly + strError := FormatErrorStringCtx(ctx, e, cv) + return strError, strError != "" +} + +// UnwrapErrorString is a convenience wrapper for UnwrapErrorStringCtx. +func (a ABI) UnwrapErrorString(revertData []byte) (string, bool) { + return a.UnwrapErrorStringCtx(context.Background(), revertData) +} + +// unwrapNestedRevertReasons recursively decodes Error(string) values that contain +// embedded ABI-encoded error data from Solidity catch-and-rethrow patterns. +func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) string { + if depth >= maxNestedRevertDepth { + return sanitizeBinaryString([]byte(s)) + } + + raw := []byte(s) + + // Find the earliest occurrence of any known error selector + bestIdx := -1 + var bestEntry *Entry + errorsWithDefault := append(ABI{ + {Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, + }, a...) + for _, e := range errorsWithDefault { + if e.Type != Error { + continue + } + sel := e.FunctionSelectorBytes() + if idx := bytes.Index(raw, sel); idx >= 0 && (bestIdx < 0 || idx < bestIdx) { + bestIdx = idx + bestEntry = e + } + } + + if bestIdx < 0 { + return sanitizeBinaryString(raw) + } + + prefix := sanitizeBinaryString(raw[:bestIdx]) + embedded := raw[bestIdx:] + + cv, err := bestEntry.DecodeCallDataCtx(ctx, embedded) + if err == nil { + if bestEntry.Name == "Error" && len(cv.Children) == 1 { + if nested, ok := cv.Children[0].Value.(string); ok { + return prefix + unwrapNestedRevertReasons(ctx, a, nested, depth+1) + } + } + formatted := FormatErrorStringCtx(ctx, bestEntry, cv) + if formatted != "" { + return prefix + formatted + } + } + + log.L(ctx).Debugf("Could not decode nested revert at depth %d, hex-encoding remaining %d bytes", depth, len(embedded)) + return prefix + "0x" + hex.EncodeToString(embedded) +} + +// SanitizeBinaryString returns the input as a text string if it is entirely +// printable ASCII, or hex-encodes the entire input otherwise. This ensures the +// output is always safe for database TEXT columns and human-readable logging. +func SanitizeBinaryString(raw []byte) string { + return sanitizeBinaryString(raw) +} + +func sanitizeBinaryString(raw []byte) string { + for _, b := range raw { + if b < 32 || b >= 127 { + return "0x" + hex.EncodeToString(raw) + } + } + return string(raw) +} + func FormatErrorStringCtx(ctx context.Context, e *Entry, cv *ComponentValue) string { var ok bool var parsed []interface{} diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 9cb5bb80..aa525a64 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -17,10 +17,12 @@ package abi import ( + "encoding/binary" "encoding/hex" "encoding/json" "fmt" "math/big" + "strings" "testing" "github.com/hyperledger/firefly-common/pkg/ffapi" @@ -1074,6 +1076,169 @@ func TestErrorString(t *testing.T) { } +// buildErrorStringABI builds the raw ABI encoding for Error(string) with the given message bytes. +func buildErrorStringABI(msgBytes []byte) []byte { + defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + sel := defaultErr.FunctionSelectorBytes() + offset := make([]byte, 32) + binary.BigEndian.PutUint64(offset[24:], 0x20) + length := make([]byte, 32) + binary.BigEndian.PutUint64(length[24:], uint64(len(msgBytes))) + paddedLen := ((len(msgBytes) + 31) / 32) * 32 + data := make([]byte, paddedLen) + copy(data, msgBytes) + result := make([]byte, 0, 4+32+32+paddedLen) + result = append(result, sel...) + result = append(result, offset...) + result = append(result, length...) + result = append(result, data...) + return result +} + +func TestUnwrapErrorStringPlainError(t *testing.T) { + revertData := ethtypes.MustNewHexBytes0xPrefix( + "0x08c379a0" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "000000000000000000000000000000000000000000000000000000000000001a" + + "4e6f7420656e6f7567682045746865722070726f76696465642e000000000000") + + result, ok := ABI{}.UnwrapErrorString(revertData) + assert.True(t, ok) + assert.Equal(t, "Not enough Ether provided.", result) +} + +func TestUnwrapErrorStringSingleNested(t *testing.T) { + revertData := ethtypes.MustNewHexBytes0xPrefix( + "0x08c379a00000000000000000000000000000000000000000000000000000000000000020" + + "000000000000000000000000000000000000000000000000000000000000006b" + + "6f757465723a20" + + "08c379a0" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000013" + + "696e6e6572206572726f72206d65737361676500000000000000000000000000" + + "000000000000000000000000000000000000000000") + + result, ok := ABI{}.UnwrapErrorString(revertData) + assert.True(t, ok) + assert.Equal(t, "outer: inner error message", result) +} + +func TestUnwrapErrorStringDoubleNested(t *testing.T) { + revertData := ethtypes.MustNewHexBytes0xPrefix( + "0x08c379a0" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "00000000000000000000000000000000000000000000000000000000000000cc" + + "6c6576656c313a20" + + "08c379a0" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "000000000000000000000000000000000000000000000000000000000000006c" + + "6c6576656c323a20" + + "08c379a0" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "000000000000000000000000000000000000000000000000000000000000000d" + + "64656570657374206572726f720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + + result, ok := ABI{}.UnwrapErrorString(revertData) + assert.True(t, ok) + assert.Equal(t, "level1: level2: deepest error", result) +} + +func TestUnwrapErrorStringNestedCustomError(t *testing.T) { + customABI := ABI{ + {Type: Error, Name: "MyCustomError", Inputs: ParameterArray{{Type: "bytes"}}}, + } + customSelector := hex.EncodeToString(customABI[0].FunctionSelectorBytes()) + + revertData := ethtypes.MustNewHexBytes0xPrefix( + "0x08c379a0" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "000000000000000000000000000000000000000000000000000000000000007c" + + "5b3430345d303164202d206361756768742062797465733a" + + customSelector + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "deadbeef00000000000000000000000000000000000000000000000000000000" + + "00000000") + + // Without the custom ABI, the nested section can't be decoded + result, ok := ABI{}.UnwrapErrorString(revertData) + assert.True(t, ok) + assert.True(t, strings.HasPrefix(result, "0x")) + + // With the custom ABI, the nested error is decoded + result, ok = customABI.UnwrapErrorString(revertData) + assert.True(t, ok) + assert.Equal(t, `[404]01d - caught bytes:MyCustomError("0xdeadbeef")`, result) +} + +func TestUnwrapErrorStringUnknownSelector(t *testing.T) { + // Unknown top-level selector + _, ok := ABI{}.UnwrapErrorString([]byte{0x11, 0x22, 0x33, 0x44}) + assert.False(t, ok) +} + +func TestUnwrapErrorStringMalformedNestedABI(t *testing.T) { + defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + sel := defaultErr.FunctionSelectorBytes() + + badData := "prefix:" + string(sel) + "truncated" + outerABI := buildErrorStringABI([]byte(badData)) + + result, ok := ABI{}.UnwrapErrorString(outerABI) + assert.True(t, ok) + assert.Equal(t, "prefix:0x08c379a07472756e6361746564", result) +} + +func TestUnwrapErrorStringDepthLimit(t *testing.T) { + innerABI := buildErrorStringABI([]byte("should not decode")) + s := "prefix:" + string(innerABI) + outerABI := buildErrorStringABI([]byte(s)) + + // Passes through processRevertReason, then into unwrap at depth 0. + // We can't easily test depth=10 through the public API without 10 levels of nesting, + // so test the internal function directly. + result := unwrapNestedRevertReasons(nil, ABI{}, s, maxNestedRevertDepth) + assert.True(t, strings.HasPrefix(result, "0x")) + assert.NotEqual(t, "prefix:should not decode", result) + + // At depth max-1, it still decodes + result = unwrapNestedRevertReasons(nil, ABI{}, s, maxNestedRevertDepth-1) + assert.Equal(t, "prefix:should not decode", result) + + _ = outerABI // suppress unused +} + +func TestUnwrapErrorStringCustomBeforeDefaultError(t *testing.T) { + customABI := ABI{ + {Type: Error, Name: "EarlyErr", Inputs: ParameterArray{{Type: "uint256"}}}, + } + customSel := customABI[0].FunctionSelectorBytes() + + arg := make([]byte, 32) + binary.BigEndian.PutUint64(arg[24:], 42) + customEncoded := append([]byte(nil), customSel...) + customEncoded = append(customEncoded, arg...) + + innerErrorABI := buildErrorStringABI([]byte("late-error")) + // Custom selector appears before the Error(string) selector + s := "head:" + string(customEncoded) + "middle:" + string(innerErrorABI) + outerABI := buildErrorStringABI([]byte(s)) + + result, ok := customABI.UnwrapErrorString(outerABI) + assert.True(t, ok) + assert.Equal(t, `head:EarlyErr("42")`, result) +} + +func TestSanitizeBinaryString(t *testing.T) { + assert.Equal(t, "", SanitizeBinaryString(nil)) + assert.Equal(t, "", SanitizeBinaryString([]byte{})) + assert.Equal(t, "hello world", SanitizeBinaryString([]byte("hello world"))) + assert.Equal(t, "0xdeadbeef", SanitizeBinaryString([]byte{0xde, 0xad, 0xbe, 0xef})) + assert.Equal(t, "0x000000", SanitizeBinaryString([]byte{0x00, 0x00, 0x00})) + assert.Equal(t, "0x736f6d65206572726f72000000", SanitizeBinaryString([]byte("some error\x00\x00\x00"))) + assert.Equal(t, "0x0168656c6c6f", SanitizeBinaryString([]byte{0x01, 'h', 'e', 'l', 'l', 'o'})) +} + func TestUnnamedInputOutput(t *testing.T) { sampleABI := ABI{ From ca9568a360bdc984936356e6e1c34fb02e7422e0 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Fri, 27 Feb 2026 15:54:58 +0000 Subject: [PATCH 02/22] remove duplication Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index ffb44302..e5f60571 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -382,7 +382,7 @@ func (a ABI) UnwrapErrorString(revertData []byte) (string, bool) { // embedded ABI-encoded error data from Solidity catch-and-rethrow patterns. func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) string { if depth >= maxNestedRevertDepth { - return sanitizeBinaryString([]byte(s)) + return SanitizeBinaryString([]byte(s)) } raw := []byte(s) @@ -405,10 +405,10 @@ func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) } if bestIdx < 0 { - return sanitizeBinaryString(raw) + return SanitizeBinaryString(raw) } - prefix := sanitizeBinaryString(raw[:bestIdx]) + prefix := SanitizeBinaryString(raw[:bestIdx]) embedded := raw[bestIdx:] cv, err := bestEntry.DecodeCallDataCtx(ctx, embedded) @@ -432,10 +432,6 @@ func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) // printable ASCII, or hex-encodes the entire input otherwise. This ensures the // output is always safe for database TEXT columns and human-readable logging. func SanitizeBinaryString(raw []byte) string { - return sanitizeBinaryString(raw) -} - -func sanitizeBinaryString(raw []byte) string { for _, b := range raw { if b < 32 || b >= 127 { return "0x" + hex.EncodeToString(raw) From 4cfdcaa0bf824ecc5789896620078c52c6504154 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Fri, 27 Feb 2026 16:00:40 +0000 Subject: [PATCH 03/22] Do a single efficient pass on string instead of a pass per selector Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index e5f60571..8a0c712b 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -387,9 +387,9 @@ func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) raw := []byte(s) - // Find the earliest occurrence of any known error selector - bestIdx := -1 - var bestEntry *Entry + // Build a lookup map of 4-byte selectors so we can scan the string once + type selectorKey = [4]byte + selectors := make(map[selectorKey]*Entry) errorsWithDefault := append(ABI{ {Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, }, a...) @@ -398,9 +398,27 @@ func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) continue } sel := e.FunctionSelectorBytes() - if idx := bytes.Index(raw, sel); idx >= 0 && (bestIdx < 0 || idx < bestIdx) { - bestIdx = idx - bestEntry = e + if len(sel) >= 4 { + var key selectorKey + copy(key[:], sel[:4]) + if _, exists := selectors[key]; !exists { + selectors[key] = e + } + } + } + + // Single pass: walk through the bytes looking for any known selector + bestIdx := -1 + var bestEntry *Entry + if len(raw) >= 4 { + for i := 0; i <= len(raw)-4; i++ { + var key selectorKey + copy(key[:], raw[i:i+4]) + if e, ok := selectors[key]; ok { + bestIdx = i + bestEntry = e + break + } } } From 66d919f6bdd2fc625f9d8dd6d80b6d1d101d0cfb Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Mon, 2 Mar 2026 14:00:17 +0000 Subject: [PATCH 04/22] dont checkin allow for local rebuild Signed-off-by: Dave Crighton --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 404b5393..758927e7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/hyperledger/firefly-signer +module github.com/davecrighton/firefly-signer go 1.23.0 From 430958521caa5c30ec50e8bdc9d5eaa985eeea27 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 14 Apr 2026 12:21:58 +0100 Subject: [PATCH 05/22] Create structs to represent nested errors Signed-off-by: Dave Crighton --- go.mod | 2 +- pkg/abi/reverterror.go | 107 ++++++++++++ pkg/abi/reverterror_test.go | 328 ++++++++++++++++++++++++++++++++++++ 3 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 pkg/abi/reverterror.go create mode 100644 pkg/abi/reverterror_test.go diff --git a/go.mod b/go.mod index 758927e7..404b5393 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/davecrighton/firefly-signer +module github.com/hyperledger/firefly-signer go 1.23.0 diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go new file mode 100644 index 00000000..05f6f77e --- /dev/null +++ b/pkg/abi/reverterror.go @@ -0,0 +1,107 @@ +// Copyright © 2026 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package abi + +import ( + "context" + "strings" +) + +// RevertError represents a decoded Solidity revert error. For nested errors +// (where a contract catches a revert and re-throws with the original error +// embedded in the string), the Nested field links to the inner decoded error, +// forming a recursive chain. +type RevertError struct { + ErrorEntry *Entry // the matched ABI error entry at this level + cv *ComponentValue // decoded ABI data for this level + Prefix string // readable text before a nested error + Nested *RevertError // recursively decoded inner error, nil if none +} + +// String returns a human-readable representation of the full error chain. +// It concatenates the Prefix at each level, with the leaf error formatted +// as ErrorName(arg1,arg2,...). +func (r *RevertError) String() string { + if r == nil { + return "" + } + var b strings.Builder + b.WriteString(r.Prefix) + if r.Nested != nil { + b.WriteString(r.Nested.String()) + } else { + b.WriteString(FormatErrorStringCtx(context.Background(), r.ErrorEntry, r.cv)) + } + return b.String() +} + +// Signature returns the ABI signature of the error at this level, +// e.g. "Error(string)" or "AnError(string,uint256)". +func (r *RevertError) Signature() (string, error) { + if r == nil || r.ErrorEntry == nil { + return "", nil + } + return r.ErrorEntry.SignatureCtx(context.Background()) +} + +// SerializeJSON serializes the decoded error data at this level using +// the provided Serializer. This is most useful on the Innermost() error +// where the data is cleanly structured. +func (r *RevertError) SerializeJSON(ctx context.Context, s *Serializer) ([]byte, error) { + if r == nil || r.cv == nil { + return nil, nil + } + if s == nil { + s = NewSerializer() + } + return s.SerializeJSONCtx(ctx, r.cv) +} + +// Cause returns the next error in the chain (one level deeper), or nil +// at the leaf. Analogous to Java's Throwable.getCause(). +func (r *RevertError) Cause() *RevertError { + if r == nil { + return nil + } + return r.Nested +} + +// Innermost walks the chain to return the deepest RevertError — the +// original error that triggered the chain of catch-and-rethrow wrappers. +func (r *RevertError) Innermost() *RevertError { + if r == nil { + return nil + } + cur := r + for cur.Nested != nil { + cur = cur.Nested + } + return cur +} + +// Errors returns a flattened slice of all RevertError entries in the chain, +// from outermost to innermost. +func (r *RevertError) Errors() []*RevertError { + if r == nil { + return nil + } + var result []*RevertError + for cur := r; cur != nil; cur = cur.Nested { + result = append(result, cur) + } + return result +} diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go new file mode 100644 index 00000000..6307ce57 --- /dev/null +++ b/pkg/abi/reverterror_test.go @@ -0,0 +1,328 @@ +// Copyright © 2026 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package abi + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// decodeTestError builds a properly typed ComponentValue by encoding and then +// decoding through the ABI pipeline. +func decodeTestError(t *testing.T, entry *Entry, jsonArgs string) *ComponentValue { + t.Helper() + encoded, err := entry.EncodeCallDataJSON([]byte(jsonArgs)) + require.NoError(t, err) + cv, err := entry.DecodeCallDataCtx(context.Background(), encoded) + require.NoError(t, err) + return cv +} + +// --- Nil receiver safety --- + +func TestRevertErrorNilString(t *testing.T) { + var r *RevertError + assert.Equal(t, "", r.String()) +} + +func TestRevertErrorNilSignature(t *testing.T) { + var r *RevertError + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "", sig) +} + +func TestRevertErrorNilSerializeJSON(t *testing.T) { + var r *RevertError + b, err := r.SerializeJSON(context.Background(), nil) + assert.NoError(t, err) + assert.Nil(t, b) +} + +func TestRevertErrorNilCause(t *testing.T) { + var r *RevertError + assert.Nil(t, r.Cause()) +} + +func TestRevertErrorNilInnermost(t *testing.T) { + var r *RevertError + assert.Nil(t, r.Innermost()) +} + +func TestRevertErrorNilErrors(t *testing.T) { + var r *RevertError + assert.Nil(t, r.Errors()) +} + +// --- Single (non-nested) error --- + +func TestRevertErrorSingleString(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + assert.Equal(t, `AnError("something went wrong")`, r.String()) +} + +func TestRevertErrorSingleSignature(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "AnError(string)", sig) +} + +func TestRevertErrorSingleSerializeJSON(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + b, err := r.SerializeJSON(context.Background(), nil) + assert.NoError(t, err) + assert.Contains(t, string(b), "something went wrong") +} + +func TestRevertErrorSingleSerializeJSONNilCV(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + r := &RevertError{ErrorEntry: entry} + b, err := r.SerializeJSON(context.Background(), nil) + assert.NoError(t, err) + assert.Nil(t, b) +} + +func TestRevertErrorSingleCause(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + assert.Nil(t, r.Cause()) +} + +func TestRevertErrorSingleInnermost(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + assert.Equal(t, r, r.Innermost()) +} + +func TestRevertErrorSingleErrors(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + errs := r.Errors() + require.Len(t, errs, 1) + assert.Equal(t, r, errs[0]) +} + +func TestRevertErrorSingleWithPrefix(t *testing.T) { + entry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + cv := decodeTestError(t, entry, `{"reason":"plain error"}`) + r := &RevertError{ErrorEntry: entry, cv: cv, Prefix: "context: "} + assert.Equal(t, `context: Error("plain error")`, r.String()) +} + +func TestRevertErrorMultipleParams(t *testing.T) { + entry := &Entry{Type: Error, Name: "ExampleError", Inputs: ParameterArray{ + {Name: "param1", Type: "string"}, + {Name: "param2", Type: "uint256"}, + }} + cv := decodeTestError(t, entry, `{"param1":"test1","param2":12345}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + assert.Equal(t, `ExampleError("test1","12345")`, r.String()) +} + +// --- Two-level nested error --- + +func TestRevertErrorNestedTwoLevelString(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outerCV := decodeTestError(t, outerEntry, `{"reason":"raw outer value"}`) + outer := &RevertError{ + ErrorEntry: outerEntry, + cv: outerCV, + Prefix: "[404]caught bytes", + Nested: inner, + } + assert.Equal(t, `[404]caught bytesAnError("I am an error")`, outer.String()) +} + +func TestRevertErrorNestedTwoLevelSignatures(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outer := &RevertError{ + ErrorEntry: outerEntry, + Prefix: "[404]caught bytes", + Nested: inner, + } + + outerSig, err := outer.Signature() + assert.NoError(t, err) + assert.Equal(t, "Error(string)", outerSig) + + innerSig, err := inner.Signature() + assert.NoError(t, err) + assert.Equal(t, "AnError(string)", innerSig) +} + +func TestRevertErrorNestedTwoLevelCause(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Nested: inner} + + assert.Equal(t, inner, outer.Cause()) + assert.Nil(t, inner.Cause()) +} + +func TestRevertErrorNestedTwoLevelInnermost(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Nested: inner} + + assert.Equal(t, inner, outer.Innermost()) + assert.Equal(t, inner, inner.Innermost()) +} + +func TestRevertErrorNestedTwoLevelErrors(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Nested: inner} + + errs := outer.Errors() + require.Len(t, errs, 2) + assert.Equal(t, outer, errs[0]) + assert.Equal(t, inner, errs[1]) +} + +func TestRevertErrorNestedTwoLevelSerializeJSONInnermost(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outerCV := decodeTestError(t, outerEntry, `{"reason":"raw bytes here"}`) + outer := &RevertError{ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", Nested: inner} + + b, err := outer.Innermost().SerializeJSON(context.Background(), nil) + assert.NoError(t, err) + assert.Contains(t, string(b), "I am an error") +} + +// --- Three-level nested error --- + +func TestRevertErrorNestedThreeLevelString(t *testing.T) { + leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} + leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) + leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} + + middleEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + middle := &RevertError{ErrorEntry: middleEntry, Prefix: "middleware: ", Nested: leaf} + + outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "gateway: ", Nested: middle} + + assert.Equal(t, `gateway: middleware: RootCause("the real problem")`, outer.String()) +} + +func TestRevertErrorNestedThreeLevelInnermost(t *testing.T) { + leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} + leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) + leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} + + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Nested: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Nested: middle} + + assert.Equal(t, leaf, outer.Innermost()) +} + +func TestRevertErrorNestedThreeLevelErrors(t *testing.T) { + leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} + leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) + leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} + + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Nested: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Nested: middle} + + errs := outer.Errors() + require.Len(t, errs, 3) + assert.Equal(t, outer, errs[0]) + assert.Equal(t, middle, errs[1]) + assert.Equal(t, leaf, errs[2]) +} + +func TestRevertErrorNestedThreeLevelCauseChain(t *testing.T) { + leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} + leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) + leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} + + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Nested: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Nested: middle} + + assert.Equal(t, middle, outer.Cause()) + assert.Equal(t, leaf, outer.Cause().Cause()) + assert.Nil(t, outer.Cause().Cause().Cause()) +} + +// --- SerializeJSON with custom serializer --- + +func TestRevertErrorSerializeJSONCustomSerializer(t *testing.T) { + entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + cv := decodeTestError(t, entry, `{"message":"test value"}`) + r := &RevertError{ErrorEntry: entry, cv: cv} + s := NewSerializer().SetPretty(true) + b, err := r.SerializeJSON(context.Background(), s) + assert.NoError(t, err) + assert.Contains(t, string(b), "\n") + assert.Contains(t, string(b), "test value") +} + +// --- Edge cases --- + +func TestRevertErrorEmptyPrefix(t *testing.T) { + innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} + innerCV := decodeTestError(t, innerEntry, `{"message":"direct"}`) + inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} + + outer := &RevertError{ + ErrorEntry: &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, + Prefix: "", + Nested: inner, + } + assert.Equal(t, `AnError("direct")`, outer.String()) +} + +func TestRevertErrorSignatureNilEntry(t *testing.T) { + r := &RevertError{} + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "", sig) +} From 3cd770101c6a34bc4e09321239ec7ed0471e37ab Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 14 Apr 2026 12:36:10 +0100 Subject: [PATCH 06/22] Add parsing methods for simple (non nested cases) Signed-off-by: Dave Crighton --- pkg/abi/reverterror.go | 37 +++++++++++ pkg/abi/reverterror_test.go | 118 ++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 05f6f77e..a42cf91f 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -21,6 +21,13 @@ import ( "strings" ) +// defaultErrorEntries are the built-in Solidity error types that are always +// tried when decoding revert data, even if the caller's ABI is empty. +var defaultErrorEntries = ABI{ + {Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, + {Type: Error, Name: "Panic", Inputs: ParameterArray{{Name: "code", Type: "uint256"}}}, +} + // RevertError represents a decoded Solidity revert error. For nested errors // (where a contract catches a revert and re-throws with the original error // embedded in the string), the Nested field links to the inner decoded error, @@ -32,6 +39,36 @@ type RevertError struct { Nested *RevertError // recursively decoded inner error, nil if none } +// DecodeRevertError decodes raw EVM revert data into a RevertError. +// Returns nil if the data does not match any known error selector. +func (a ABI) DecodeRevertError(revertData []byte) *RevertError { + return a.DecodeRevertErrorCtx(context.Background(), revertData) +} + +// DecodeRevertErrorCtx decodes raw EVM revert data into a RevertError. +// The ABI's error entries are tried first, followed by the built-in +// Error(string) and Panic(uint256). Returns nil if no selector matches. +func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *RevertError { + candidates := append(a.errors(), defaultErrorEntries...) + for _, e := range candidates { + if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { + return &RevertError{ErrorEntry: e, cv: cv} + } + } + return nil +} + +// errors returns only the Error-type entries from the ABI. +func (a ABI) errors() ABI { + var out ABI + for _, e := range a { + if e.Type == Error { + out = append(out, e) + } + } + return out +} + // String returns a human-readable representation of the full error chain. // It concatenates the Prefix at each level, with the leaf error formatted // as ErrorName(arg1,arg2,...). diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index 6307ce57..af20e4a8 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -326,3 +326,121 @@ func TestRevertErrorSignatureNilEntry(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "", sig) } + +// --- DecodeRevertError / DecodeRevertErrorCtx --- + +func TestDecodeRevertErrorDefaultErrorString(t *testing.T) { + revertData := testEncodeError(t, + &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, + `{"reason":"Not enough Ether provided."}`, + ) + r := ABI{}.DecodeRevertError(revertData) + require.NotNil(t, r) + assert.Equal(t, "Error", r.ErrorEntry.Name) + assert.Equal(t, `Error("Not enough Ether provided.")`, r.String()) + assert.Nil(t, r.Cause()) +} + +func TestDecodeRevertErrorDefaultPanic(t *testing.T) { + revertData := testEncodeError(t, + &Entry{Type: Error, Name: "Panic", Inputs: ParameterArray{{Name: "code", Type: "uint256"}}}, + `{"code":1}`, + ) + r := ABI{}.DecodeRevertError(revertData) + require.NotNil(t, r) + assert.Equal(t, "Panic", r.ErrorEntry.Name) + assert.Equal(t, `Panic("1")`, r.String()) + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "Panic(uint256)", sig) +} + +func TestDecodeRevertErrorCustomError(t *testing.T) { + customEntry := &Entry{Type: Error, Name: "InsufficientBalance", Inputs: ParameterArray{ + {Name: "available", Type: "uint256"}, + {Name: "required", Type: "uint256"}, + }} + customABI := ABI{customEntry} + revertData := testEncodeError(t, customEntry, `{"available":100,"required":200}`) + + r := customABI.DecodeRevertError(revertData) + require.NotNil(t, r) + assert.Equal(t, "InsufficientBalance", r.ErrorEntry.Name) + assert.Equal(t, `InsufficientBalance("100","200")`, r.String()) + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "InsufficientBalance(uint256,uint256)", sig) +} + +func TestDecodeRevertErrorCustomBeforeBuiltin(t *testing.T) { + customEntry := &Entry{Type: Error, Name: "MyError", Inputs: ParameterArray{{Name: "msg", Type: "string"}}} + customABI := ABI{customEntry} + revertData := testEncodeError(t, customEntry, `{"msg":"custom message"}`) + + r := customABI.DecodeRevertError(revertData) + require.NotNil(t, r) + assert.Equal(t, "MyError", r.ErrorEntry.Name, "custom ABI entries should be tried before builtins") +} + +func TestDecodeRevertErrorNoMatch(t *testing.T) { + r := ABI{}.DecodeRevertError([]byte{0x11, 0x22, 0x33, 0x44}) + assert.Nil(t, r) +} + +func TestDecodeRevertErrorTooShort(t *testing.T) { + r := ABI{}.DecodeRevertError([]byte{0x08}) + assert.Nil(t, r) +} + +func TestDecodeRevertErrorNilData(t *testing.T) { + r := ABI{}.DecodeRevertError(nil) + assert.Nil(t, r) +} + +func TestDecodeRevertErrorEmptyData(t *testing.T) { + r := ABI{}.DecodeRevertError([]byte{}) + assert.Nil(t, r) +} + +func TestDecodeRevertErrorCtxPassesContext(t *testing.T) { + revertData := testEncodeError(t, + &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, + `{"reason":"with context"}`, + ) + ctx := context.Background() + r := ABI{}.DecodeRevertErrorCtx(ctx, revertData) + require.NotNil(t, r) + assert.Equal(t, `Error("with context")`, r.String()) +} + +func TestDecodeRevertErrorSerializeJSON(t *testing.T) { + customEntry := &Entry{Type: Error, Name: "ExampleError", Inputs: ParameterArray{ + {Name: "param1", Type: "string"}, + {Name: "param2", Type: "uint256"}, + }} + revertData := testEncodeError(t, customEntry, `{"param1":"test1","param2":12345}`) + r := ABI{customEntry}.DecodeRevertError(revertData) + require.NotNil(t, r) + + b, err := r.SerializeJSON(context.Background(), nil) + assert.NoError(t, err) + assert.Contains(t, string(b), "test1") + assert.Contains(t, string(b), "12345") +} + +func TestDecodeRevertErrorNonErrorEntriesIgnored(t *testing.T) { + fnEntry := &Entry{Type: Function, Name: "transfer", Inputs: ParameterArray{ + {Name: "to", Type: "address"}, + {Name: "amount", Type: "uint256"}, + }} + r := ABI{fnEntry}.DecodeRevertError([]byte{0x11, 0x22, 0x33, 0x44}) + assert.Nil(t, r, "function entries should not be tried for error decoding") +} + +// testEncodeError is a helper that ABI-encodes error data for a given entry and JSON args. +func testEncodeError(t *testing.T, entry *Entry, jsonArgs string) []byte { + t.Helper() + encoded, err := entry.EncodeCallDataJSON([]byte(jsonArgs)) + require.NoError(t, err) + return encoded +} From 84f3ed03039100e05d939a9fa783fb26f8cb4740 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 14 Apr 2026 12:56:20 +0100 Subject: [PATCH 07/22] Add unwrapping functionality for revert errors Signed-off-by: Dave Crighton --- pkg/abi/reverterror.go | 96 ++++++++++++++++++++-- pkg/abi/reverterror_test.go | 156 ++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+), 6 deletions(-) diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index a42cf91f..75126495 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -19,6 +19,8 @@ package abi import ( "context" "strings" + + "github.com/hyperledger/firefly-common/pkg/log" ) // defaultErrorEntries are the built-in Solidity error types that are always @@ -28,6 +30,8 @@ var defaultErrorEntries = ABI{ {Type: Error, Name: "Panic", Inputs: ParameterArray{{Name: "code", Type: "uint256"}}}, } +const maxRevertErrorDepth = 10 + // RevertError represents a decoded Solidity revert error. For nested errors // (where a contract catches a revert and re-throws with the original error // embedded in the string), the Nested field links to the inner decoded error, @@ -39,25 +43,105 @@ type RevertError struct { Nested *RevertError // recursively decoded inner error, nil if none } -// DecodeRevertError decodes raw EVM revert data into a RevertError. +// DecodeRevertError decodes raw EVM revert data into a RevertError, +// recursively unwrapping nested errors embedded in Error(string) values. // Returns nil if the data does not match any known error selector. func (a ABI) DecodeRevertError(revertData []byte) *RevertError { return a.DecodeRevertErrorCtx(context.Background(), revertData) } -// DecodeRevertErrorCtx decodes raw EVM revert data into a RevertError. +// DecodeRevertErrorCtx decodes raw EVM revert data into a RevertError, +// recursively unwrapping nested errors embedded in Error(string) values. // The ABI's error entries are tried first, followed by the built-in // Error(string) and Panic(uint256). Returns nil if no selector matches. func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *RevertError { - candidates := append(a.errors(), defaultErrorEntries...) - for _, e := range candidates { - if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { - return &RevertError{ErrorEntry: e, cv: cv} + for _, source := range []ABI{a.errors(), defaultErrorEntries} { + for _, e := range source { + if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { + r := &RevertError{ErrorEntry: e, cv: cv} + // Only Error(string) is unwrapped for nesting, because the Solidity + // catch-and-rethrow pattern (string.concat + string(reason)) always + // produces Error(string). Custom errors with string/bytes params that + // also embed error data are not yet handled here. + if e.Name == "Error" && len(cv.Children) == 1 { + if strVal, ok := cv.Children[0].Value.(string); ok { + r.unwrapNested(ctx, a.selectorMap(), strVal, 0) + } + } + return r + } } } return nil } +type selectorKey = [4]byte + +// selectorMap builds a lookup of 4-byte selectors for all error entries in +// the ABI plus the builtins. +func (a ABI) selectorMap() map[selectorKey]*Entry { + selectors := make(map[selectorKey]*Entry) + var key selectorKey + for _, source := range []ABI{a.errors(), defaultErrorEntries} { + for _, e := range source { + sel := e.FunctionSelectorBytes() + if len(sel) >= 4 { + copy(key[:], sel[:4]) + if _, exists := selectors[key]; !exists { + selectors[key] = e + } + } + } + } + return selectors +} + +// unwrapNested scans a decoded string value for an embedded ABI error selector. +// If found, it populates r.Prefix and r.Nested to form the recursive chain. +func (r *RevertError) unwrapNested(ctx context.Context, selectors map[selectorKey]*Entry, s string, depth int) { + if depth >= maxRevertErrorDepth { + return + } + + raw := []byte(s) + idx, entry := findSelector(raw, selectors) + if idx < 0 { + return + } + + cv, err := entry.DecodeCallDataCtx(ctx, raw[idx:]) + if err != nil { + log.L(ctx).Debugf("Could not decode nested revert at depth %d: %s", depth, err) + return + } + + nested := &RevertError{ErrorEntry: entry, cv: cv} + r.Prefix = SanitizeBinaryString(raw[:idx]) + r.Nested = nested + + // If the nested error is also Error(string), keep unwrapping + if entry.Name == "Error" && len(cv.Children) == 1 { + if strVal, ok := cv.Children[0].Value.(string); ok { + nested.unwrapNested(ctx, selectors, strVal, depth+1) + } + } +} + +// findSelector scans raw bytes for the first occurrence of a known 4-byte error selector. +func findSelector(raw []byte, selectors map[selectorKey]*Entry) (int, *Entry) { + if len(raw) < 4 { + return -1, nil + } + var key selectorKey + for i := 0; i <= len(raw)-4; i++ { + copy(key[:], raw[i:i+4]) + if e, ok := selectors[key]; ok { + return i, e + } + } + return -1, nil +} + // errors returns only the Error-type entries from the ABI. func (a ABI) errors() ABI { var out ABI diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index af20e4a8..baa937b1 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -444,3 +444,159 @@ func testEncodeError(t *testing.T, entry *Entry, jsonArgs string) []byte { require.NoError(t, err) return encoded } + +// --- Nested unwrapping (DecodeRevertError with nesting) --- + +func TestDecodeRevertErrorSingleNested(t *testing.T) { + innerABI := buildErrorStringABI([]byte("inner error message")) + outerABI := buildErrorStringABI(append([]byte("outer: "), innerABI...)) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Equal(t, "Error", r.ErrorEntry.Name) + assert.Equal(t, "outer: ", r.Prefix) + + require.NotNil(t, r.Cause()) + assert.Equal(t, "Error", r.Cause().ErrorEntry.Name) + assert.Nil(t, r.Cause().Cause()) + + assert.Equal(t, `outer: Error("inner error message")`, r.String()) +} + +func TestDecodeRevertErrorDoubleNested(t *testing.T) { + deepABI := buildErrorStringABI([]byte("deepest error")) + midABI := buildErrorStringABI(append([]byte("level2: "), deepABI...)) + outerABI := buildErrorStringABI(append([]byte("level1: "), midABI...)) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Equal(t, "level1: ", r.Prefix) + + mid := r.Cause() + require.NotNil(t, mid) + assert.Equal(t, "level2: ", mid.Prefix) + + leaf := mid.Cause() + require.NotNil(t, leaf) + assert.Nil(t, leaf.Cause()) + + assert.Equal(t, `level1: level2: Error("deepest error")`, r.String()) + assert.Equal(t, leaf, r.Innermost()) + + errs := r.Errors() + require.Len(t, errs, 3) +} + +func TestDecodeRevertErrorNestedCustomError(t *testing.T) { + customEntry := &Entry{Type: Error, Name: "MyCustomError", Inputs: ParameterArray{{Type: "bytes"}}} + customEncoded := testEncodeError(t, customEntry, `{"0":"0xdeadbeef"}`) + + outerABI := buildErrorStringABI(append([]byte("[404]01d - caught bytes:"), customEncoded...)) + + r := ABI{customEntry}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Equal(t, "Error", r.ErrorEntry.Name) + assert.Equal(t, "[404]01d - caught bytes:", r.Prefix) + + inner := r.Cause() + require.NotNil(t, inner) + assert.Equal(t, "MyCustomError", inner.ErrorEntry.Name) + assert.Nil(t, inner.Cause()) + + sig, err := inner.Signature() + assert.NoError(t, err) + assert.Equal(t, "MyCustomError(bytes)", sig) + + assert.Equal(t, `[404]01d - caught bytes:MyCustomError("0xdeadbeef")`, r.String()) +} + +func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { + customEntry := &Entry{Type: Error, Name: "EarlyErr", Inputs: ParameterArray{{Type: "uint256"}}} + customEncoded := testEncodeError(t, customEntry, `{"0":42}`) + + innerErrorABI := buildErrorStringABI([]byte("late-error")) + // Custom selector appears before the Error(string) selector + payload := append([]byte("head:"), customEncoded...) + payload = append(payload, []byte("middle:")...) + payload = append(payload, innerErrorABI...) + outerABI := buildErrorStringABI(payload) + + r := ABI{customEntry}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Equal(t, "head:", r.Prefix) + + inner := r.Cause() + require.NotNil(t, inner) + assert.Equal(t, "EarlyErr", inner.ErrorEntry.Name, "first matching selector wins") +} + +func TestDecodeRevertErrorNoNesting(t *testing.T) { + outerABI := buildErrorStringABI([]byte("plain error with no nesting")) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Equal(t, "", r.Prefix) + assert.Nil(t, r.Cause()) + assert.Equal(t, `Error("plain error with no nesting")`, r.String()) +} + +func TestDecodeRevertErrorMalformedNested(t *testing.T) { + defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + sel := defaultErr.FunctionSelectorBytes() + + badData := append([]byte("prefix:"), sel...) + badData = append(badData, []byte("truncated")...) + outerABI := buildErrorStringABI(badData) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Nil(t, r.Cause(), "malformed nested data should not produce a nested error") + assert.Equal(t, "", r.Prefix) +} + +func TestDecodeRevertErrorDepthLimit(t *testing.T) { + // Build a chain deeper than maxRevertErrorDepth (10) + data := []byte("leaf") + for i := 0; i < maxRevertErrorDepth+2; i++ { + data = buildErrorStringABI(append([]byte("L:"), data...)) + } + + r := ABI{}.DecodeRevertError(data) + require.NotNil(t, r) + + depth := 0 + for cur := r; cur != nil; cur = cur.Cause() { + depth++ + } + assert.LessOrEqual(t, depth, maxRevertErrorDepth+1, "chain should be capped by depth limit") +} + +func TestDecodeRevertErrorInnermostSerializeJSON(t *testing.T) { + innerABI := buildErrorStringABI([]byte("inner value")) + outerABI := buildErrorStringABI(append([]byte("prefix:"), innerABI...)) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + + leaf := r.Innermost() + require.NotNil(t, leaf) + b, err := leaf.SerializeJSON(context.Background(), nil) + assert.NoError(t, err) + assert.Contains(t, string(b), "inner value") +} + +func TestDecodeRevertErrorNestedSignatures(t *testing.T) { + innerABI := buildErrorStringABI([]byte("inner")) + outerABI := buildErrorStringABI(append([]byte("prefix:"), innerABI...)) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + + outerSig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "Error(string)", outerSig) + + innerSig, err := r.Cause().Signature() + assert.NoError(t, err) + assert.Equal(t, "Error(string)", innerSig) +} From 63dfb287996b290498ccb7c7b182e4b230e41da5 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 14 Apr 2026 13:07:29 +0100 Subject: [PATCH 08/22] Move unwrap error to use new method Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 105 +++++--------------------------------------- pkg/abi/abi_test.go | 39 ++++++++-------- 2 files changed, 31 insertions(+), 113 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 8a0c712b..8d449896 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -320,14 +320,12 @@ func (a ABI) ParseError(revertData []byte) (*Entry, *ComponentValue, bool) { return a.ParseErrorCtx(context.Background(), revertData) } -// Returns the components value from the parsed error +// ParseErrorCtx returns the matched Entry and decoded ComponentValue from the +// given revert data. The ABI's error entries are tried first, followed by the +// built-in Error(string) and Panic(uint256). func (a ABI) ParseErrorCtx(ctx context.Context, revertData []byte) (*Entry, *ComponentValue, bool) { - // Always include the default error - a = append(ABI{ - {Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, - }, a...) - for _, e := range a { - if e.Type == Error { + for _, source := range []ABI{a.errors(), defaultErrorEntries} { + for _, e := range source { if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { return e, cv, true } @@ -349,28 +347,17 @@ func (a ABI) ErrorStringCtx(ctx context.Context, revertData []byte) (strError st return strError, ok } -const maxNestedRevertDepth = 10 - // UnwrapErrorStringCtx is like ErrorStringCtx but handles nested errors caused by -// Solidity contracts that catch a revert and re-throw using string(reason). This -// embeds raw ABI-encoded error data (including null bytes from ABI padding) inside -// the new Error(string). The function scans for all known error selectors, -// recursively decodes nested Error(string) chains, and formats known custom errors. -// Any undecoded binary data is hex-encoded. +// Solidity contracts that catch a revert and re-throw using string(reason). +// Delegates to DecodeRevertErrorCtx for structured decoding, then formats the +// result as a human-readable string. func (a ABI) UnwrapErrorStringCtx(ctx context.Context, revertData []byte) (string, bool) { - e, cv, ok := a.ParseErrorCtx(ctx, revertData) - if !ok { + r := a.DecodeRevertErrorCtx(ctx, revertData) + if r == nil { return "", false } - // For Error(string), unwrap any nested errors inside the decoded string - if e.Name == "Error" && len(cv.Children) == 1 { - if strVal, ok := cv.Children[0].Value.(string); ok { - return unwrapNestedRevertReasons(ctx, a, strVal, 0), true - } - } - // For other error types, format directly - strError := FormatErrorStringCtx(ctx, e, cv) - return strError, strError != "" + s := r.String() + return s, s != "" } // UnwrapErrorString is a convenience wrapper for UnwrapErrorStringCtx. @@ -378,74 +365,6 @@ func (a ABI) UnwrapErrorString(revertData []byte) (string, bool) { return a.UnwrapErrorStringCtx(context.Background(), revertData) } -// unwrapNestedRevertReasons recursively decodes Error(string) values that contain -// embedded ABI-encoded error data from Solidity catch-and-rethrow patterns. -func unwrapNestedRevertReasons(ctx context.Context, a ABI, s string, depth int) string { - if depth >= maxNestedRevertDepth { - return SanitizeBinaryString([]byte(s)) - } - - raw := []byte(s) - - // Build a lookup map of 4-byte selectors so we can scan the string once - type selectorKey = [4]byte - selectors := make(map[selectorKey]*Entry) - errorsWithDefault := append(ABI{ - {Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, - }, a...) - for _, e := range errorsWithDefault { - if e.Type != Error { - continue - } - sel := e.FunctionSelectorBytes() - if len(sel) >= 4 { - var key selectorKey - copy(key[:], sel[:4]) - if _, exists := selectors[key]; !exists { - selectors[key] = e - } - } - } - - // Single pass: walk through the bytes looking for any known selector - bestIdx := -1 - var bestEntry *Entry - if len(raw) >= 4 { - for i := 0; i <= len(raw)-4; i++ { - var key selectorKey - copy(key[:], raw[i:i+4]) - if e, ok := selectors[key]; ok { - bestIdx = i - bestEntry = e - break - } - } - } - - if bestIdx < 0 { - return SanitizeBinaryString(raw) - } - - prefix := SanitizeBinaryString(raw[:bestIdx]) - embedded := raw[bestIdx:] - - cv, err := bestEntry.DecodeCallDataCtx(ctx, embedded) - if err == nil { - if bestEntry.Name == "Error" && len(cv.Children) == 1 { - if nested, ok := cv.Children[0].Value.(string); ok { - return prefix + unwrapNestedRevertReasons(ctx, a, nested, depth+1) - } - } - formatted := FormatErrorStringCtx(ctx, bestEntry, cv) - if formatted != "" { - return prefix + formatted - } - } - - log.L(ctx).Debugf("Could not decode nested revert at depth %d, hex-encoding remaining %d bytes", depth, len(embedded)) - return prefix + "0x" + hex.EncodeToString(embedded) -} - // SanitizeBinaryString returns the input as a text string if it is entirely // printable ASCII, or hex-encodes the entire input otherwise. This ensures the // output is always safe for database TEXT columns and human-readable logging. diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index aa525a64..e9850ae7 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -1104,7 +1104,7 @@ func TestUnwrapErrorStringPlainError(t *testing.T) { result, ok := ABI{}.UnwrapErrorString(revertData) assert.True(t, ok) - assert.Equal(t, "Not enough Ether provided.", result) + assert.Equal(t, `Error("Not enough Ether provided.")`, result) } func TestUnwrapErrorStringSingleNested(t *testing.T) { @@ -1120,7 +1120,7 @@ func TestUnwrapErrorStringSingleNested(t *testing.T) { result, ok := ABI{}.UnwrapErrorString(revertData) assert.True(t, ok) - assert.Equal(t, "outer: inner error message", result) + assert.Equal(t, `outer: Error("inner error message")`, result) } func TestUnwrapErrorStringDoubleNested(t *testing.T) { @@ -1140,7 +1140,7 @@ func TestUnwrapErrorStringDoubleNested(t *testing.T) { result, ok := ABI{}.UnwrapErrorString(revertData) assert.True(t, ok) - assert.Equal(t, "level1: level2: deepest error", result) + assert.Equal(t, `level1: level2: Error("deepest error")`, result) } func TestUnwrapErrorStringNestedCustomError(t *testing.T) { @@ -1160,10 +1160,11 @@ func TestUnwrapErrorStringNestedCustomError(t *testing.T) { "deadbeef00000000000000000000000000000000000000000000000000000000" + "00000000") - // Without the custom ABI, the nested section can't be decoded + // Without the custom ABI, the nested section can't be decoded — the + // outer Error(string) is formatted directly (binary content included) result, ok := ABI{}.UnwrapErrorString(revertData) assert.True(t, ok) - assert.True(t, strings.HasPrefix(result, "0x")) + assert.True(t, strings.HasPrefix(result, `Error("[404]01d`)) // With the custom ABI, the nested error is decoded result, ok = customABI.UnwrapErrorString(revertData) @@ -1184,28 +1185,26 @@ func TestUnwrapErrorStringMalformedNestedABI(t *testing.T) { badData := "prefix:" + string(sel) + "truncated" outerABI := buildErrorStringABI([]byte(badData)) + // Malformed nested data can't be decoded, so the outer Error(string) + // is formatted directly with the raw string content result, ok := ABI{}.UnwrapErrorString(outerABI) assert.True(t, ok) - assert.Equal(t, "prefix:0x08c379a07472756e6361746564", result) + assert.True(t, strings.HasPrefix(result, "Error(")) } func TestUnwrapErrorStringDepthLimit(t *testing.T) { - innerABI := buildErrorStringABI([]byte("should not decode")) - s := "prefix:" + string(innerABI) - outerABI := buildErrorStringABI([]byte(s)) - - // Passes through processRevertReason, then into unwrap at depth 0. - // We can't easily test depth=10 through the public API without 10 levels of nesting, - // so test the internal function directly. - result := unwrapNestedRevertReasons(nil, ABI{}, s, maxNestedRevertDepth) - assert.True(t, strings.HasPrefix(result, "0x")) - assert.NotEqual(t, "prefix:should not decode", result) + // Build a chain deeper than maxRevertErrorDepth (10) + data := []byte("leaf") + for i := 0; i < maxRevertErrorDepth+2; i++ { + data = buildErrorStringABI(append([]byte("L:"), data...)) + } - // At depth max-1, it still decodes - result = unwrapNestedRevertReasons(nil, ABI{}, s, maxNestedRevertDepth-1) - assert.Equal(t, "prefix:should not decode", result) + result, ok := ABI{}.UnwrapErrorString(data) + assert.True(t, ok) - _ = outerABI // suppress unused + // The chain should be capped — the leaf should not be fully unwrapped + // through all levels, so the result won't contain a cleanly decoded "leaf" + assert.NotEmpty(t, result) } func TestUnwrapErrorStringCustomBeforeDefaultError(t *testing.T) { From d6fcfb97b36f7f48b2e63713c1bb53320b2c578c Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 14 Apr 2026 14:12:20 +0100 Subject: [PATCH 09/22] Add method for getting error string without unwrapping for backwards compatibility Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 3 +++ pkg/abi/reverterror.go | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 8d449896..6fde0986 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -378,6 +378,9 @@ func SanitizeBinaryString(raw []byte) string { } func FormatErrorStringCtx(ctx context.Context, e *Entry, cv *ComponentValue) string { + if e == nil || cv == nil { + return "" + } var ok bool var parsed []interface{} if res, err := NewSerializer(). diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 75126495..19f3de74 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -170,6 +170,17 @@ func (r *RevertError) String() string { return b.String() } +// ErrorString returns the formatted error at this level only, e.g. +// Error("not enough funds") or MyCustomError("0x1234","-100"). +// Unlike String(), it does not walk the Nested chain — use it when +// you need the single-level description without recursive unwrapping. +func (r *RevertError) ErrorString() string { + if r == nil { + return "" + } + return FormatErrorStringCtx(context.Background(), r.ErrorEntry, r.cv) +} + // Signature returns the ABI signature of the error at this level, // e.g. "Error(string)" or "AnError(string,uint256)". func (r *RevertError) Signature() (string, error) { From ae0074e0f817e9a0f3afddd1dc636b655139f3a9 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Wed, 15 Apr 2026 13:02:46 +0100 Subject: [PATCH 10/22] go mod updates Signed-off-by: Dave Crighton --- go.mod | 91 ++++++++++++------------- go.sum | 204 ++++++++++++++------------------------------------------- 2 files changed, 96 insertions(+), 199 deletions(-) diff --git a/go.mod b/go.mod index 404b5393..6a33ffa3 100644 --- a/go.mod +++ b/go.mod @@ -1,80 +1,83 @@ module github.com/hyperledger/firefly-signer -go 1.23.0 +go 1.24.0 + +toolchain go1.24.13 require ( github.com/btcsuite/btcd/btcec/v2 v2.3.5 - github.com/fsnotify/fsnotify v1.7.0 - github.com/go-resty/resty/v2 v2.11.0 + github.com/fsnotify/fsnotify v1.9.0 + github.com/go-resty/resty/v2 v2.17.1 github.com/gorilla/mux v1.8.1 - github.com/hyperledger/firefly-common v1.5.5 + github.com/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763 github.com/karlseguin/ccache v2.0.3+incompatible github.com/pelletier/go-toml v1.9.5 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.0 - github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.36.0 - golang.org/x/text v0.23.0 + github.com/sirupsen/logrus v1.9.4 + github.com/spf13/cobra v1.10.2 + github.com/spf13/viper v1.21.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/crypto v0.47.0 + golang.org/x/text v0.33.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/aidarkhanov/nanoid v1.0.8 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/getkin/kin-openapi v0.131.0 // indirect + github.com/getkin/kin-openapi v0.133.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/gorilla/websocket v1.5.1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/nxadm/tail v1.4.8 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nxadm/tail v1.4.11 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/onsi/gomega v1.35.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + 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.19.2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect gitlab.com/hfuss/mux-prometheus v0.0.5 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 382a43b0..14b274ef 100644 --- a/go.sum +++ b/go.sum @@ -4,50 +4,33 @@ 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/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= -github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= -github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hyperledger/firefly-common v1.5.5 h1:UaANgBIT0aBvAk0Yt+Qrn6qXxpwNIrFfwnW3EBivrQs= -github.com/hyperledger/firefly-common v1.5.5/go.mod h1:1Xawm5PUhxT7k+CL/Kr3i1LE3cTTzoQwZMLimvlW8rs= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763 h1:hcVVxUu7zWJld2TL8prVyZ4mxae4wt0eRM1dnzIwzhg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= @@ -58,179 +41,90 @@ github.com/karlseguin/ccache v2.0.3+incompatible h1:j68C9tWOROiOLWTS/kCGg9IcJG+A github.com/karlseguin/ccache v2.0.3+incompatible/go.mod h1:CM9tNPzT6EdRh14+jiW8mEF9mkNZuuE51qmgGYUB93w= github.com/karlseguin/expect v1.0.8 h1:Bb0H6IgBWQpadY25UDNkYPDB9ITqK1xnSoZfAq362fw= github.com/karlseguin/expect v1.0.8/go.mod h1:lXdI8iGiQhmzpnnmU/EGA60vqKs8NbRNFnhhrJGoD5g= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 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.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -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/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/hfuss/mux-prometheus v0.0.5 h1:Kcqyiekx8W2dO1EHg+6wOL1F0cFNgRO1uCK18V31D0s= gitlab.com/hfuss/mux-prometheus v0.0.5/go.mod h1:xcedy8rVGr9TFgRu2urfGuh99B4NdfYdpE4aUMQ0dxA= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.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/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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= -gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= From fae3fbe5d20c049305a2a190258262e382336992 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Thu, 16 Apr 2026 10:49:16 +0100 Subject: [PATCH 11/22] Rename Nested to Cause to better encapsulate meaning Signed-off-by: Dave Crighton --- pkg/abi/reverterror.go | 28 ++++++------- pkg/abi/reverterror_test.go | 78 ++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 19f3de74..5c4867e4 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -34,13 +34,13 @@ const maxRevertErrorDepth = 10 // RevertError represents a decoded Solidity revert error. For nested errors // (where a contract catches a revert and re-throws with the original error -// embedded in the string), the Nested field links to the inner decoded error, +// embedded in the string), the Cause field links to the inner decoded error, // forming a recursive chain. type RevertError struct { - ErrorEntry *Entry // the matched ABI error entry at this level + ErrorEntry *Entry // the matched ABI error entry at this level cv *ComponentValue // decoded ABI data for this level - Prefix string // readable text before a nested error - Nested *RevertError // recursively decoded inner error, nil if none + Prefix string // readable text before a nested error + Cause *RevertError // recursively decoded inner error, nil if none } // DecodeRevertError decodes raw EVM revert data into a RevertError, @@ -115,14 +115,14 @@ func (r *RevertError) unwrapNested(ctx context.Context, selectors map[selectorKe return } - nested := &RevertError{ErrorEntry: entry, cv: cv} + cause := &RevertError{ErrorEntry: entry, cv: cv} r.Prefix = SanitizeBinaryString(raw[:idx]) - r.Nested = nested + r.Cause = cause // If the nested error is also Error(string), keep unwrapping if entry.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { - nested.unwrapNested(ctx, selectors, strVal, depth+1) + cause.unwrapNested(ctx, selectors, strVal, depth+1) } } } @@ -162,8 +162,8 @@ func (r *RevertError) String() string { } var b strings.Builder b.WriteString(r.Prefix) - if r.Nested != nil { - b.WriteString(r.Nested.String()) + if r.Cause != nil { + b.WriteString(r.Cause.String()) } else { b.WriteString(FormatErrorStringCtx(context.Background(), r.ErrorEntry, r.cv)) } @@ -205,11 +205,11 @@ func (r *RevertError) SerializeJSON(ctx context.Context, s *Serializer) ([]byte, // Cause returns the next error in the chain (one level deeper), or nil // at the leaf. Analogous to Java's Throwable.getCause(). -func (r *RevertError) Cause() *RevertError { +func (r *RevertError) GetCause() *RevertError { if r == nil { return nil } - return r.Nested + return r.Cause } // Innermost walks the chain to return the deepest RevertError — the @@ -219,8 +219,8 @@ func (r *RevertError) Innermost() *RevertError { return nil } cur := r - for cur.Nested != nil { - cur = cur.Nested + for cur.Cause != nil { + cur = cur.Cause } return cur } @@ -232,7 +232,7 @@ func (r *RevertError) Errors() []*RevertError { return nil } var result []*RevertError - for cur := r; cur != nil; cur = cur.Nested { + for cur := r; cur != nil; cur = cur.Cause { result = append(result, cur) } return result diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index baa937b1..7d4d9e83 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -58,7 +58,7 @@ func TestRevertErrorNilSerializeJSON(t *testing.T) { func TestRevertErrorNilCause(t *testing.T) { var r *RevertError - assert.Nil(t, r.Cause()) + assert.Nil(t, r.GetCause()) } func TestRevertErrorNilInnermost(t *testing.T) { @@ -110,7 +110,7 @@ func TestRevertErrorSingleCause(t *testing.T) { entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) r := &RevertError{ErrorEntry: entry, cv: cv} - assert.Nil(t, r.Cause()) + assert.Nil(t, r.GetCause()) } func TestRevertErrorSingleInnermost(t *testing.T) { @@ -146,7 +146,7 @@ func TestRevertErrorMultipleParams(t *testing.T) { assert.Equal(t, `ExampleError("test1","12345")`, r.String()) } -// --- Two-level nested error --- +// --- Two-levelCause error --- func TestRevertErrorNestedTwoLevelString(t *testing.T) { innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} @@ -159,7 +159,7 @@ func TestRevertErrorNestedTwoLevelString(t *testing.T) { ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", - Nested: inner, + Cause: inner, } assert.Equal(t, `[404]caught bytesAnError("I am an error")`, outer.String()) } @@ -173,7 +173,7 @@ func TestRevertErrorNestedTwoLevelSignatures(t *testing.T) { outer := &RevertError{ ErrorEntry: outerEntry, Prefix: "[404]caught bytes", - Nested: inner, + Cause: inner, } outerSig, err := outer.Signature() @@ -191,10 +191,10 @@ func TestRevertErrorNestedTwoLevelCause(t *testing.T) { inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Nested: inner} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Cause: inner} - assert.Equal(t, inner, outer.Cause()) - assert.Nil(t, inner.Cause()) + assert.Equal(t, inner, outer.GetCause()) + assert.Nil(t, inner.GetCause()) } func TestRevertErrorNestedTwoLevelInnermost(t *testing.T) { @@ -203,7 +203,7 @@ func TestRevertErrorNestedTwoLevelInnermost(t *testing.T) { inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Nested: inner} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Cause: inner} assert.Equal(t, inner, outer.Innermost()) assert.Equal(t, inner, inner.Innermost()) @@ -215,7 +215,7 @@ func TestRevertErrorNestedTwoLevelErrors(t *testing.T) { inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Nested: inner} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Cause: inner} errs := outer.Errors() require.Len(t, errs, 2) @@ -230,14 +230,14 @@ func TestRevertErrorNestedTwoLevelSerializeJSONInnermost(t *testing.T) { outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} outerCV := decodeTestError(t, outerEntry, `{"reason":"raw bytes here"}`) - outer := &RevertError{ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", Nested: inner} + outer := &RevertError{ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", Cause: inner} b, err := outer.Innermost().SerializeJSON(context.Background(), nil) assert.NoError(t, err) assert.Contains(t, string(b), "I am an error") } -// --- Three-level nested error --- +// --- Three-levelCause error --- func TestRevertErrorNestedThreeLevelString(t *testing.T) { leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} @@ -245,10 +245,10 @@ func TestRevertErrorNestedThreeLevelString(t *testing.T) { leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} middleEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - middle := &RevertError{ErrorEntry: middleEntry, Prefix: "middleware: ", Nested: leaf} + middle := &RevertError{ErrorEntry: middleEntry, Prefix: "middleware: ", Cause: leaf} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "gateway: ", Nested: middle} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "gateway: ", Cause: middle} assert.Equal(t, `gateway: middleware: RootCause("the real problem")`, outer.String()) } @@ -258,8 +258,8 @@ func TestRevertErrorNestedThreeLevelInnermost(t *testing.T) { leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} - middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Nested: leaf} - outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Nested: middle} + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Cause: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Cause: middle} assert.Equal(t, leaf, outer.Innermost()) } @@ -269,8 +269,8 @@ func TestRevertErrorNestedThreeLevelErrors(t *testing.T) { leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} - middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Nested: leaf} - outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Nested: middle} + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Cause: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Cause: middle} errs := outer.Errors() require.Len(t, errs, 3) @@ -284,12 +284,12 @@ func TestRevertErrorNestedThreeLevelCauseChain(t *testing.T) { leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} - middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Nested: leaf} - outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Nested: middle} + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Cause: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Cause: middle} - assert.Equal(t, middle, outer.Cause()) - assert.Equal(t, leaf, outer.Cause().Cause()) - assert.Nil(t, outer.Cause().Cause().Cause()) + assert.Equal(t, middle, outer.GetCause()) + assert.Equal(t, leaf, outer.GetCause().GetCause()) + assert.Nil(t, outer.GetCause().GetCause().GetCause()) } // --- SerializeJSON with custom serializer --- @@ -315,7 +315,7 @@ func TestRevertErrorEmptyPrefix(t *testing.T) { outer := &RevertError{ ErrorEntry: &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, Prefix: "", - Nested: inner, + Cause: inner, } assert.Equal(t, `AnError("direct")`, outer.String()) } @@ -338,7 +338,7 @@ func TestDecodeRevertErrorDefaultErrorString(t *testing.T) { require.NotNil(t, r) assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, `Error("Not enough Ether provided.")`, r.String()) - assert.Nil(t, r.Cause()) + assert.Nil(t, r.GetCause()) } func TestDecodeRevertErrorDefaultPanic(t *testing.T) { @@ -445,7 +445,7 @@ func testEncodeError(t *testing.T, entry *Entry, jsonArgs string) []byte { return encoded } -// --- Nested unwrapping (DecodeRevertError with nesting) --- +// ---Cause unwrapping (DecodeRevertError with nesting) --- func TestDecodeRevertErrorSingleNested(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner error message")) @@ -456,9 +456,9 @@ func TestDecodeRevertErrorSingleNested(t *testing.T) { assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "outer: ", r.Prefix) - require.NotNil(t, r.Cause()) - assert.Equal(t, "Error", r.Cause().ErrorEntry.Name) - assert.Nil(t, r.Cause().Cause()) + require.NotNil(t, r.GetCause()) + assert.Equal(t, "Error", r.GetCause().ErrorEntry.Name) + assert.Nil(t, r.GetCause().GetCause()) assert.Equal(t, `outer: Error("inner error message")`, r.String()) } @@ -472,13 +472,13 @@ func TestDecodeRevertErrorDoubleNested(t *testing.T) { require.NotNil(t, r) assert.Equal(t, "level1: ", r.Prefix) - mid := r.Cause() + mid := r.GetCause() require.NotNil(t, mid) assert.Equal(t, "level2: ", mid.Prefix) - leaf := mid.Cause() + leaf := mid.GetCause() require.NotNil(t, leaf) - assert.Nil(t, leaf.Cause()) + assert.Nil(t, leaf.GetCause()) assert.Equal(t, `level1: level2: Error("deepest error")`, r.String()) assert.Equal(t, leaf, r.Innermost()) @@ -498,10 +498,10 @@ func TestDecodeRevertErrorNestedCustomError(t *testing.T) { assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "[404]01d - caught bytes:", r.Prefix) - inner := r.Cause() + inner := r.GetCause() require.NotNil(t, inner) assert.Equal(t, "MyCustomError", inner.ErrorEntry.Name) - assert.Nil(t, inner.Cause()) + assert.Nil(t, inner.GetCause()) sig, err := inner.Signature() assert.NoError(t, err) @@ -525,7 +525,7 @@ func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { require.NotNil(t, r) assert.Equal(t, "head:", r.Prefix) - inner := r.Cause() + inner := r.GetCause() require.NotNil(t, inner) assert.Equal(t, "EarlyErr", inner.ErrorEntry.Name, "first matching selector wins") } @@ -536,7 +536,7 @@ func TestDecodeRevertErrorNoNesting(t *testing.T) { r := ABI{}.DecodeRevertError(outerABI) require.NotNil(t, r) assert.Equal(t, "", r.Prefix) - assert.Nil(t, r.Cause()) + assert.Nil(t, r.GetCause()) assert.Equal(t, `Error("plain error with no nesting")`, r.String()) } @@ -550,7 +550,7 @@ func TestDecodeRevertErrorMalformedNested(t *testing.T) { r := ABI{}.DecodeRevertError(outerABI) require.NotNil(t, r) - assert.Nil(t, r.Cause(), "malformed nested data should not produce a nested error") + assert.Nil(t, r.GetCause(), "malformedCause data should not produce aCause error") assert.Equal(t, "", r.Prefix) } @@ -565,7 +565,7 @@ func TestDecodeRevertErrorDepthLimit(t *testing.T) { require.NotNil(t, r) depth := 0 - for cur := r; cur != nil; cur = cur.Cause() { + for cur := r; cur != nil; cur = cur.GetCause() { depth++ } assert.LessOrEqual(t, depth, maxRevertErrorDepth+1, "chain should be capped by depth limit") @@ -596,7 +596,7 @@ func TestDecodeRevertErrorNestedSignatures(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Error(string)", outerSig) - innerSig, err := r.Cause().Signature() + innerSig, err := r.GetCause().Signature() assert.NoError(t, err) assert.Equal(t, "Error(string)", innerSig) } From b97148986e68c5e06f58c4ea8194c3a040ed6b37 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Thu, 16 Apr 2026 11:14:53 +0100 Subject: [PATCH 12/22] Update go.sum Signed-off-by: Dave Crighton --- go.sum | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/go.sum b/go.sum index 14b274ef..48a14336 100644 --- a/go.sum +++ b/go.sum @@ -5,32 +5,49 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 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/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= +github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 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/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763 h1:hcVVxUu7zWJld2TL8prVyZ4mxae4wt0eRM1dnzIwzhg= +github.com/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763/go.mod h1:1Xawm5PUhxT7k+CL/Kr3i1LE3cTTzoQwZMLimvlW8rs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= @@ -42,13 +59,17 @@ github.com/karlseguin/ccache v2.0.3+incompatible/go.mod h1:CM9tNPzT6EdRh14+jiW8m github.com/karlseguin/expect v1.0.8 h1:Bb0H6IgBWQpadY25UDNkYPDB9ITqK1xnSoZfAq362fw= github.com/karlseguin/expect v1.0.8/go.mod h1:lXdI8iGiQhmzpnnmU/EGA60vqKs8NbRNFnhhrJGoD5g= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= @@ -56,7 +77,9 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyex github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= @@ -64,9 +87,11 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 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.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -74,29 +99,45 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= 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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +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.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= 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 v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= @@ -104,16 +145,27 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V gitlab.com/hfuss/mux-prometheus v0.0.5 h1:Kcqyiekx8W2dO1EHg+6wOL1F0cFNgRO1uCK18V31D0s= gitlab.com/hfuss/mux-prometheus v0.0.5/go.mod h1:xcedy8rVGr9TFgRu2urfGuh99B4NdfYdpE4aUMQ0dxA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -128,3 +180,4 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= From 7a8f9529d782541df510863ab46e8548c38e17a0 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Thu, 16 Apr 2026 11:50:57 +0100 Subject: [PATCH 13/22] Clarify some comments Signed-off-by: Dave Crighton --- pkg/abi/reverterror.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 5c4867e4..91579690 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -62,7 +62,8 @@ func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *Rever // Only Error(string) is unwrapped for nesting, because the Solidity // catch-and-rethrow pattern (string.concat + string(reason)) always // produces Error(string). Custom errors with string/bytes params that - // also embed error data are not yet handled here. + // also embed error data are not yet handled since there is a high liklihood + //that they are not intended to carry error data. if e.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { r.unwrapNested(ctx, a.selectorMap(), strVal, 0) @@ -97,7 +98,7 @@ func (a ABI) selectorMap() map[selectorKey]*Entry { } // unwrapNested scans a decoded string value for an embedded ABI error selector. -// If found, it populates r.Prefix and r.Nested to form the recursive chain. +// If found, it populates r.Prefix and r.Cause to form the recursive chain. func (r *RevertError) unwrapNested(ctx context.Context, selectors map[selectorKey]*Entry, s string, depth int) { if depth >= maxRevertErrorDepth { return @@ -172,7 +173,7 @@ func (r *RevertError) String() string { // ErrorString returns the formatted error at this level only, e.g. // Error("not enough funds") or MyCustomError("0x1234","-100"). -// Unlike String(), it does not walk the Nested chain — use it when +// Unlike String(), it does not walk the Cause chain — use it when // you need the single-level description without recursive unwrapping. func (r *RevertError) ErrorString() string { if r == nil { @@ -191,8 +192,7 @@ func (r *RevertError) Signature() (string, error) { } // SerializeJSON serializes the decoded error data at this level using -// the provided Serializer. This is most useful on the Innermost() error -// where the data is cleanly structured. +// the provided Serializer. func (r *RevertError) SerializeJSON(ctx context.Context, s *Serializer) ([]byte, error) { if r == nil || r.cv == nil { return nil, nil @@ -204,7 +204,7 @@ func (r *RevertError) SerializeJSON(ctx context.Context, s *Serializer) ([]byte, } // Cause returns the next error in the chain (one level deeper), or nil -// at the leaf. Analogous to Java's Throwable.getCause(). +// at the leaf. func (r *RevertError) GetCause() *RevertError { if r == nil { return nil From 0f33fb8c1f915b51405865be9f90f440fb00994a Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Thu, 16 Apr 2026 12:12:43 +0100 Subject: [PATCH 14/22] go mod updates Signed-off-by: Dave Crighton --- go.mod | 91 ++++++++++---------- go.sum | 257 ++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 199 insertions(+), 149 deletions(-) diff --git a/go.mod b/go.mod index 6a33ffa3..404b5393 100644 --- a/go.mod +++ b/go.mod @@ -1,83 +1,80 @@ module github.com/hyperledger/firefly-signer -go 1.24.0 - -toolchain go1.24.13 +go 1.23.0 require ( github.com/btcsuite/btcd/btcec/v2 v2.3.5 - github.com/fsnotify/fsnotify v1.9.0 - github.com/go-resty/resty/v2 v2.17.1 + github.com/fsnotify/fsnotify v1.7.0 + github.com/go-resty/resty/v2 v2.11.0 github.com/gorilla/mux v1.8.1 - github.com/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763 + github.com/hyperledger/firefly-common v1.5.5 github.com/karlseguin/ccache v2.0.3+incompatible github.com/pelletier/go-toml v1.9.5 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 - github.com/sirupsen/logrus v1.9.4 - github.com/spf13/cobra v1.10.2 - github.com/spf13/viper v1.21.0 - github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.47.0 - golang.org/x/text v0.33.0 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.36.0 + golang.org/x/text v0.23.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/aidarkhanov/nanoid v1.0.8 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/getkin/kin-openapi v0.133.0 // indirect + github.com/getkin/kin-openapi v0.131.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-openapi/jsonpointer v0.22.4 // indirect - github.com/go-openapi/swag/jsonname v0.25.4 // indirect - github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.3 // indirect - github.com/mailru/easyjson v0.9.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.11 // indirect + github.com/nxadm/tail v1.4.8 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/onsi/gomega v1.35.1 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - 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.19.2 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/sagikazarmark/locafero v0.12.0 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect - github.com/stretchr/objx v0.5.3 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect gitlab.com/hfuss/mux-prometheus v0.0.5 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/time v0.14.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 48a14336..382a43b0 100644 --- a/go.sum +++ b/go.sum @@ -4,50 +4,50 @@ 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/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= -github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= -github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -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/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= -github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= +github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= -github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= -github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= -github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= -github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= -github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= -github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= -github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= -github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -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/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763 h1:hcVVxUu7zWJld2TL8prVyZ4mxae4wt0eRM1dnzIwzhg= -github.com/hyperledger/firefly-common v1.5.10-0.20260317153823-d83da7690763/go.mod h1:1Xawm5PUhxT7k+CL/Kr3i1LE3cTTzoQwZMLimvlW8rs= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hyperledger/firefly-common v1.5.5 h1:UaANgBIT0aBvAk0Yt+Qrn6qXxpwNIrFfwnW3EBivrQs= +github.com/hyperledger/firefly-common v1.5.5/go.mod h1:1Xawm5PUhxT7k+CL/Kr3i1LE3cTTzoQwZMLimvlW8rs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= @@ -58,126 +58,179 @@ github.com/karlseguin/ccache v2.0.3+incompatible h1:j68C9tWOROiOLWTS/kCGg9IcJG+A github.com/karlseguin/ccache v2.0.3+incompatible/go.mod h1:CM9tNPzT6EdRh14+jiW8mEF9mkNZuuE51qmgGYUB93w= github.com/karlseguin/expect v1.0.8 h1:Bb0H6IgBWQpadY25UDNkYPDB9ITqK1xnSoZfAq362fw= github.com/karlseguin/expect v1.0.8/go.mod h1:lXdI8iGiQhmzpnnmU/EGA60vqKs8NbRNFnhhrJGoD5g= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= -github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 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.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= -github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= -github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -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.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -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 v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= -github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/hfuss/mux-prometheus v0.0.5 h1:Kcqyiekx8W2dO1EHg+6wOL1F0cFNgRO1uCK18V31D0s= gitlab.com/hfuss/mux-prometheus v0.0.5/go.mod h1:xcedy8rVGr9TFgRu2urfGuh99B4NdfYdpE4aUMQ0dxA= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.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/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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= +gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= From 41816c0c21d9dc1b6a7993ee0a744cc109de47d0 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 10:24:19 +0100 Subject: [PATCH 15/22] Update to use InnerError as spelling Signed-off-by: Dave Crighton --- internal/signermsgs/en_field_descriptions.go | 4 + pkg/abi/reverterror.go | 46 +++++------ pkg/abi/reverterror_test.go | 86 ++++++++++---------- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/internal/signermsgs/en_field_descriptions.go b/internal/signermsgs/en_field_descriptions.go index b41f5a33..ca669035 100644 --- a/internal/signermsgs/en_field_descriptions.go +++ b/internal/signermsgs/en_field_descriptions.go @@ -48,6 +48,10 @@ var ( EIP712ResultR = ffm("EIP712Result.r", "The R value of the ECDSA signature as a 32byte hex encoded array") EIP712ResultS = ffm("EIP712Result.s", "The S value of the ECDSA signature as a 32byte hex encoded array") + RevertErrorErrorEntry = ffm("RevertError.errorEntry", "The matched ABI error entry at this level of the error chain") + RevertErrorPrefix = ffm("RevertError.prefix", "The readable text prefix before the nested inner error, extracted from the outer Error(string) value") + RevertErrorInnerError = ffm("RevertError.innerError", "The recursively decoded inner error, forming the next level of the error chain, or nil if this is the leaf") + TypedDataDomain = ffm("TypedData.domain", "The data to encode into the EIP712Domain as part fo signing the transaction") TypedDataMessage = ffm("TypedData.message", "The data to encode into primaryType structure, with nested values for any sub-structures") TypedDataTypes = ffm("TypedData.types", "Array of types to use when encoding, which must include the primaryType and the EIP712Domain (noting the primary type can be EIP712Domain if the message is empty)") diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 91579690..f18d5e27 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -34,13 +34,13 @@ const maxRevertErrorDepth = 10 // RevertError represents a decoded Solidity revert error. For nested errors // (where a contract catches a revert and re-throws with the original error -// embedded in the string), the Cause field links to the inner decoded error, -// forming a recursive chain. +// embedded in the string), the InnerError field links to the inner decoded +// error, forming a recursive chain. type RevertError struct { - ErrorEntry *Entry // the matched ABI error entry at this level - cv *ComponentValue // decoded ABI data for this level - Prefix string // readable text before a nested error - Cause *RevertError // recursively decoded inner error, nil if none + ErrorEntry *Entry `ffstruct:"RevertError" json:"errorEntry,omitempty"` // the matched ABI error entry at this level + cv *ComponentValue // decoded ABI data for this level (unexported) + Prefix string `ffstruct:"RevertError" json:"prefix,omitempty"` // readable text before the inner error + InnerError *RevertError `ffstruct:"RevertError" json:"innerError,omitempty"` // recursively decoded inner error, nil if none } // DecodeRevertError decodes raw EVM revert data into a RevertError, @@ -66,7 +66,7 @@ func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *Rever //that they are not intended to carry error data. if e.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { - r.unwrapNested(ctx, a.selectorMap(), strVal, 0) + r.unwrapInnerError(ctx, a.selectorMap(), strVal, 0) } } return r @@ -97,9 +97,9 @@ func (a ABI) selectorMap() map[selectorKey]*Entry { return selectors } -// unwrapNested scans a decoded string value for an embedded ABI error selector. -// If found, it populates r.Prefix and r.Cause to form the recursive chain. -func (r *RevertError) unwrapNested(ctx context.Context, selectors map[selectorKey]*Entry, s string, depth int) { +// unwrapInnerError scans a decoded string value for an embedded ABI error selector. +// If found, it populates r.Prefix and r.InnerError to form the recursive chain. +func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[selectorKey]*Entry, s string, depth int) { if depth >= maxRevertErrorDepth { return } @@ -112,18 +112,18 @@ func (r *RevertError) unwrapNested(ctx context.Context, selectors map[selectorKe cv, err := entry.DecodeCallDataCtx(ctx, raw[idx:]) if err != nil { - log.L(ctx).Debugf("Could not decode nested revert at depth %d: %s", depth, err) + log.L(ctx).Debugf("Could not decode inner error at depth %d: %s", depth, err) return } - cause := &RevertError{ErrorEntry: entry, cv: cv} + inner := &RevertError{ErrorEntry: entry, cv: cv} r.Prefix = SanitizeBinaryString(raw[:idx]) - r.Cause = cause + r.InnerError = inner - // If the nested error is also Error(string), keep unwrapping + // If the inner error is also Error(string), keep unwrapping if entry.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { - cause.unwrapNested(ctx, selectors, strVal, depth+1) + inner.unwrapInnerError(ctx, selectors, strVal, depth+1) } } } @@ -163,8 +163,8 @@ func (r *RevertError) String() string { } var b strings.Builder b.WriteString(r.Prefix) - if r.Cause != nil { - b.WriteString(r.Cause.String()) + if r.InnerError != nil { + b.WriteString(r.InnerError.String()) } else { b.WriteString(FormatErrorStringCtx(context.Background(), r.ErrorEntry, r.cv)) } @@ -203,13 +203,13 @@ func (r *RevertError) SerializeJSON(ctx context.Context, s *Serializer) ([]byte, return s.SerializeJSONCtx(ctx, r.cv) } -// Cause returns the next error in the chain (one level deeper), or nil +// GetInnerError returns the next error in the chain (one level deeper), or nil // at the leaf. -func (r *RevertError) GetCause() *RevertError { +func (r *RevertError) GetInnerError() *RevertError { if r == nil { return nil } - return r.Cause + return r.InnerError } // Innermost walks the chain to return the deepest RevertError — the @@ -219,8 +219,8 @@ func (r *RevertError) Innermost() *RevertError { return nil } cur := r - for cur.Cause != nil { - cur = cur.Cause + for cur.InnerError != nil { + cur = cur.InnerError } return cur } @@ -232,7 +232,7 @@ func (r *RevertError) Errors() []*RevertError { return nil } var result []*RevertError - for cur := r; cur != nil; cur = cur.Cause { + for cur := r; cur != nil; cur = cur.InnerError { result = append(result, cur) } return result diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index 7d4d9e83..ccaf8f80 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -56,9 +56,9 @@ func TestRevertErrorNilSerializeJSON(t *testing.T) { assert.Nil(t, b) } -func TestRevertErrorNilCause(t *testing.T) { +func TestRevertErrorNilInnerError(t *testing.T) { var r *RevertError - assert.Nil(t, r.GetCause()) + assert.Nil(t, r.GetInnerError()) } func TestRevertErrorNilInnermost(t *testing.T) { @@ -106,11 +106,11 @@ func TestRevertErrorSingleSerializeJSONNilCV(t *testing.T) { assert.Nil(t, b) } -func TestRevertErrorSingleCause(t *testing.T) { +func TestRevertErrorSingleInnerError(t *testing.T) { entry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} cv := decodeTestError(t, entry, `{"message":"something went wrong"}`) r := &RevertError{ErrorEntry: entry, cv: cv} - assert.Nil(t, r.GetCause()) + assert.Nil(t, r.GetInnerError()) } func TestRevertErrorSingleInnermost(t *testing.T) { @@ -146,7 +146,7 @@ func TestRevertErrorMultipleParams(t *testing.T) { assert.Equal(t, `ExampleError("test1","12345")`, r.String()) } -// --- Two-levelCause error --- +// --- Two-level InnerError --- func TestRevertErrorNestedTwoLevelString(t *testing.T) { innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} @@ -159,7 +159,7 @@ func TestRevertErrorNestedTwoLevelString(t *testing.T) { ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", - Cause: inner, + InnerError: inner, } assert.Equal(t, `[404]caught bytesAnError("I am an error")`, outer.String()) } @@ -173,7 +173,7 @@ func TestRevertErrorNestedTwoLevelSignatures(t *testing.T) { outer := &RevertError{ ErrorEntry: outerEntry, Prefix: "[404]caught bytes", - Cause: inner, + InnerError: inner, } outerSig, err := outer.Signature() @@ -185,16 +185,16 @@ func TestRevertErrorNestedTwoLevelSignatures(t *testing.T) { assert.Equal(t, "AnError(string)", innerSig) } -func TestRevertErrorNestedTwoLevelCause(t *testing.T) { +func TestRevertErrorNestedTwoLevelInnerError(t *testing.T) { innerEntry := &Entry{Type: Error, Name: "AnError", Inputs: ParameterArray{{Name: "message", Type: "string"}}} innerCV := decodeTestError(t, innerEntry, `{"message":"I am an error"}`) inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Cause: inner} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", InnerError: inner} - assert.Equal(t, inner, outer.GetCause()) - assert.Nil(t, inner.GetCause()) + assert.Equal(t, inner, outer.GetInnerError()) + assert.Nil(t, inner.GetInnerError()) } func TestRevertErrorNestedTwoLevelInnermost(t *testing.T) { @@ -203,7 +203,7 @@ func TestRevertErrorNestedTwoLevelInnermost(t *testing.T) { inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Cause: inner} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", InnerError: inner} assert.Equal(t, inner, outer.Innermost()) assert.Equal(t, inner, inner.Innermost()) @@ -215,7 +215,7 @@ func TestRevertErrorNestedTwoLevelErrors(t *testing.T) { inner := &RevertError{ErrorEntry: innerEntry, cv: innerCV} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", Cause: inner} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "[404]caught bytes", InnerError: inner} errs := outer.Errors() require.Len(t, errs, 2) @@ -230,14 +230,14 @@ func TestRevertErrorNestedTwoLevelSerializeJSONInnermost(t *testing.T) { outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} outerCV := decodeTestError(t, outerEntry, `{"reason":"raw bytes here"}`) - outer := &RevertError{ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", Cause: inner} + outer := &RevertError{ErrorEntry: outerEntry, cv: outerCV, Prefix: "[404]caught bytes", InnerError: inner} b, err := outer.Innermost().SerializeJSON(context.Background(), nil) assert.NoError(t, err) assert.Contains(t, string(b), "I am an error") } -// --- Three-levelCause error --- +// --- Three-level InnerError --- func TestRevertErrorNestedThreeLevelString(t *testing.T) { leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} @@ -245,10 +245,10 @@ func TestRevertErrorNestedThreeLevelString(t *testing.T) { leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} middleEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - middle := &RevertError{ErrorEntry: middleEntry, Prefix: "middleware: ", Cause: leaf} + middle := &RevertError{ErrorEntry: middleEntry, Prefix: "middleware: ", InnerError: leaf} outerEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - outer := &RevertError{ErrorEntry: outerEntry, Prefix: "gateway: ", Cause: middle} + outer := &RevertError{ErrorEntry: outerEntry, Prefix: "gateway: ", InnerError: middle} assert.Equal(t, `gateway: middleware: RootCause("the real problem")`, outer.String()) } @@ -258,8 +258,8 @@ func TestRevertErrorNestedThreeLevelInnermost(t *testing.T) { leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} - middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Cause: leaf} - outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Cause: middle} + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", InnerError: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", InnerError: middle} assert.Equal(t, leaf, outer.Innermost()) } @@ -269,8 +269,8 @@ func TestRevertErrorNestedThreeLevelErrors(t *testing.T) { leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} - middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", Cause: leaf} - outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", Cause: middle} + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "middleware: ", InnerError: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Prefix: "gateway: ", InnerError: middle} errs := outer.Errors() require.Len(t, errs, 3) @@ -279,17 +279,17 @@ func TestRevertErrorNestedThreeLevelErrors(t *testing.T) { assert.Equal(t, leaf, errs[2]) } -func TestRevertErrorNestedThreeLevelCauseChain(t *testing.T) { +func TestRevertErrorNestedThreeLevelInnerErrorChain(t *testing.T) { leafEntry := &Entry{Type: Error, Name: "RootCause", Inputs: ParameterArray{{Name: "detail", Type: "string"}}} leafCV := decodeTestError(t, leafEntry, `{"detail":"the real problem"}`) leaf := &RevertError{ErrorEntry: leafEntry, cv: leafCV} - middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Cause: leaf} - outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, Cause: middle} + middle := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, InnerError: leaf} + outer := &RevertError{ErrorEntry: &Entry{Type: Error, Name: "Error"}, InnerError: middle} - assert.Equal(t, middle, outer.GetCause()) - assert.Equal(t, leaf, outer.GetCause().GetCause()) - assert.Nil(t, outer.GetCause().GetCause().GetCause()) + assert.Equal(t, middle, outer.GetInnerError()) + assert.Equal(t, leaf, outer.GetInnerError().GetInnerError()) + assert.Nil(t, outer.GetInnerError().GetInnerError().GetInnerError()) } // --- SerializeJSON with custom serializer --- @@ -315,7 +315,7 @@ func TestRevertErrorEmptyPrefix(t *testing.T) { outer := &RevertError{ ErrorEntry: &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}}, Prefix: "", - Cause: inner, + InnerError: inner, } assert.Equal(t, `AnError("direct")`, outer.String()) } @@ -338,7 +338,7 @@ func TestDecodeRevertErrorDefaultErrorString(t *testing.T) { require.NotNil(t, r) assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, `Error("Not enough Ether provided.")`, r.String()) - assert.Nil(t, r.GetCause()) + assert.Nil(t, r.GetInnerError()) } func TestDecodeRevertErrorDefaultPanic(t *testing.T) { @@ -445,7 +445,7 @@ func testEncodeError(t *testing.T, entry *Entry, jsonArgs string) []byte { return encoded } -// ---Cause unwrapping (DecodeRevertError with nesting) --- +// --- InnerError unwrapping (DecodeRevertError with nesting) --- func TestDecodeRevertErrorSingleNested(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner error message")) @@ -456,9 +456,9 @@ func TestDecodeRevertErrorSingleNested(t *testing.T) { assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "outer: ", r.Prefix) - require.NotNil(t, r.GetCause()) - assert.Equal(t, "Error", r.GetCause().ErrorEntry.Name) - assert.Nil(t, r.GetCause().GetCause()) + require.NotNil(t, r.GetInnerError()) + assert.Equal(t, "Error", r.GetInnerError().ErrorEntry.Name) + assert.Nil(t, r.GetInnerError().GetInnerError()) assert.Equal(t, `outer: Error("inner error message")`, r.String()) } @@ -472,13 +472,13 @@ func TestDecodeRevertErrorDoubleNested(t *testing.T) { require.NotNil(t, r) assert.Equal(t, "level1: ", r.Prefix) - mid := r.GetCause() + mid := r.GetInnerError() require.NotNil(t, mid) assert.Equal(t, "level2: ", mid.Prefix) - leaf := mid.GetCause() + leaf := mid.GetInnerError() require.NotNil(t, leaf) - assert.Nil(t, leaf.GetCause()) + assert.Nil(t, leaf.GetInnerError()) assert.Equal(t, `level1: level2: Error("deepest error")`, r.String()) assert.Equal(t, leaf, r.Innermost()) @@ -498,10 +498,10 @@ func TestDecodeRevertErrorNestedCustomError(t *testing.T) { assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "[404]01d - caught bytes:", r.Prefix) - inner := r.GetCause() + inner := r.GetInnerError() require.NotNil(t, inner) assert.Equal(t, "MyCustomError", inner.ErrorEntry.Name) - assert.Nil(t, inner.GetCause()) + assert.Nil(t, inner.GetInnerError()) sig, err := inner.Signature() assert.NoError(t, err) @@ -525,7 +525,7 @@ func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { require.NotNil(t, r) assert.Equal(t, "head:", r.Prefix) - inner := r.GetCause() + inner := r.GetInnerError() require.NotNil(t, inner) assert.Equal(t, "EarlyErr", inner.ErrorEntry.Name, "first matching selector wins") } @@ -536,7 +536,7 @@ func TestDecodeRevertErrorNoNesting(t *testing.T) { r := ABI{}.DecodeRevertError(outerABI) require.NotNil(t, r) assert.Equal(t, "", r.Prefix) - assert.Nil(t, r.GetCause()) + assert.Nil(t, r.GetInnerError()) assert.Equal(t, `Error("plain error with no nesting")`, r.String()) } @@ -550,7 +550,7 @@ func TestDecodeRevertErrorMalformedNested(t *testing.T) { r := ABI{}.DecodeRevertError(outerABI) require.NotNil(t, r) - assert.Nil(t, r.GetCause(), "malformedCause data should not produce aCause error") + assert.Nil(t, r.GetInnerError(), "malformed inner data should not produce an inner error") assert.Equal(t, "", r.Prefix) } @@ -565,7 +565,7 @@ func TestDecodeRevertErrorDepthLimit(t *testing.T) { require.NotNil(t, r) depth := 0 - for cur := r; cur != nil; cur = cur.GetCause() { + for cur := r; cur != nil; cur = cur.GetInnerError() { depth++ } assert.LessOrEqual(t, depth, maxRevertErrorDepth+1, "chain should be capped by depth limit") @@ -596,7 +596,7 @@ func TestDecodeRevertErrorNestedSignatures(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Error(string)", outerSig) - innerSig, err := r.GetCause().Signature() + innerSig, err := r.GetInnerError().Signature() assert.NoError(t, err) assert.Equal(t, "Error(string)", innerSig) } From d9ab8242337a50c9687d871d4023bc9fa50c6a73 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 10:33:27 +0100 Subject: [PATCH 16/22] Expose selectorMap as an external and introduce FilterType helper Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 38 ++++++++++++++++++++- pkg/abi/abi_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++ pkg/abi/reverterror.go | 53 ++++++++--------------------- 3 files changed, 125 insertions(+), 41 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 6fde0986..af946d5a 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -315,6 +315,42 @@ func (a ABI) Errors() map[string]*Entry { return m } +// FilterType returns a new ABI containing only the entries whose Type matches t, +// preserving their original order. Returns nil if no entries match. +// Use this to narrow an ABI before passing it to SelectorMap or other helpers, +// e.g. a.FilterType(Error) to obtain only error definitions. +func (a ABI) FilterType(t EntryType) ABI { + var out ABI + for _, e := range a { + if e.Type == t { + out = append(out, e) + } + } + return out +} + +// SelectorMap builds a map from 4-byte ABI selectors to their corresponding +// entries. When two entries in the ABI produce the same 4-byte selector, the +// first one wins. Entries for which selector generation fails are silently +// skipped. +// +// SelectorMap operates on all entry types — combine with FilterType to restrict +// to a specific type, e.g. a.FilterType(Error).SelectorMap(). +func (a ABI) SelectorMap() map[[4]byte]*Entry { + m := make(map[[4]byte]*Entry) + for _, e := range a { + sel := e.FunctionSelectorBytes() + if len(sel) >= 4 { + var key [4]byte + copy(key[:], sel[:4]) + if _, exists := m[key]; !exists { + m[key] = e + } + } + } + return m +} + // Returns the components value from the parsed error func (a ABI) ParseError(revertData []byte) (*Entry, *ComponentValue, bool) { return a.ParseErrorCtx(context.Background(), revertData) @@ -324,7 +360,7 @@ func (a ABI) ParseError(revertData []byte) (*Entry, *ComponentValue, bool) { // given revert data. The ABI's error entries are tried first, followed by the // built-in Error(string) and Panic(uint256). func (a ABI) ParseErrorCtx(ctx context.Context, revertData []byte) (*Entry, *ComponentValue, bool) { - for _, source := range []ABI{a.errors(), defaultErrorEntries} { + for _, source := range []ABI{a.FilterType(Error), defaultErrorEntries} { for _, e := range source { if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { return e, cv, true diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index e9850ae7..99c48c4e 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -1076,6 +1076,81 @@ func TestErrorString(t *testing.T) { } +func TestFilterType(t *testing.T) { + abi := ABI{ + {Type: Function, Name: "transfer"}, + {Type: Error, Name: "MyError", Inputs: ParameterArray{{Type: "string"}}}, + {Type: Event, Name: "Transfer"}, + {Type: Error, Name: "AnotherError", Inputs: ParameterArray{{Type: "uint256"}}}, + } + + errors := abi.FilterType(Error) + require.Len(t, errors, 2) + assert.Equal(t, "MyError", errors[0].Name) + assert.Equal(t, "AnotherError", errors[1].Name) + + functions := abi.FilterType(Function) + require.Len(t, functions, 1) + assert.Equal(t, "transfer", functions[0].Name) + + // No constructors present — result should be nil + assert.Nil(t, abi.FilterType(Constructor)) + + // Empty ABI + assert.Nil(t, ABI{}.FilterType(Error)) +} + +func TestSelectorMap(t *testing.T) { + errorEntry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + panicEntry := &Entry{Type: Error, Name: "Panic", Inputs: ParameterArray{{Name: "code", Type: "uint256"}}} + abi := ABI{errorEntry, panicEntry} + + m := abi.SelectorMap() + assert.Len(t, m, 2) + + var errorKey [4]byte + copy(errorKey[:], errorEntry.FunctionSelectorBytes()) + assert.Equal(t, errorEntry, m[errorKey]) + + var panicKey [4]byte + copy(panicKey[:], panicEntry.FunctionSelectorBytes()) + assert.Equal(t, panicEntry, m[panicKey]) +} + +func TestSelectorMapFirstWins(t *testing.T) { + // Two entries with identical signatures produce the same 4-byte selector; + // the first one should win as a deterministic tiebreaker. + first := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + second := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + m := ABI{first, second}.SelectorMap() + + var key [4]byte + copy(key[:], first.FunctionSelectorBytes()) + assert.Equal(t, first, m[key], "first entry should win on selector collision") +} + +func TestSelectorMapEmpty(t *testing.T) { + assert.Empty(t, ABI{}.SelectorMap()) +} + +func TestSelectorMapAllEntryTypes(t *testing.T) { + // SelectorMap works on any entry type, not just errors. + // Callers can use FilterType first to restrict the scope. + fnEntry := &Entry{Type: Function, Name: "transfer", Inputs: ParameterArray{{Type: "address"}, {Type: "uint256"}}} + errEntry := &Entry{Type: Error, Name: "InsufficientBalance", Inputs: ParameterArray{{Type: "uint256"}}} + abi := ABI{fnEntry, errEntry} + + m := abi.SelectorMap() + assert.Len(t, m, 2) + + // Restricting to errors only via FilterType gives a smaller map. + errOnly := abi.FilterType(Error).SelectorMap() + assert.Len(t, errOnly, 1) + var key [4]byte + copy(key[:], errEntry.FunctionSelectorBytes()) + assert.Equal(t, errEntry, errOnly[key]) +} + // buildErrorStringABI builds the raw ABI encoding for Error(string) with the given message bytes. func buildErrorStringABI(msgBytes []byte) []byte { defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index f18d5e27..9fb39442 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -55,18 +55,23 @@ func (a ABI) DecodeRevertError(revertData []byte) *RevertError { // The ABI's error entries are tried first, followed by the built-in // Error(string) and Panic(uint256). Returns nil if no selector matches. func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *RevertError { - for _, source := range []ABI{a.errors(), defaultErrorEntries} { + abiErrors := a.FilterType(Error) + for _, source := range []ABI{abiErrors, defaultErrorEntries} { for _, e := range source { if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { r := &RevertError{ErrorEntry: e, cv: cv} - // Only Error(string) is unwrapped for nesting, because the Solidity + // Only Error(string) is unwrapped for inner errors, because the Solidity // catch-and-rethrow pattern (string.concat + string(reason)) always // produces Error(string). Custom errors with string/bytes params that - // also embed error data are not yet handled since there is a high liklihood - //that they are not intended to carry error data. + // also embed error data are not yet handled since there is a high likelihood + // that they are not intended to carry error data. if e.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { - r.unwrapInnerError(ctx, a.selectorMap(), strVal, 0) + // Build a selector map covering both the caller's error entries + // and the built-in defaults, so inner errors of either kind can + // be recognised during recursive unwrapping. + selectors := append(abiErrors, defaultErrorEntries...).SelectorMap() + r.unwrapInnerError(ctx, selectors, strVal, 0) } } return r @@ -76,30 +81,9 @@ func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *Rever return nil } -type selectorKey = [4]byte - -// selectorMap builds a lookup of 4-byte selectors for all error entries in -// the ABI plus the builtins. -func (a ABI) selectorMap() map[selectorKey]*Entry { - selectors := make(map[selectorKey]*Entry) - var key selectorKey - for _, source := range []ABI{a.errors(), defaultErrorEntries} { - for _, e := range source { - sel := e.FunctionSelectorBytes() - if len(sel) >= 4 { - copy(key[:], sel[:4]) - if _, exists := selectors[key]; !exists { - selectors[key] = e - } - } - } - } - return selectors -} - // unwrapInnerError scans a decoded string value for an embedded ABI error selector. // If found, it populates r.Prefix and r.InnerError to form the recursive chain. -func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[selectorKey]*Entry, s string, depth int) { +func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[[4]byte]*Entry, s string, depth int) { if depth >= maxRevertErrorDepth { return } @@ -129,11 +113,11 @@ func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[select } // findSelector scans raw bytes for the first occurrence of a known 4-byte error selector. -func findSelector(raw []byte, selectors map[selectorKey]*Entry) (int, *Entry) { +func findSelector(raw []byte, selectors map[[4]byte]*Entry) (int, *Entry) { if len(raw) < 4 { return -1, nil } - var key selectorKey + var key [4]byte for i := 0; i <= len(raw)-4; i++ { copy(key[:], raw[i:i+4]) if e, ok := selectors[key]; ok { @@ -143,17 +127,6 @@ func findSelector(raw []byte, selectors map[selectorKey]*Entry) (int, *Entry) { return -1, nil } -// errors returns only the Error-type entries from the ABI. -func (a ABI) errors() ABI { - var out ABI - for _, e := range a { - if e.Type == Error { - out = append(out, e) - } - } - return out -} - // String returns a human-readable representation of the full error chain. // It concatenates the Prefix at each level, with the leaf error formatted // as ErrorName(arg1,arg2,...). From 3536d3b9f65897dbb132d3a468385a47c374a736 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 10:42:20 +0100 Subject: [PATCH 17/22] Use existing helper methods to encode test data Signed-off-by: Dave Crighton --- pkg/abi/abi_test.go | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 99c48c4e..3ce3d2a6 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -17,7 +17,6 @@ package abi import ( - "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -1151,23 +1150,16 @@ func TestSelectorMapAllEntryTypes(t *testing.T) { assert.Equal(t, errEntry, errOnly[key]) } -// buildErrorStringABI builds the raw ABI encoding for Error(string) with the given message bytes. +// buildErrorStringABI encodes msgBytes as the reason argument of Error(string) +// using the ABI pipeline, producing call data prefixed with the 4-byte selector. +// msgBytes may contain arbitrary binary content (e.g. embedded ABI-encoded errors). func buildErrorStringABI(msgBytes []byte) []byte { - defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} - sel := defaultErr.FunctionSelectorBytes() - offset := make([]byte, 32) - binary.BigEndian.PutUint64(offset[24:], 0x20) - length := make([]byte, 32) - binary.BigEndian.PutUint64(length[24:], uint64(len(msgBytes))) - paddedLen := ((len(msgBytes) + 31) / 32) * 32 - data := make([]byte, paddedLen) - copy(data, msgBytes) - result := make([]byte, 0, 4+32+32+paddedLen) - result = append(result, sel...) - result = append(result, offset...) - result = append(result, length...) - result = append(result, data...) - return result + entry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + data, err := entry.EncodeCallDataValues([]interface{}{string(msgBytes)}) + if err != nil { + panic(fmt.Sprintf("buildErrorStringABI: %s", err)) + } + return data } func TestUnwrapErrorStringPlainError(t *testing.T) { @@ -1286,12 +1278,8 @@ func TestUnwrapErrorStringCustomBeforeDefaultError(t *testing.T) { customABI := ABI{ {Type: Error, Name: "EarlyErr", Inputs: ParameterArray{{Type: "uint256"}}}, } - customSel := customABI[0].FunctionSelectorBytes() - - arg := make([]byte, 32) - binary.BigEndian.PutUint64(arg[24:], 42) - customEncoded := append([]byte(nil), customSel...) - customEncoded = append(customEncoded, arg...) + customEncoded, err := customABI[0].EncodeCallDataValues([]interface{}{42}) + require.NoError(t, err) innerErrorABI := buildErrorStringABI([]byte("late-error")) // Custom selector appears before the Error(string) selector From 46f1c0d7eb4ee5c4034fd056bbedd426680d8f21 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 10:49:44 +0100 Subject: [PATCH 18/22] Align migration case on single call signature Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 61 ++++++++++++++++++++++++++------------------- pkg/abi/abi_test.go | 40 ++++++++++++++--------------- 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index af946d5a..22fd104c 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -370,37 +370,48 @@ func (a ABI) ParseErrorCtx(ctx context.Context, revertData []byte) (*Entry, *Com return nil, nil, false } -func (a ABI) ErrorString(revertData []byte) (string, bool) { - return a.ErrorStringCtx(context.Background(), revertData) -} - -func (a ABI) ErrorStringCtx(ctx context.Context, revertData []byte) (strError string, ok bool) { - e, cv, ok := a.ParseErrorCtx(ctx, revertData) - if ok { - strError = FormatErrorStringCtx(ctx, e, cv) - ok = strError != "" +// ErrorFormatOption configures the behaviour of ErrorString and ErrorStringCtx. +type ErrorFormatOption struct { + // Unwrap causes the full inner error chain to be decoded and formatted, + // handling the Solidity catch-and-rethrow pattern where a contract embeds + // a caught revert inside a new Error(string) via string.concat/string(reason). + // Without this option only the outermost error is formatted. + Unwrap bool +} + +// ErrorString formats raw EVM revert data as a human-readable string. +// Pass ErrorFormatOption{Unwrap: true} to recursively decode inner errors +// produced by Solidity catch-and-rethrow patterns. +func (a ABI) ErrorString(revertData []byte, options ...ErrorFormatOption) (string, bool) { + return a.ErrorStringCtx(context.Background(), revertData, options...) +} + +// ErrorStringCtx formats raw EVM revert data as a human-readable string. +// The ABI's own error entries are tried first, followed by the built-in +// Error(string) and Panic(uint256). +// Pass ErrorFormatOption{Unwrap: true} to recursively decode inner errors +// produced by Solidity catch-and-rethrow patterns. +func (a ABI) ErrorStringCtx(ctx context.Context, revertData []byte, options ...ErrorFormatOption) (string, bool) { + unwrap := false + for _, o := range options { + unwrap = unwrap || o.Unwrap + } + if unwrap { + r := a.DecodeRevertErrorCtx(ctx, revertData) + if r == nil { + return "", false + } + s := r.String() + return s, s != "" } - return strError, ok -} - -// UnwrapErrorStringCtx is like ErrorStringCtx but handles nested errors caused by -// Solidity contracts that catch a revert and re-throw using string(reason). -// Delegates to DecodeRevertErrorCtx for structured decoding, then formats the -// result as a human-readable string. -func (a ABI) UnwrapErrorStringCtx(ctx context.Context, revertData []byte) (string, bool) { - r := a.DecodeRevertErrorCtx(ctx, revertData) - if r == nil { + e, cv, ok := a.ParseErrorCtx(ctx, revertData) + if !ok { return "", false } - s := r.String() + s := FormatErrorStringCtx(ctx, e, cv) return s, s != "" } -// UnwrapErrorString is a convenience wrapper for UnwrapErrorStringCtx. -func (a ABI) UnwrapErrorString(revertData []byte) (string, bool) { - return a.UnwrapErrorStringCtx(context.Background(), revertData) -} - // SanitizeBinaryString returns the input as a text string if it is entirely // printable ASCII, or hex-encodes the entire input otherwise. This ensures the // output is always safe for database TEXT columns and human-readable logging. diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 3ce3d2a6..050533f3 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -1162,19 +1162,19 @@ func buildErrorStringABI(msgBytes []byte) []byte { return data } -func TestUnwrapErrorStringPlainError(t *testing.T) { +func TestErrorStringUnwrapPlainError(t *testing.T) { revertData := ethtypes.MustNewHexBytes0xPrefix( "0x08c379a0" + "0000000000000000000000000000000000000000000000000000000000000020" + "000000000000000000000000000000000000000000000000000000000000001a" + "4e6f7420656e6f7567682045746865722070726f76696465642e000000000000") - result, ok := ABI{}.UnwrapErrorString(revertData) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.Equal(t, `Error("Not enough Ether provided.")`, result) } -func TestUnwrapErrorStringSingleNested(t *testing.T) { +func TestErrorStringUnwrapSingleNested(t *testing.T) { revertData := ethtypes.MustNewHexBytes0xPrefix( "0x08c379a00000000000000000000000000000000000000000000000000000000000000020" + "000000000000000000000000000000000000000000000000000000000000006b" + @@ -1185,12 +1185,12 @@ func TestUnwrapErrorStringSingleNested(t *testing.T) { "696e6e6572206572726f72206d65737361676500000000000000000000000000" + "000000000000000000000000000000000000000000") - result, ok := ABI{}.UnwrapErrorString(revertData) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.Equal(t, `outer: Error("inner error message")`, result) } -func TestUnwrapErrorStringDoubleNested(t *testing.T) { +func TestErrorStringUnwrapDoubleNested(t *testing.T) { revertData := ethtypes.MustNewHexBytes0xPrefix( "0x08c379a0" + "0000000000000000000000000000000000000000000000000000000000000020" + @@ -1205,12 +1205,12 @@ func TestUnwrapErrorStringDoubleNested(t *testing.T) { "000000000000000000000000000000000000000000000000000000000000000d" + "64656570657374206572726f720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - result, ok := ABI{}.UnwrapErrorString(revertData) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.Equal(t, `level1: level2: Error("deepest error")`, result) } -func TestUnwrapErrorStringNestedCustomError(t *testing.T) { +func TestErrorStringUnwrapNestedCustomError(t *testing.T) { customABI := ABI{ {Type: Error, Name: "MyCustomError", Inputs: ParameterArray{{Type: "bytes"}}}, } @@ -1227,46 +1227,46 @@ func TestUnwrapErrorStringNestedCustomError(t *testing.T) { "deadbeef00000000000000000000000000000000000000000000000000000000" + "00000000") - // Without the custom ABI, the nested section can't be decoded — the + // Without the custom ABI the inner error can't be decoded — the // outer Error(string) is formatted directly (binary content included) - result, ok := ABI{}.UnwrapErrorString(revertData) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.True(t, strings.HasPrefix(result, `Error("[404]01d`)) - // With the custom ABI, the nested error is decoded - result, ok = customABI.UnwrapErrorString(revertData) + // With the custom ABI the inner error is decoded + result, ok = customABI.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.Equal(t, `[404]01d - caught bytes:MyCustomError("0xdeadbeef")`, result) } -func TestUnwrapErrorStringUnknownSelector(t *testing.T) { +func TestErrorStringUnwrapUnknownSelector(t *testing.T) { // Unknown top-level selector - _, ok := ABI{}.UnwrapErrorString([]byte{0x11, 0x22, 0x33, 0x44}) + _, ok := ABI{}.ErrorString([]byte{0x11, 0x22, 0x33, 0x44}, ErrorFormatOption{Unwrap: true}) assert.False(t, ok) } -func TestUnwrapErrorStringMalformedNestedABI(t *testing.T) { +func TestErrorStringUnwrapMalformedInnerABI(t *testing.T) { defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} sel := defaultErr.FunctionSelectorBytes() badData := "prefix:" + string(sel) + "truncated" outerABI := buildErrorStringABI([]byte(badData)) - // Malformed nested data can't be decoded, so the outer Error(string) + // Malformed inner data can't be decoded, so the outer Error(string) // is formatted directly with the raw string content - result, ok := ABI{}.UnwrapErrorString(outerABI) + result, ok := ABI{}.ErrorString(outerABI, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.True(t, strings.HasPrefix(result, "Error(")) } -func TestUnwrapErrorStringDepthLimit(t *testing.T) { +func TestErrorStringUnwrapDepthLimit(t *testing.T) { // Build a chain deeper than maxRevertErrorDepth (10) data := []byte("leaf") for i := 0; i < maxRevertErrorDepth+2; i++ { data = buildErrorStringABI(append([]byte("L:"), data...)) } - result, ok := ABI{}.UnwrapErrorString(data) + result, ok := ABI{}.ErrorString(data, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) // The chain should be capped — the leaf should not be fully unwrapped @@ -1274,7 +1274,7 @@ func TestUnwrapErrorStringDepthLimit(t *testing.T) { assert.NotEmpty(t, result) } -func TestUnwrapErrorStringCustomBeforeDefaultError(t *testing.T) { +func TestErrorStringUnwrapCustomBeforeDefault(t *testing.T) { customABI := ABI{ {Type: Error, Name: "EarlyErr", Inputs: ParameterArray{{Type: "uint256"}}}, } @@ -1286,7 +1286,7 @@ func TestUnwrapErrorStringCustomBeforeDefaultError(t *testing.T) { s := "head:" + string(customEncoded) + "middle:" + string(innerErrorABI) outerABI := buildErrorStringABI([]byte(s)) - result, ok := customABI.UnwrapErrorString(outerABI) + result, ok := customABI.ErrorString(outerABI, ErrorFormatOption{Unwrap: true}) assert.True(t, ok) assert.Equal(t, `head:EarlyErr("42")`, result) } From 4b81bbba975af03b930d5484beb5f207ef376a68 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 11:15:14 +0100 Subject: [PATCH 19/22] Cap scan length to 1024 ensure that string is long enough to contain an ABI after the matched selector Signed-off-by: Dave Crighton --- pkg/abi/reverterror.go | 30 ++++++++++++++++++++++++++---- pkg/abi/reverterror_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 9fb39442..9cef1a36 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -32,6 +32,17 @@ var defaultErrorEntries = ABI{ const maxRevertErrorDepth = 10 +// maxInnerErrorScanBytes caps how far into a string value we search for an +// embedded ABI selector. Revert strings are measured in tens of bytes in +// practice, so 1024 is already far beyond any realistic prefix length. +const maxInnerErrorScanBytes = 1024 + +// minABIEncodedLen is the minimum byte length for a decodable ABI-encoded +// error: 4 bytes for the selector plus at least one 32-byte word for the +// first parameter. A candidate selector with fewer bytes remaining is +// guaranteed to fail decoding, so it is skipped. +const minABIEncodedLen = 4 + 32 + // RevertError represents a decoded Solidity revert error. For nested errors // (where a contract catches a revert and re-throws with the original error // embedded in the string), the InnerError field links to the inner decoded @@ -112,13 +123,24 @@ func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[[4]byt } } -// findSelector scans raw bytes for the first occurrence of a known 4-byte error selector. +// findSelector scans raw bytes for the first occurrence of a known 4-byte +// error selector. Two constraints are folded into the loop bound: +// - We stop scanning after maxInnerErrorScanBytes, bounding performance on +// large payloads (revert strings are tiny in practice). +// - We only consider positions where at least minABIEncodedLen bytes remain, +// since a selector with fewer bytes after it cannot decode successfully. func findSelector(raw []byte, selectors map[[4]byte]*Entry) (int, *Entry) { - if len(raw) < 4 { - return -1, nil + scanLimit := len(raw) + if scanLimit > maxInnerErrorScanBytes { + scanLimit = maxInnerErrorScanBytes + } + // limit is the highest start index that satisfies both constraints. + limit := scanLimit - 4 + if remainingLimit := len(raw) - minABIEncodedLen; remainingLimit < limit { + limit = remainingLimit } var key [4]byte - for i := 0; i <= len(raw)-4; i++ { + for i := 0; i <= limit; i++ { copy(key[:], raw[i:i+4]) if e, ok := selectors[key]; ok { return i, e diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index ccaf8f80..b0cfb844 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -600,3 +600,34 @@ func TestDecodeRevertErrorNestedSignatures(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Error(string)", innerSig) } + +// --- findSelector constraints --- + +func TestFindSelectorScanCapExceeded(t *testing.T) { + // Place a valid inner error beyond the maxInnerErrorScanBytes boundary. + // It should not be found. + innerABI := buildErrorStringABI([]byte("inner")) + prefix := make([]byte, maxInnerErrorScanBytes) // pushes selector past the cap + outerABI := buildErrorStringABI(append(prefix, innerABI...)) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Nil(t, r.GetInnerError(), "selector beyond scan cap should not be found") +} + +func TestFindSelectorInsufficientBytesAfterSelector(t *testing.T) { + // Build a payload where the selector appears near the end of the string + // with fewer than minABIEncodedLen bytes remaining after it — too short + // to hold a valid ABI encoding. + entry := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} + sel := entry.FunctionSelectorBytes() + + // Append only 3 bytes after the selector — less than the 32-byte minimum word. + truncated := append([]byte(nil), sel...) + truncated = append(truncated, 0x00, 0x00, 0x00) + outerABI := buildErrorStringABI(truncated) + + r := ABI{}.DecodeRevertError(outerABI) + require.NotNil(t, r) + assert.Nil(t, r.GetInnerError(), "selector with insufficient trailing bytes should be skipped") +} From 0c389ecf53851230a1ab1b4f5f23fa1dff4785ad Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 13:55:20 +0100 Subject: [PATCH 20/22] Add tests for assembly revert pattern Signed-off-by: Dave Crighton --- pkg/abi/abi_test.go | 159 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 050533f3..d6cc6f56 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -1291,6 +1291,165 @@ func TestErrorStringUnwrapCustomBeforeDefault(t *testing.T) { assert.Equal(t, `head:EarlyErr("42")`, result) } +// TestErrorStringAssemblyBubbleUp demonstrates that the assembly bubble-up +// pattern produces legible output from ErrorString — with or without Unwrap. +// +// Solidity's catch-and-rethrow via string.concat wraps the inner error bytes +// inside an Error(string), so ErrorString without Unwrap returns unreadable +// binary. The assembly bubble-up pattern: +// +// (bool success, bytes memory result) = target.call(data); +// if (!success) { assembly { revert(add(32, result), mload(result)) } } +// +// passes the raw error bytes through unchanged (no outer wrapper), so the +// error is decoded directly and is legible either way. Unwrap is not required +// but is harmless. +// TestAssemblyBubbleUpRealPayloads uses revert bytes captured from a live +// Solidity deployment on Kaleido (contract AssemblyRevertTest) where each +// bubbleXxx() function catches its inner revert via `catch (bytes memory data)` +// and re-reverts with: +// +// assembly { revert(add(32, data), mload(data)) } +// +// These payloads confirm that the assembly pattern passes bytes through +// unchanged and that DecodeRevertError handles each without a new option. +func TestAssemblyBubbleUpRealPayloads(t *testing.T) { + noParamsEntry := &Entry{Type: Error, Name: "NoParams", Inputs: ParameterArray{}} + insufficientEntry := &Entry{Type: Error, Name: "InsufficientBalance", Inputs: ParameterArray{ + {Name: "available", Type: "uint256"}, + {Name: "required", Type: "uint256"}, + }} + unauthorizedEntry := &Entry{Type: Error, Name: "Unauthorized", Inputs: ParameterArray{ + {Name: "caller", Type: "address"}, + }} + withStringEntry := &Entry{Type: Error, Name: "WithString", Inputs: ParameterArray{ + {Name: "message", Type: "string"}, + }} + customABI := ABI{noParamsEntry, insufficientEntry, unauthorizedEntry, withStringEntry} + + tests := []struct { + name string + hex string + abi ABI + wantStr string + wantSig string + }{ + { + name: "Error(string)", + hex: "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a4e6f7420656e6f7567682045746865722070726f76696465642e000000000000", + abi: ABI{}, + wantStr: `Error("Not enough Ether provided.")`, + wantSig: "Error(string)", + }, + { + name: "Panic(uint256)", + hex: "0x4e487b710000000000000000000000000000000000000000000000000000000000000001", + abi: ABI{}, + wantStr: `Panic("1")`, + wantSig: "Panic(uint256)", + }, + { + name: "NoParams()", + hex: "0xa28f5fc7", + abi: customABI, + wantStr: `NoParams()`, + wantSig: "NoParams()", + }, + { + name: "InsufficientBalance(uint256,uint256)", + hex: "0xcf479181000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000c8", + abi: customABI, + wantStr: `InsufficientBalance("100","200")`, + wantSig: "InsufficientBalance(uint256,uint256)", + }, + { + name: "WithString(string)", + hex: "0x64ed940e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000962616420696e7075740000000000000000000000000000000000000000000000", + abi: customABI, + wantStr: `WithString("bad input")`, + wantSig: "WithString(string)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + revertData := ethtypes.MustNewHexBytes0xPrefix(tt.hex) + + r := tt.abi.DecodeRevertError(revertData) + require.NotNil(t, r) + assert.Nil(t, r.GetInnerError(), "assembly bubble-up should produce no nesting") + assert.Equal(t, tt.wantStr, r.String()) + + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, tt.wantSig, sig) + + // Unwrap option must not change the result for non-nested payloads + result, ok := tt.abi.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + assert.True(t, ok) + assert.Equal(t, tt.wantStr, result) + }) + } +} + +func TestAssemblyBubbleUpUnauthorizedAddress(t *testing.T) { + // Separate test: address formatting needs its own assertion since the + // exact hex representation depends on the serializer's address format. + unauthorizedEntry := &Entry{Type: Error, Name: "Unauthorized", Inputs: ParameterArray{ + {Name: "caller", Type: "address"}, + }} + revertData := ethtypes.MustNewHexBytes0xPrefix( + "0x8e4a23d6000000000000000000000000000000000000000000000000000000000000dead") + + r := ABI{unauthorizedEntry}.DecodeRevertError(revertData) + require.NotNil(t, r) + assert.Nil(t, r.GetInnerError()) + assert.Equal(t, "Unauthorized", r.ErrorEntry.Name) + sig, err := r.Signature() + assert.NoError(t, err) + assert.Equal(t, "Unauthorized(address)", sig) + // Confirm the address value is present in the formatted string + assert.Contains(t, r.String(), "dead") +} + +func TestErrorStringAssemblyBubbleUp(t *testing.T) { + customEntry := &Entry{Type: Error, Name: "InsufficientBalance", Inputs: ParameterArray{ + {Name: "available", Type: "uint256"}, + {Name: "required", Type: "uint256"}, + }} + customABI := ABI{customEntry} + + // Real bytes from eth_call on AssemblyRevertTest.bubbleCustomWithUints() + // deployed on Kaleido — identical to a direct InsufficientBalance(100,200) revert. + rawErrorBytes := ethtypes.MustNewHexBytes0xPrefix( + "0xcf479181" + + "0000000000000000000000000000000000000000000000000000000000000064" + + "00000000000000000000000000000000000000000000000000000000000000c8") + + // Without Unwrap: legible because the raw bytes ARE the error. + result, ok := customABI.ErrorString(rawErrorBytes) + assert.True(t, ok) + assert.Equal(t, `InsufficientBalance("100","200")`, result) + + // With Unwrap: same output — no nesting to unwrap, so result is unchanged. + resultUnwrap, ok := customABI.ErrorString(rawErrorBytes, ErrorFormatOption{Unwrap: true}) + assert.True(t, ok) + assert.Equal(t, result, resultUnwrap) + + // Contrast: string.concat wraps the same error inside Error(string). + // Without Unwrap the string contains raw binary and is not legible. + wrappedBytes := buildErrorStringABI(append([]byte("outer: "), rawErrorBytes...)) + resultWrappedNoUnwrap, ok := customABI.ErrorString(wrappedBytes) + assert.True(t, ok) + assert.False(t, strings.HasPrefix(resultWrappedNoUnwrap, "InsufficientBalance"), + "string.concat wrapping without Unwrap is not legible") + + // With Unwrap the nesting is decoded and the result is legible again. + resultWrappedUnwrap, ok := customABI.ErrorString(wrappedBytes, ErrorFormatOption{Unwrap: true}) + assert.True(t, ok) + assert.Equal(t, `outer: InsufficientBalance("100","200")`, resultWrappedUnwrap) +} + func TestSanitizeBinaryString(t *testing.T) { assert.Equal(t, "", SanitizeBinaryString(nil)) assert.Equal(t, "", SanitizeBinaryString([]byte{})) From 77652c2eeb43a6627e9ebc30e195a886d282c087 Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 14:08:52 +0100 Subject: [PATCH 21/22] Update DecodeRevertErrorCtx to use ErrorFormatOptions Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 2 +- pkg/abi/reverterror.go | 22 ++++++++++++++-------- pkg/abi/reverterror_test.go | 22 +++++++++++----------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 22fd104c..dd447e31 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -397,7 +397,7 @@ func (a ABI) ErrorStringCtx(ctx context.Context, revertData []byte, options ...E unwrap = unwrap || o.Unwrap } if unwrap { - r := a.DecodeRevertErrorCtx(ctx, revertData) + r := a.DecodeRevertErrorCtx(ctx, revertData, ErrorFormatOption{Unwrap: true}) if r == nil { return "", false } diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 9cef1a36..8e871265 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -54,18 +54,24 @@ type RevertError struct { InnerError *RevertError `ffstruct:"RevertError" json:"innerError,omitempty"` // recursively decoded inner error, nil if none } -// DecodeRevertError decodes raw EVM revert data into a RevertError, -// recursively unwrapping nested errors embedded in Error(string) values. +// DecodeRevertError decodes raw EVM revert data into a RevertError. +// Pass ErrorFormatOption{Unwrap: true} to recursively decode nested errors +// embedded in Error(string) values by the Solidity catch-and-rethrow pattern. // Returns nil if the data does not match any known error selector. -func (a ABI) DecodeRevertError(revertData []byte) *RevertError { - return a.DecodeRevertErrorCtx(context.Background(), revertData) +func (a ABI) DecodeRevertError(revertData []byte, options ...ErrorFormatOption) *RevertError { + return a.DecodeRevertErrorCtx(context.Background(), revertData, options...) } -// DecodeRevertErrorCtx decodes raw EVM revert data into a RevertError, -// recursively unwrapping nested errors embedded in Error(string) values. +// DecodeRevertErrorCtx decodes raw EVM revert data into a RevertError. // The ABI's error entries are tried first, followed by the built-in // Error(string) and Panic(uint256). Returns nil if no selector matches. -func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *RevertError { +// Pass ErrorFormatOption{Unwrap: true} to recursively decode nested errors +// embedded in Error(string) values by the Solidity catch-and-rethrow pattern. +func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte, options ...ErrorFormatOption) *RevertError { + unwrap := false + for _, o := range options { + unwrap = unwrap || o.Unwrap + } abiErrors := a.FilterType(Error) for _, source := range []ABI{abiErrors, defaultErrorEntries} { for _, e := range source { @@ -76,7 +82,7 @@ func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte) *Rever // produces Error(string). Custom errors with string/bytes params that // also embed error data are not yet handled since there is a high likelihood // that they are not intended to carry error data. - if e.Name == "Error" && len(cv.Children) == 1 { + if unwrap && e.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { // Build a selector map covering both the caller's error entries // and the built-in defaults, so inner errors of either kind can diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index b0cfb844..1bb3350c 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -451,7 +451,7 @@ func TestDecodeRevertErrorSingleNested(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner error message")) outerABI := buildErrorStringABI(append([]byte("outer: "), innerABI...)) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "outer: ", r.Prefix) @@ -468,7 +468,7 @@ func TestDecodeRevertErrorDoubleNested(t *testing.T) { midABI := buildErrorStringABI(append([]byte("level2: "), deepABI...)) outerABI := buildErrorStringABI(append([]byte("level1: "), midABI...)) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Equal(t, "level1: ", r.Prefix) @@ -493,7 +493,7 @@ func TestDecodeRevertErrorNestedCustomError(t *testing.T) { outerABI := buildErrorStringABI(append([]byte("[404]01d - caught bytes:"), customEncoded...)) - r := ABI{customEntry}.DecodeRevertError(outerABI) + r := ABI{customEntry}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "[404]01d - caught bytes:", r.Prefix) @@ -521,7 +521,7 @@ func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { payload = append(payload, innerErrorABI...) outerABI := buildErrorStringABI(payload) - r := ABI{customEntry}.DecodeRevertError(outerABI) + r := ABI{customEntry}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Equal(t, "head:", r.Prefix) @@ -533,7 +533,7 @@ func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { func TestDecodeRevertErrorNoNesting(t *testing.T) { outerABI := buildErrorStringABI([]byte("plain error with no nesting")) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Equal(t, "", r.Prefix) assert.Nil(t, r.GetInnerError()) @@ -548,7 +548,7 @@ func TestDecodeRevertErrorMalformedNested(t *testing.T) { badData = append(badData, []byte("truncated")...) outerABI := buildErrorStringABI(badData) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Nil(t, r.GetInnerError(), "malformed inner data should not produce an inner error") assert.Equal(t, "", r.Prefix) @@ -561,7 +561,7 @@ func TestDecodeRevertErrorDepthLimit(t *testing.T) { data = buildErrorStringABI(append([]byte("L:"), data...)) } - r := ABI{}.DecodeRevertError(data) + r := ABI{}.DecodeRevertError(data, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) depth := 0 @@ -575,7 +575,7 @@ func TestDecodeRevertErrorInnermostSerializeJSON(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner value")) outerABI := buildErrorStringABI(append([]byte("prefix:"), innerABI...)) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) leaf := r.Innermost() @@ -589,7 +589,7 @@ func TestDecodeRevertErrorNestedSignatures(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner")) outerABI := buildErrorStringABI(append([]byte("prefix:"), innerABI...)) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) outerSig, err := r.Signature() @@ -610,7 +610,7 @@ func TestFindSelectorScanCapExceeded(t *testing.T) { prefix := make([]byte, maxInnerErrorScanBytes) // pushes selector past the cap outerABI := buildErrorStringABI(append(prefix, innerABI...)) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Nil(t, r.GetInnerError(), "selector beyond scan cap should not be found") } @@ -627,7 +627,7 @@ func TestFindSelectorInsufficientBytesAfterSelector(t *testing.T) { truncated = append(truncated, 0x00, 0x00, 0x00) outerABI := buildErrorStringABI(truncated) - r := ABI{}.DecodeRevertError(outerABI) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) require.NotNil(t, r) assert.Nil(t, r.GetInnerError(), "selector with insufficient trailing bytes should be skipped") } From 26efec7ad89b5d8e9497c2c5d80444a71b1fa1cd Mon Sep 17 00:00:00 2001 From: Dave Crighton Date: Tue, 21 Apr 2026 16:45:58 +0100 Subject: [PATCH 22/22] Rename option and update description Signed-off-by: Dave Crighton --- pkg/abi/abi.go | 27 ++++++++-------- pkg/abi/abi_test.go | 62 ++++++++++++++++++------------------- pkg/abi/reverterror.go | 38 +++++++++++------------ pkg/abi/reverterror_test.go | 24 +++++++------- 4 files changed, 74 insertions(+), 77 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index dd447e31..db184b44 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -372,16 +372,17 @@ func (a ABI) ParseErrorCtx(ctx context.Context, revertData []byte) (*Entry, *Com // ErrorFormatOption configures the behaviour of ErrorString and ErrorStringCtx. type ErrorFormatOption struct { - // Unwrap causes the full inner error chain to be decoded and formatted, - // handling the Solidity catch-and-rethrow pattern where a contract embeds - // a caught revert inside a new Error(string) via string.concat/string(reason). - // Without this option only the outermost error is formatted. - Unwrap bool + // There is a pattern used in some smart contracts, where a string error is used + // to embed the raw bytes of an ABI encoded sub-error. + // While uncommon, this pattern is supported by the library if you set this switch. + // When set, every standard revert `Error(string)` error will be traversed to look for + // eyecatcher (4 byte signatures) of other errors binary encoded within the string. + SearchForWrappedBinaryErrors bool } // ErrorString formats raw EVM revert data as a human-readable string. -// Pass ErrorFormatOption{Unwrap: true} to recursively decode inner errors -// produced by Solidity catch-and-rethrow patterns. +// Pass ErrorFormatOption{SearchForWrappedBinaryErrors: true} to decode inner errors +// that are binary-encoded within an Error(string) value. func (a ABI) ErrorString(revertData []byte, options ...ErrorFormatOption) (string, bool) { return a.ErrorStringCtx(context.Background(), revertData, options...) } @@ -389,15 +390,15 @@ func (a ABI) ErrorString(revertData []byte, options ...ErrorFormatOption) (strin // ErrorStringCtx formats raw EVM revert data as a human-readable string. // The ABI's own error entries are tried first, followed by the built-in // Error(string) and Panic(uint256). -// Pass ErrorFormatOption{Unwrap: true} to recursively decode inner errors -// produced by Solidity catch-and-rethrow patterns. +// Pass ErrorFormatOption{SearchForWrappedBinaryErrors: true} to decode inner errors +// that are binary-encoded within an Error(string) value. func (a ABI) ErrorStringCtx(ctx context.Context, revertData []byte, options ...ErrorFormatOption) (string, bool) { - unwrap := false + searchBinary := false for _, o := range options { - unwrap = unwrap || o.Unwrap + searchBinary = searchBinary || o.SearchForWrappedBinaryErrors } - if unwrap { - r := a.DecodeRevertErrorCtx(ctx, revertData, ErrorFormatOption{Unwrap: true}) + if searchBinary { + r := a.DecodeRevertErrorCtx(ctx, revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) if r == nil { return "", false } diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index d6cc6f56..cc793bee 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -1162,19 +1162,19 @@ func buildErrorStringABI(msgBytes []byte) []byte { return data } -func TestErrorStringUnwrapPlainError(t *testing.T) { +func TestErrorStringBinaryWrappedPlainError(t *testing.T) { revertData := ethtypes.MustNewHexBytes0xPrefix( "0x08c379a0" + "0000000000000000000000000000000000000000000000000000000000000020" + "000000000000000000000000000000000000000000000000000000000000001a" + "4e6f7420656e6f7567682045746865722070726f76696465642e000000000000") - result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, `Error("Not enough Ether provided.")`, result) } -func TestErrorStringUnwrapSingleNested(t *testing.T) { +func TestErrorStringBinaryWrappedSingleNested(t *testing.T) { revertData := ethtypes.MustNewHexBytes0xPrefix( "0x08c379a00000000000000000000000000000000000000000000000000000000000000020" + "000000000000000000000000000000000000000000000000000000000000006b" + @@ -1185,12 +1185,12 @@ func TestErrorStringUnwrapSingleNested(t *testing.T) { "696e6e6572206572726f72206d65737361676500000000000000000000000000" + "000000000000000000000000000000000000000000") - result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, `outer: Error("inner error message")`, result) } -func TestErrorStringUnwrapDoubleNested(t *testing.T) { +func TestErrorStringBinaryWrappedDoubleNested(t *testing.T) { revertData := ethtypes.MustNewHexBytes0xPrefix( "0x08c379a0" + "0000000000000000000000000000000000000000000000000000000000000020" + @@ -1205,12 +1205,12 @@ func TestErrorStringUnwrapDoubleNested(t *testing.T) { "000000000000000000000000000000000000000000000000000000000000000d" + "64656570657374206572726f720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, `level1: level2: Error("deepest error")`, result) } -func TestErrorStringUnwrapNestedCustomError(t *testing.T) { +func TestErrorStringBinaryWrappedNestedCustomError(t *testing.T) { customABI := ABI{ {Type: Error, Name: "MyCustomError", Inputs: ParameterArray{{Type: "bytes"}}}, } @@ -1229,23 +1229,23 @@ func TestErrorStringUnwrapNestedCustomError(t *testing.T) { // Without the custom ABI the inner error can't be decoded — the // outer Error(string) is formatted directly (binary content included) - result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + result, ok := ABI{}.ErrorString(revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.True(t, strings.HasPrefix(result, `Error("[404]01d`)) // With the custom ABI the inner error is decoded - result, ok = customABI.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + result, ok = customABI.ErrorString(revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, `[404]01d - caught bytes:MyCustomError("0xdeadbeef")`, result) } -func TestErrorStringUnwrapUnknownSelector(t *testing.T) { +func TestErrorStringBinaryWrappedUnknownSelector(t *testing.T) { // Unknown top-level selector - _, ok := ABI{}.ErrorString([]byte{0x11, 0x22, 0x33, 0x44}, ErrorFormatOption{Unwrap: true}) + _, ok := ABI{}.ErrorString([]byte{0x11, 0x22, 0x33, 0x44}, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.False(t, ok) } -func TestErrorStringUnwrapMalformedInnerABI(t *testing.T) { +func TestErrorStringBinaryWrappedMalformedInner(t *testing.T) { defaultErr := &Entry{Type: Error, Name: "Error", Inputs: ParameterArray{{Name: "reason", Type: "string"}}} sel := defaultErr.FunctionSelectorBytes() @@ -1254,19 +1254,19 @@ func TestErrorStringUnwrapMalformedInnerABI(t *testing.T) { // Malformed inner data can't be decoded, so the outer Error(string) // is formatted directly with the raw string content - result, ok := ABI{}.ErrorString(outerABI, ErrorFormatOption{Unwrap: true}) + result, ok := ABI{}.ErrorString(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.True(t, strings.HasPrefix(result, "Error(")) } -func TestErrorStringUnwrapDepthLimit(t *testing.T) { +func TestErrorStringBinaryWrappedDepthLimit(t *testing.T) { // Build a chain deeper than maxRevertErrorDepth (10) data := []byte("leaf") for i := 0; i < maxRevertErrorDepth+2; i++ { data = buildErrorStringABI(append([]byte("L:"), data...)) } - result, ok := ABI{}.ErrorString(data, ErrorFormatOption{Unwrap: true}) + result, ok := ABI{}.ErrorString(data, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) // The chain should be capped — the leaf should not be fully unwrapped @@ -1274,7 +1274,7 @@ func TestErrorStringUnwrapDepthLimit(t *testing.T) { assert.NotEmpty(t, result) } -func TestErrorStringUnwrapCustomBeforeDefault(t *testing.T) { +func TestErrorStringBinaryWrappedCustomBeforeDefault(t *testing.T) { customABI := ABI{ {Type: Error, Name: "EarlyErr", Inputs: ParameterArray{{Type: "uint256"}}}, } @@ -1286,24 +1286,22 @@ func TestErrorStringUnwrapCustomBeforeDefault(t *testing.T) { s := "head:" + string(customEncoded) + "middle:" + string(innerErrorABI) outerABI := buildErrorStringABI([]byte(s)) - result, ok := customABI.ErrorString(outerABI, ErrorFormatOption{Unwrap: true}) + result, ok := customABI.ErrorString(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, `head:EarlyErr("42")`, result) } // TestErrorStringAssemblyBubbleUp demonstrates that the assembly bubble-up -// pattern produces legible output from ErrorString — with or without Unwrap. +// pattern produces legible output from ErrorString — with or without SearchForWrappedBinaryErrors. // -// Solidity's catch-and-rethrow via string.concat wraps the inner error bytes -// inside an Error(string), so ErrorString without Unwrap returns unreadable -// binary. The assembly bubble-up pattern: +// The assembly bubble-up pattern: // // (bool success, bytes memory result) = target.call(data); // if (!success) { assembly { revert(add(32, result), mload(result)) } } // // passes the raw error bytes through unchanged (no outer wrapper), so the -// error is decoded directly and is legible either way. Unwrap is not required -// but is harmless. +// error is decoded directly and is legible either way. SearchForWrappedBinaryErrors +// is not required but is harmless. // TestAssemblyBubbleUpRealPayloads uses revert bytes captured from a live // Solidity deployment on Kaleido (contract AssemblyRevertTest) where each // bubbleXxx() function catches its inner revert via `catch (bytes memory data)` @@ -1384,8 +1382,8 @@ func TestAssemblyBubbleUpRealPayloads(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tt.wantSig, sig) - // Unwrap option must not change the result for non-nested payloads - result, ok := tt.abi.ErrorString(revertData, ErrorFormatOption{Unwrap: true}) + // SearchForWrappedBinaryErrors must not change the result for non-nested payloads + result, ok := tt.abi.ErrorString(revertData, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, tt.wantStr, result) }) @@ -1431,21 +1429,21 @@ func TestErrorStringAssemblyBubbleUp(t *testing.T) { assert.True(t, ok) assert.Equal(t, `InsufficientBalance("100","200")`, result) - // With Unwrap: same output — no nesting to unwrap, so result is unchanged. - resultUnwrap, ok := customABI.ErrorString(rawErrorBytes, ErrorFormatOption{Unwrap: true}) + // With SearchForWrappedBinaryErrors: same output — no nesting, so result is unchanged. + resultUnwrap, ok := customABI.ErrorString(rawErrorBytes, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, result, resultUnwrap) - // Contrast: string.concat wraps the same error inside Error(string). - // Without Unwrap the string contains raw binary and is not legible. + // Contrast: wrapping the same error inside Error(string) embeds binary in the string. + // Without SearchForWrappedBinaryErrors the string contains raw binary and is not legible. wrappedBytes := buildErrorStringABI(append([]byte("outer: "), rawErrorBytes...)) resultWrappedNoUnwrap, ok := customABI.ErrorString(wrappedBytes) assert.True(t, ok) assert.False(t, strings.HasPrefix(resultWrappedNoUnwrap, "InsufficientBalance"), - "string.concat wrapping without Unwrap is not legible") + "binary-wrapped error without SearchForWrappedBinaryErrors is not legible") - // With Unwrap the nesting is decoded and the result is legible again. - resultWrappedUnwrap, ok := customABI.ErrorString(wrappedBytes, ErrorFormatOption{Unwrap: true}) + // With SearchForWrappedBinaryErrors the inner error is decoded and the result is legible. + resultWrappedUnwrap, ok := customABI.ErrorString(wrappedBytes, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) assert.True(t, ok) assert.Equal(t, `outer: InsufficientBalance("100","200")`, resultWrappedUnwrap) } diff --git a/pkg/abi/reverterror.go b/pkg/abi/reverterror.go index 8e871265..2da3a000 100644 --- a/pkg/abi/reverterror.go +++ b/pkg/abi/reverterror.go @@ -55,8 +55,8 @@ type RevertError struct { } // DecodeRevertError decodes raw EVM revert data into a RevertError. -// Pass ErrorFormatOption{Unwrap: true} to recursively decode nested errors -// embedded in Error(string) values by the Solidity catch-and-rethrow pattern. +// Pass ErrorFormatOption{SearchForWrappedBinaryErrors: true} to scan for inner errors +// binary-encoded within an Error(string) value. // Returns nil if the data does not match any known error selector. func (a ABI) DecodeRevertError(revertData []byte, options ...ErrorFormatOption) *RevertError { return a.DecodeRevertErrorCtx(context.Background(), revertData, options...) @@ -65,30 +65,28 @@ func (a ABI) DecodeRevertError(revertData []byte, options ...ErrorFormatOption) // DecodeRevertErrorCtx decodes raw EVM revert data into a RevertError. // The ABI's error entries are tried first, followed by the built-in // Error(string) and Panic(uint256). Returns nil if no selector matches. -// Pass ErrorFormatOption{Unwrap: true} to recursively decode nested errors -// embedded in Error(string) values by the Solidity catch-and-rethrow pattern. +// Pass ErrorFormatOption{SearchForWrappedBinaryErrors: true} to scan for inner errors +// binary-encoded within an Error(string) value. func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte, options ...ErrorFormatOption) *RevertError { - unwrap := false + searchBinary := false for _, o := range options { - unwrap = unwrap || o.Unwrap + searchBinary = searchBinary || o.SearchForWrappedBinaryErrors } abiErrors := a.FilterType(Error) for _, source := range []ABI{abiErrors, defaultErrorEntries} { for _, e := range source { if cv, err := e.DecodeCallDataCtx(ctx, revertData); err == nil { r := &RevertError{ErrorEntry: e, cv: cv} - // Only Error(string) is unwrapped for inner errors, because the Solidity - // catch-and-rethrow pattern (string.concat + string(reason)) always - // produces Error(string). Custom errors with string/bytes params that - // also embed error data are not yet handled since there is a high likelihood - // that they are not intended to carry error data. - if unwrap && e.Name == "Error" && len(cv.Children) == 1 { + // Only Error(string) is scanned for binary-wrapped inner errors. + // Custom errors with string/bytes params are not scanned since there + // is a high likelihood that they are not intended to carry error data. + if searchBinary && e.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { // Build a selector map covering both the caller's error entries // and the built-in defaults, so inner errors of either kind can // be recognised during recursive unwrapping. selectors := append(abiErrors, defaultErrorEntries...).SelectorMap() - r.unwrapInnerError(ctx, selectors, strVal, 0) + r.scanForBinaryWrappedError(ctx, selectors, strVal, 0) } } return r @@ -98,9 +96,9 @@ func (a ABI) DecodeRevertErrorCtx(ctx context.Context, revertData []byte, option return nil } -// unwrapInnerError scans a decoded string value for an embedded ABI error selector. +// scanForBinaryWrappedError scans a decoded string value for an embedded ABI error selector. // If found, it populates r.Prefix and r.InnerError to form the recursive chain. -func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[[4]byte]*Entry, s string, depth int) { +func (r *RevertError) scanForBinaryWrappedError(ctx context.Context, selectors map[[4]byte]*Entry, s string, depth int) { if depth >= maxRevertErrorDepth { return } @@ -121,10 +119,10 @@ func (r *RevertError) unwrapInnerError(ctx context.Context, selectors map[[4]byt r.Prefix = SanitizeBinaryString(raw[:idx]) r.InnerError = inner - // If the inner error is also Error(string), keep unwrapping + // If the inner error is also Error(string), keep scanning recursively if entry.Name == "Error" && len(cv.Children) == 1 { if strVal, ok := cv.Children[0].Value.(string); ok { - inner.unwrapInnerError(ctx, selectors, strVal, depth+1) + inner.scanForBinaryWrappedError(ctx, selectors, strVal, depth+1) } } } @@ -174,8 +172,8 @@ func (r *RevertError) String() string { // ErrorString returns the formatted error at this level only, e.g. // Error("not enough funds") or MyCustomError("0x1234","-100"). -// Unlike String(), it does not walk the Cause chain — use it when -// you need the single-level description without recursive unwrapping. +// Unlike String(), it does not walk the chain — use it when +// you need the single-level description. func (r *RevertError) ErrorString() string { if r == nil { return "" @@ -214,7 +212,7 @@ func (r *RevertError) GetInnerError() *RevertError { } // Innermost walks the chain to return the deepest RevertError — the -// original error that triggered the chain of catch-and-rethrow wrappers. +// innermost binary-wrapped error. func (r *RevertError) Innermost() *RevertError { if r == nil { return nil diff --git a/pkg/abi/reverterror_test.go b/pkg/abi/reverterror_test.go index 1bb3350c..f989961f 100644 --- a/pkg/abi/reverterror_test.go +++ b/pkg/abi/reverterror_test.go @@ -445,13 +445,13 @@ func testEncodeError(t *testing.T, entry *Entry, jsonArgs string) []byte { return encoded } -// --- InnerError unwrapping (DecodeRevertError with nesting) --- +// --- Binary-wrapped inner errors (DecodeRevertError with SearchForWrappedBinaryErrors) --- func TestDecodeRevertErrorSingleNested(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner error message")) outerABI := buildErrorStringABI(append([]byte("outer: "), innerABI...)) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "outer: ", r.Prefix) @@ -468,7 +468,7 @@ func TestDecodeRevertErrorDoubleNested(t *testing.T) { midABI := buildErrorStringABI(append([]byte("level2: "), deepABI...)) outerABI := buildErrorStringABI(append([]byte("level1: "), midABI...)) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Equal(t, "level1: ", r.Prefix) @@ -493,7 +493,7 @@ func TestDecodeRevertErrorNestedCustomError(t *testing.T) { outerABI := buildErrorStringABI(append([]byte("[404]01d - caught bytes:"), customEncoded...)) - r := ABI{customEntry}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{customEntry}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Equal(t, "Error", r.ErrorEntry.Name) assert.Equal(t, "[404]01d - caught bytes:", r.Prefix) @@ -521,7 +521,7 @@ func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { payload = append(payload, innerErrorABI...) outerABI := buildErrorStringABI(payload) - r := ABI{customEntry}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{customEntry}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Equal(t, "head:", r.Prefix) @@ -533,7 +533,7 @@ func TestDecodeRevertErrorCustomBeforeDefaultNested(t *testing.T) { func TestDecodeRevertErrorNoNesting(t *testing.T) { outerABI := buildErrorStringABI([]byte("plain error with no nesting")) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Equal(t, "", r.Prefix) assert.Nil(t, r.GetInnerError()) @@ -548,7 +548,7 @@ func TestDecodeRevertErrorMalformedNested(t *testing.T) { badData = append(badData, []byte("truncated")...) outerABI := buildErrorStringABI(badData) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Nil(t, r.GetInnerError(), "malformed inner data should not produce an inner error") assert.Equal(t, "", r.Prefix) @@ -561,7 +561,7 @@ func TestDecodeRevertErrorDepthLimit(t *testing.T) { data = buildErrorStringABI(append([]byte("L:"), data...)) } - r := ABI{}.DecodeRevertError(data, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(data, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) depth := 0 @@ -575,7 +575,7 @@ func TestDecodeRevertErrorInnermostSerializeJSON(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner value")) outerABI := buildErrorStringABI(append([]byte("prefix:"), innerABI...)) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) leaf := r.Innermost() @@ -589,7 +589,7 @@ func TestDecodeRevertErrorNestedSignatures(t *testing.T) { innerABI := buildErrorStringABI([]byte("inner")) outerABI := buildErrorStringABI(append([]byte("prefix:"), innerABI...)) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) outerSig, err := r.Signature() @@ -610,7 +610,7 @@ func TestFindSelectorScanCapExceeded(t *testing.T) { prefix := make([]byte, maxInnerErrorScanBytes) // pushes selector past the cap outerABI := buildErrorStringABI(append(prefix, innerABI...)) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Nil(t, r.GetInnerError(), "selector beyond scan cap should not be found") } @@ -627,7 +627,7 @@ func TestFindSelectorInsufficientBytesAfterSelector(t *testing.T) { truncated = append(truncated, 0x00, 0x00, 0x00) outerABI := buildErrorStringABI(truncated) - r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{Unwrap: true}) + r := ABI{}.DecodeRevertError(outerABI, ErrorFormatOption{SearchForWrappedBinaryErrors: true}) require.NotNil(t, r) assert.Nil(t, r.GetInnerError(), "selector with insufficient trailing bytes should be skipped") }