decred.org/dcrdex@v1.0.5/dex/networks/eth/abi.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // This file lifted from go-ethereum/signer/fourbyte at v1.10.6 commit 18 // 576681f29b895dd39e559b7ba17fcd89b42e4833 and modified to make parseCalldata take 19 // an abi instead of a string. 20 package eth 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "regexp" 27 "strings" 28 29 "github.com/ethereum/go-ethereum/accounts/abi" 30 "github.com/ethereum/go-ethereum/common" 31 ) 32 33 // DecodedCallData is an internal type to represent a method call parsed according 34 // to an ABI method signature. 35 type DecodedCallData struct { 36 signature string 37 inputs []decodedArgument 38 39 Name string 40 Args []any 41 } 42 43 // decodedArgument is an internal type to represent an argument parsed according 44 // to an ABI method signature. 45 type decodedArgument struct { 46 soltype abi.Argument 47 value any 48 } 49 50 // String implements stringer interface, tries to use the underlying value-type 51 func (arg decodedArgument) String() string { 52 var value string 53 switch val := arg.value.(type) { 54 case fmt.Stringer: 55 value = val.String() 56 default: 57 value = fmt.Sprintf("%v", val) 58 } 59 return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value) 60 } 61 62 // String implements stringer interface for decodedCallData 63 func (cd *DecodedCallData) String() string { 64 args := make([]string, len(cd.inputs)) 65 for i, arg := range cd.inputs { 66 args[i] = arg.String() 67 } 68 return fmt.Sprintf("%s(%s)", cd.Name, strings.Join(args, ",")) 69 } 70 71 // verifySelector checks whether the ABI encoded data blob matches the requested 72 // function signature. 73 func verifySelector(selector string, calldata []byte) (*DecodedCallData, error) { 74 // Parse the selector into an ABI JSON spec 75 abidata, err := parseSelector(selector) 76 if err != nil { 77 return nil, err 78 } 79 parsedAbi, err := abi.JSON(bytes.NewReader(abidata)) 80 if err != nil { 81 return nil, fmt.Errorf("invalid method signature (%q): %v", abidata, err) 82 } 83 // Parse the call data according to the requested selector 84 return ParseCallData(calldata, &parsedAbi) 85 } 86 87 // selectorRegexp is used to validate that a 4byte database selector corresponds 88 // to a valid ABI function declaration. 89 // 90 // Note, although uppercase letters are not part of the ABI spec, this regexp 91 // still accepts it as the general format is valid. It will be rejected later 92 // by the type checker. 93 var selectorRegexp = regexp.MustCompile(`^([^\)]+)\(([A-Za-z0-9,\[\]]*)\)`) 94 95 // parseSelector converts a method selector into an ABI JSON spec. The returned 96 // data is a valid JSON string which can be consumed by the standard abi package. 97 func parseSelector(unescapedSelector string) ([]byte, error) { 98 // Define a tiny fake ABI struct for JSON marshalling 99 type fakeArg struct { 100 Type string `json:"type"` 101 } 102 type fakeABI struct { 103 Name string `json:"name"` 104 Type string `json:"type"` 105 Inputs []fakeArg `json:"inputs"` 106 } 107 // Validate the unescapedSelector and extract it's components 108 groups := selectorRegexp.FindStringSubmatch(unescapedSelector) 109 if len(groups) != 3 { 110 return nil, fmt.Errorf("invalid selector %q (%v matches)", unescapedSelector, len(groups)) 111 } 112 name := groups[1] 113 args := groups[2] 114 115 // Reassemble the fake ABI and constuct the JSON 116 arguments := make([]fakeArg, 0) 117 if len(args) > 0 { 118 for _, arg := range strings.Split(args, ",") { 119 arguments = append(arguments, fakeArg{arg}) 120 } 121 } 122 return json.Marshal([]fakeABI{{name, "function", arguments}}) 123 } 124 125 // ParseCallData matches the provided call data against the ABI definition and 126 // returns a struct containing the actual go-typed values. 127 func ParseCallData(calldata []byte, abispec *abi.ABI) (*DecodedCallData, error) { 128 // Validate the call data that it has the 4byte prefix and the rest divisible by 32 bytes 129 if len(calldata) < 4 { 130 return nil, fmt.Errorf("invalid call data, incomplete method signature (%d bytes < 4)", len(calldata)) 131 } 132 sigdata := calldata[:4] 133 134 argdata := calldata[4:] 135 if len(argdata)%32 != 0 { 136 return nil, fmt.Errorf("invalid call data; length should be a multiple of 32 bytes (was %d)", len(argdata)) 137 } 138 method, err := abispec.MethodById(sigdata) 139 if err != nil { 140 return nil, err 141 } 142 values, err := method.Inputs.UnpackValues(argdata) 143 if err != nil { 144 return nil, fmt.Errorf("signature %q matches, but arguments mismatch: %v", method.String(), err) 145 } 146 // Everything valid, assemble the call infos for the signer 147 decoded := &DecodedCallData{signature: method.Sig, Name: method.RawName, Args: values} 148 for i := 0; i < len(method.Inputs); i++ { 149 decoded.inputs = append(decoded.inputs, decodedArgument{ 150 soltype: method.Inputs[i], 151 value: values[i], 152 }) 153 } 154 // We're finished decoding the data. At this point, we encode the decoded data 155 // to see if it matches with the original data. If we didn't do that, it would 156 // be possible to stuff extra data into the arguments, which is not detected 157 // by merely decoding the data. 158 encoded, err := method.Inputs.PackValues(values) 159 if err != nil { 160 return nil, err 161 } 162 if !bytes.Equal(encoded, argdata) { 163 was := common.Bytes2Hex(encoded) 164 exp := common.Bytes2Hex(argdata) 165 return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig) 166 } 167 return decoded, nil 168 }