1717package ethereum
1818
1919import (
20- "bytes"
2120 "context"
22- "encoding/hex"
2321 "encoding/json"
24- "fmt"
2522
2623 "github.com/hyperledger/firefly-common/pkg/fftypes"
2724 "github.com/hyperledger/firefly-common/pkg/i18n"
@@ -35,21 +32,6 @@ import (
3532 "github.com/hyperledger/firefly-transaction-manager/pkg/ffcapi"
3633)
3734
38- var (
39- // See https://docs.soliditylang.org/en/v0.8.14/control-structures.html#revert
40- // There default error for `revert("some error")` is a function Error(string)
41- defaultError = & abi.Entry {
42- Type : abi .Error ,
43- Name : "Error" ,
44- Inputs : abi.ParameterArray {
45- {
46- Type : "string" ,
47- },
48- },
49- }
50- defaultErrorID = defaultError .FunctionSelectorBytes ()
51- )
52-
5335func (c * ethConnector ) QueryInvoke (ctx context.Context , req * ffcapi.QueryInvokeRequest ) (* ffcapi.QueryInvokeResponse , ffcapi.ErrorReason , error ) {
5436 // Parse the input JSON data, to build the call data
5537 callData , method , err := c .prepareCallData (ctx , & req .TransactionInput )
@@ -165,126 +147,15 @@ func processRevertReason(ctx context.Context, outputData ethtypes.HexBytes0xPref
165147 // result in a multiple of 32 bytes) and has exactly 4 extra bytes for a function
166148 // signature
167149 if len (outputData )% 32 == 4 {
168- signature := outputData [0 :4 ]
169- if bytes .Equal (signature , defaultErrorID ) {
170- errorInfo , err := defaultError .DecodeCallDataCtx (ctx , outputData )
171- if err == nil && len (errorInfo .Children ) == 1 {
172- if strError , ok := errorInfo .Children [0 ].Value .(string ); ok {
173- return unwrapNestedRevertReasons (ctx , strError , 0 , errorAbis )
174- }
175- }
176- log .L (ctx ).Warnf ("Invalid revert data: %s" , outputData )
177- } else if len (errorAbis ) > 0 {
178- // check if the signature matches any of the declared custom error definitions
179- for _ , e := range errorAbis {
180- idBytes := e .FunctionSelectorBytes ()
181- if bytes .Equal (signature , idBytes ) {
182- err := formatCustomError (ctx , e , outputData )
183- if err == "" {
184- log .L (ctx ).Warnf ("Invalid revert data: %s" , outputData )
185- break
186- }
187- return err
188- }
189- }
150+ var errors abi.ABI
151+ for _ , e := range errorAbis {
152+ errors = append (errors , e )
153+ }
154+ if result , ok := errors .UnwrapErrorStringCtx (ctx , outputData ); ok {
155+ return result
190156 }
191- // we call this "transient error" because it signals to the caller of the case
192- // that the raw revert data is returned, then it gets thrown away. so no need to translate
193157 log .L (ctx ).Debugf ("Directly returning revert reason: %s" , outputData )
194158 return outputData .String ()
195159 }
196160 return ""
197161}
198-
199- const maxNestedRevertDepth = 10
200-
201- // unwrapNestedRevertReasons handles Solidity contracts that catch a revert's raw bytes
202- // and re-throw them inside a new Error(string) by doing string(reason). This produces
203- // an Error(string) whose decoded "string" contains raw ABI-encoded error data
204- // (including null bytes from ABI padding). We scan for all known error selectors
205- // (Error(string) plus any custom errors from the ABI), decode the earliest match,
206- // and recurse for nested Error(string) chains.
207- func unwrapNestedRevertReasons (ctx context.Context , s string , depth int , errorAbis []* abi.Entry ) string {
208- if depth >= maxNestedRevertDepth {
209- return sanitizeBinaryString ([]byte (s ))
210- }
211-
212- raw := []byte (s )
213-
214- // Find the earliest occurrence of any known error selector
215- bestIdx := - 1
216- var bestEntry * abi.Entry
217- if idx := bytes .Index (raw , defaultErrorID ); idx >= 0 {
218- bestIdx = idx
219- bestEntry = defaultError
220- }
221- for _ , e := range errorAbis {
222- sel := e .FunctionSelectorBytes ()
223- if idx := bytes .Index (raw , sel ); idx >= 0 && (bestIdx < 0 || idx < bestIdx ) {
224- bestIdx = idx
225- bestEntry = e
226- }
227- }
228-
229- if bestIdx < 0 {
230- return sanitizeBinaryString (raw )
231- }
232-
233- prefix := sanitizeBinaryString (raw [:bestIdx ])
234- embedded := raw [bestIdx :]
235-
236- if bestEntry == defaultError {
237- errorInfo , err := defaultError .DecodeCallDataCtx (ctx , embedded )
238- if err == nil && len (errorInfo .Children ) == 1 {
239- if nested , ok := errorInfo .Children [0 ].Value .(string ); ok {
240- return prefix + unwrapNestedRevertReasons (ctx , nested , depth + 1 , errorAbis )
241- }
242- }
243- } else {
244- formatted := formatCustomError (ctx , bestEntry , embedded )
245- if formatted != "" {
246- return prefix + formatted
247- }
248- }
249-
250- log .L (ctx ).Debugf ("Could not decode nested revert at depth %d, hex-encoding remaining %d bytes" , depth , len (embedded ))
251- return prefix + "0x" + hex .EncodeToString (embedded )
252- }
253-
254- // sanitizeBinaryString returns the input as a text string if it is entirely
255- // printable ASCII, or hex-encodes the entire input otherwise. This all-or-nothing
256- // approach avoids guessing where "readable" ends in an ambiguous binary blob.
257- func sanitizeBinaryString (raw []byte ) string {
258- for _ , b := range raw {
259- if b < 32 || b >= 127 {
260- return "0x" + hex .EncodeToString (raw )
261- }
262- }
263- return string (raw )
264- }
265-
266- func formatCustomError (ctx context.Context , e * abi.Entry , outputData ethtypes.HexBytes0xPrefix ) string {
267- errorInfo , err := e .DecodeCallDataCtx (ctx , outputData )
268- if err == nil {
269- strError := fmt .Sprintf ("%s(" , e .Name )
270- for i , child := range errorInfo .Children {
271- strError += formatErrorComponent (ctx , child )
272- if i < len (errorInfo .Children )- 1 {
273- strError += ", "
274- }
275- }
276- strError += ")"
277- return strError
278- }
279- return ""
280- }
281-
282- func formatErrorComponent (ctx context.Context , child * abi.ComponentValue ) string {
283- value , err := child .JSON ()
284- if err != nil {
285- // if this part of the error structure failed to parse, simply append "?"
286- log .L (ctx ).Warnf ("Failed to parse component value in error: %+v" , child )
287- return "?"
288- }
289- return string (value )
290- }
0 commit comments