-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathfunc.go
More file actions
134 lines (118 loc) · 4.07 KB
/
func.go
File metadata and controls
134 lines (118 loc) · 4.07 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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package w3
import (
"bytes"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/crypto"
_abi "github.com/lmittmann/w3/internal/abi"
)
var (
ErrInvalidABI = errors.New("w3: invalid ABI")
ErrArgumentMismatch = errors.New("w3: argument mismatch")
ErrReturnsMismatch = errors.New("w3: returns mismatch")
ErrInvalidType = errors.New("w3: invalid type")
ErrEvmRevert = errors.New("w3: evm reverted")
revertSelector = selector("Error(string)")
outputSuccess = B("0x0000000000000000000000000000000000000000000000000000000000000001")
approveSelector = selector("approve(address,uint256)")
transferSelector = selector("transfer(address,uint256)")
transferFromSelector = selector("transferFrom(address,address,uint256)")
)
// Func represents a Smart Contract function ABI binding.
//
// Func implements the [w3types.Func] interface.
type Func struct {
Signature string // Function signature
Selector [4]byte // 4-byte selector
Args abi.Arguments // Arguments (input)
Returns abi.Arguments // Returns (output)
name string // Function name
}
// NewFunc returns a new Smart Contract function ABI binding from the given
// Solidity function signature and its returns.
//
// The optional tuples parameter accepts struct definitions that can be
// referenced by name in the signature instead of using inline tuple
// definitions. This enables cleaner, more readable function signatures when
// working with complex tuple types.
//
// Examples:
//
// // Without named tuples (inline)
// NewFunc("transfer((address,uint256))", "bool")
//
// // With named tuple
// type Token struct { To common.Address; Amount *big.Int }
// NewFunc("transfer(Token)", "bool", Token{})
//
// An error is returned if the signature or returns parsing fails.
func NewFunc(signature, returns string, tuples ...any) (*Func, error) {
name, args, err := _abi.ParseWithName(signature, tuples...)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err)
}
if name == "" {
return nil, fmt.Errorf("%w: missing function name", ErrInvalidABI)
}
returnArgs, err := _abi.Parse(returns, tuples...)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err)
}
sig := args.SignatureWithName(name)
return &Func{
Signature: sig,
Selector: selector(sig),
Args: abi.Arguments(args),
Returns: abi.Arguments(returnArgs),
name: name,
}, nil
}
// MustNewFunc is like [NewFunc] but panics if the signature or returns parsing
// fails.
func MustNewFunc(signature, returns string, tuples ...any) *Func {
fn, err := NewFunc(signature, returns, tuples...)
if err != nil {
panic(err)
}
return fn
}
// EncodeArgs ABI-encodes the given args and prepends the Func's 4-byte
// selector.
func (f *Func) EncodeArgs(args ...any) ([]byte, error) {
return _abi.Arguments(f.Args).EncodeWithSelector(f.Selector, args...)
}
// DecodeArgs ABI-decodes the given input to the given args.
func (f *Func) DecodeArgs(input []byte, args ...any) error {
if len(input) < 4 {
return errors.New("w3: insufficient input length")
}
if !bytes.Equal(input[:4], f.Selector[:]) {
return errors.New("w3: input does not match selector")
}
return _abi.Arguments(f.Args).Decode(input[4:], args...)
}
// DecodeReturns ABI-decodes the given output to the given returns.
func (f *Func) DecodeReturns(output []byte, returns ...any) error {
// check the output for a revert reason
if bytes.HasPrefix(output, revertSelector[:]) {
if reason, err := abi.UnpackRevert(output); err != nil {
return err
} else {
return fmt.Errorf("%w: %s", ErrEvmRevert, reason)
}
}
// Gracefully handle uncompliant ERC20 returns
if len(returns) == 1 && len(output) == 0 &&
(f.Selector == approveSelector ||
f.Selector == transferSelector ||
f.Selector == transferFromSelector) {
output = outputSuccess
}
return _abi.Arguments(f.Returns).Decode(output, returns...)
}
// selector returns the 4-byte selector of the given signature.
func selector(signature string) (selector [4]byte) {
copy(selector[:], crypto.Keccak256([]byte(signature)))
return
}