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