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