github.com/lmittmann/w3@v0.20.0/func.go (about) 1 package w3 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 8 "github.com/ethereum/go-ethereum/accounts/abi" 9 "github.com/ethereum/go-ethereum/crypto" 10 _abi "github.com/lmittmann/w3/internal/abi" 11 ) 12 13 var ( 14 ErrInvalidABI = errors.New("w3: invalid ABI") 15 ErrArgumentMismatch = errors.New("w3: argument mismatch") 16 ErrReturnsMismatch = errors.New("w3: returns mismatch") 17 ErrInvalidType = errors.New("w3: invalid type") 18 ErrEvmRevert = errors.New("w3: evm reverted") 19 20 revertSelector = selector("Error(string)") 21 outputSuccess = B("0x0000000000000000000000000000000000000000000000000000000000000001") 22 approveSelector = selector("approve(address,uint256)") 23 transferSelector = selector("transfer(address,uint256)") 24 transferFromSelector = selector("transferFrom(address,address,uint256)") 25 ) 26 27 // Func represents a Smart Contract function ABI binding. 28 // 29 // Func implements the [w3types.Func] interface. 30 type Func struct { 31 Signature string // Function signature 32 Selector [4]byte // 4-byte selector 33 Args abi.Arguments // Arguments (input) 34 Returns abi.Arguments // Returns (output) 35 36 name string // Function name 37 } 38 39 // NewFunc returns a new Smart Contract function ABI binding from the given 40 // Solidity function signature and its returns. 41 // 42 // The optional tuples parameter accepts struct definitions that can be 43 // referenced by name in the signature instead of using inline tuple 44 // definitions. This enables cleaner, more readable function signatures when 45 // working with complex tuple types. 46 // 47 // Examples: 48 // 49 // // Without named tuples (inline) 50 // NewFunc("transfer((address,uint256))", "bool") 51 // 52 // // With named tuple 53 // type Token struct { To common.Address; Amount *big.Int } 54 // NewFunc("transfer(Token)", "bool", Token{}) 55 // 56 // An error is returned if the signature or returns parsing fails. 57 func NewFunc(signature, returns string, tuples ...any) (*Func, error) { 58 name, args, err := _abi.ParseWithName(signature, tuples...) 59 if err != nil { 60 return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err) 61 } 62 if name == "" { 63 return nil, fmt.Errorf("%w: missing function name", ErrInvalidABI) 64 } 65 66 returnArgs, err := _abi.Parse(returns, tuples...) 67 if err != nil { 68 return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err) 69 } 70 71 sig := args.SignatureWithName(name) 72 return &Func{ 73 Signature: sig, 74 Selector: selector(sig), 75 Args: abi.Arguments(args), 76 Returns: abi.Arguments(returnArgs), 77 name: name, 78 }, nil 79 } 80 81 // MustNewFunc is like [NewFunc] but panics if the signature or returns parsing 82 // fails. 83 func MustNewFunc(signature, returns string, tuples ...any) *Func { 84 fn, err := NewFunc(signature, returns, tuples...) 85 if err != nil { 86 panic(err) 87 } 88 return fn 89 } 90 91 // EncodeArgs ABI-encodes the given args and prepends the Func's 4-byte 92 // selector. 93 func (f *Func) EncodeArgs(args ...any) ([]byte, error) { 94 return _abi.Arguments(f.Args).EncodeWithSelector(f.Selector, args...) 95 } 96 97 // DecodeArgs ABI-decodes the given input to the given args. 98 func (f *Func) DecodeArgs(input []byte, args ...any) error { 99 if len(input) < 4 { 100 return errors.New("w3: insufficient input length") 101 } 102 if !bytes.Equal(input[:4], f.Selector[:]) { 103 return errors.New("w3: input does not match selector") 104 } 105 return _abi.Arguments(f.Args).Decode(input[4:], args...) 106 } 107 108 // DecodeReturns ABI-decodes the given output to the given returns. 109 func (f *Func) DecodeReturns(output []byte, returns ...any) error { 110 // check the output for a revert reason 111 if bytes.HasPrefix(output, revertSelector[:]) { 112 if reason, err := abi.UnpackRevert(output); err != nil { 113 return err 114 } else { 115 return fmt.Errorf("%w: %s", ErrEvmRevert, reason) 116 } 117 } 118 119 // Gracefully handle uncompliant ERC20 returns 120 if len(returns) == 1 && len(output) == 0 && 121 (f.Selector == approveSelector || 122 f.Selector == transferSelector || 123 f.Selector == transferFromSelector) { 124 output = outputSuccess 125 } 126 127 return _abi.Arguments(f.Returns).Decode(output, returns...) 128 } 129 130 // selector returns the 4-byte selector of the given signature. 131 func selector(signature string) (selector [4]byte) { 132 copy(selector[:], crypto.Keccak256([]byte(signature))) 133 return 134 }