-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
106 lines (92 loc) · 2.64 KB
/
Copy patherrors.go
File metadata and controls
106 lines (92 loc) · 2.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package multicall
import (
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
)
// Standard error selectors as hex constants for readability
const (
// Error(string) selector
errorSelector = 0x08c379a0
// Panic(uint256) selector
panicSelector = 0x4e487b71
)
var (
// ErrResultMissing result count does not match call count
ErrResultMissing = errors.New("multicall: result missing from response")
)
// CallFailedError error for individual call failure, includes revert reason (if any)
type CallFailedError struct {
Reason string // Decoded revert reason
Data []byte // Raw return data
}
func (e *CallFailedError) Error() string {
if e.Reason != "" {
return fmt.Sprintf("multicall: call failed: %s", e.Reason)
}
return "multicall: call failed"
}
// newCallFailedError creates CallFailedError, attempts to decode revert reason
func newCallFailedError(data []byte) *CallFailedError {
reason := decodeRevertReason(data)
return &CallFailedError{
Reason: reason,
Data: data,
}
}
// decodeRevertReason decodes revert reason from error return data
// Supports both Error(string) and Panic(uint256) standard formats
func decodeRevertReason(data []byte) string {
if len(data) < 4 {
return ""
}
selector := binary.BigEndian.Uint32(data[:4])
switch selector {
case errorSelector:
return decodeErrorString(data[4:])
case panicSelector:
return decodePanicCode(data[4:])
default:
if len(data) == 4 {
return fmt.Sprintf("custom error: 0x%08x", selector)
}
return fmt.Sprintf("custom error: 0x%08x (with data)", selector)
}
}
// decodeErrorString decodes Error(string) revert data
func decodeErrorString(data []byte) string {
stringType, _ := abi.NewType("string", "string", nil)
args := abi.Arguments{{Type: stringType}}
decoded, err := args.Unpack(data)
if err != nil || len(decoded) == 0 {
return ""
}
if reason, ok := decoded[0].(string); ok {
return reason
}
return ""
}
// decodePanicCode decodes Panic(uint256) revert data
func decodePanicCode(data []byte) string {
uintType, _ := abi.NewType("uint256", "uint256", nil)
args := abi.Arguments{{Type: uintType}}
decoded, err := args.Unpack(data)
if err != nil || len(decoded) == 0 {
return "panic: unknown"
}
return fmt.Sprintf("panic code: %d", decoded[0])
}
// IsCallFailed checks if error is an individual call failure
func IsCallFailed(err error) bool {
var callErr *CallFailedError
return errors.As(err, &callErr)
}
// RevertReason extracts revert reason from error, returns empty string if not CallFailedError
func RevertReason(err error) string {
var callErr *CallFailedError
if errors.As(err, &callErr) {
return callErr.Reason
}
return ""
}