github.com/luckypickle/go-ethereum-vet@v1.14.2/signer/core/abihelper.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // go-ethereum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "strings" 24 25 "github.com/luckypickle/go-ethereum-vet/accounts/abi" 26 "github.com/luckypickle/go-ethereum-vet/common" 27 28 "bytes" 29 "os" 30 "regexp" 31 ) 32 33 type decodedArgument struct { 34 soltype abi.Argument 35 value interface{} 36 } 37 type decodedCallData struct { 38 signature string 39 name string 40 inputs []decodedArgument 41 } 42 43 // String implements stringer interface, tries to use the underlying value-type 44 func (arg decodedArgument) String() string { 45 var value string 46 switch val := arg.value.(type) { 47 case fmt.Stringer: 48 value = val.String() 49 default: 50 value = fmt.Sprintf("%v", val) 51 } 52 return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value) 53 } 54 55 // String implements stringer interface for decodedCallData 56 func (cd decodedCallData) String() string { 57 args := make([]string, len(cd.inputs)) 58 for i, arg := range cd.inputs { 59 args[i] = arg.String() 60 } 61 return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ",")) 62 } 63 64 // parseCallData matches the provided call data against the abi definition, 65 // and returns a struct containing the actual go-typed values 66 func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) { 67 68 if len(calldata) < 4 { 69 return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata)) 70 } 71 72 sigdata, argdata := calldata[:4], calldata[4:] 73 if len(argdata)%32 != 0 { 74 return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata)) 75 } 76 77 abispec, err := abi.JSON(strings.NewReader(abidata)) 78 if err != nil { 79 return nil, fmt.Errorf("Failed parsing JSON ABI: %v, abidata: %v", err, abidata) 80 } 81 82 method, err := abispec.MethodById(sigdata) 83 if err != nil { 84 return nil, err 85 } 86 87 v, err := method.Inputs.UnpackValues(argdata) 88 if err != nil { 89 return nil, err 90 } 91 92 decoded := decodedCallData{signature: method.Sig(), name: method.Name} 93 94 for n, argument := range method.Inputs { 95 if err != nil { 96 return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err) 97 } 98 decodedArg := decodedArgument{ 99 soltype: argument, 100 value: v[n], 101 } 102 decoded.inputs = append(decoded.inputs, decodedArg) 103 } 104 105 // We're finished decoding the data. At this point, we encode the decoded data to see if it matches with the 106 // original data. If we didn't do that, it would e.g. be possible to stuff extra data into the arguments, which 107 // is not detected by merely decoding the data. 108 109 var ( 110 encoded []byte 111 ) 112 encoded, err = method.Inputs.PackValues(v) 113 114 if err != nil { 115 return nil, err 116 } 117 118 if !bytes.Equal(encoded, argdata) { 119 was := common.Bytes2Hex(encoded) 120 exp := common.Bytes2Hex(argdata) 121 return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig()) 122 } 123 return &decoded, nil 124 } 125 126 // MethodSelectorToAbi converts a method selector into an ABI struct. The returned data is a valid json string 127 // which can be consumed by the standard abi package. 128 func MethodSelectorToAbi(selector string) ([]byte, error) { 129 130 re := regexp.MustCompile(`^([^\)]+)\(([a-z0-9,\[\]]*)\)`) 131 132 type fakeArg struct { 133 Type string `json:"type"` 134 } 135 type fakeABI struct { 136 Name string `json:"name"` 137 Type string `json:"type"` 138 Inputs []fakeArg `json:"inputs"` 139 } 140 groups := re.FindStringSubmatch(selector) 141 if len(groups) != 3 { 142 return nil, fmt.Errorf("Did not match: %v (%v matches)", selector, len(groups)) 143 } 144 name := groups[1] 145 args := groups[2] 146 arguments := make([]fakeArg, 0) 147 if len(args) > 0 { 148 for _, arg := range strings.Split(args, ",") { 149 arguments = append(arguments, fakeArg{arg}) 150 } 151 } 152 abicheat := fakeABI{ 153 name, "function", arguments, 154 } 155 return json.Marshal([]fakeABI{abicheat}) 156 157 } 158 159 type AbiDb struct { 160 db map[string]string 161 customdb map[string]string 162 customdbPath string 163 } 164 165 // NewEmptyAbiDB exists for test purposes 166 func NewEmptyAbiDB() (*AbiDb, error) { 167 return &AbiDb{make(map[string]string), make(map[string]string), ""}, nil 168 } 169 170 // NewAbiDBFromFile loads signature database from file, and 171 // errors if the file is not valid json. Does no other validation of contents 172 func NewAbiDBFromFile(path string) (*AbiDb, error) { 173 raw, err := ioutil.ReadFile(path) 174 if err != nil { 175 return nil, err 176 } 177 db, err := NewEmptyAbiDB() 178 if err != nil { 179 return nil, err 180 } 181 json.Unmarshal(raw, &db.db) 182 return db, nil 183 } 184 185 // NewAbiDBFromFiles loads both the standard signature database and a custom database. The latter will be used 186 // to write new values into if they are submitted via the API 187 func NewAbiDBFromFiles(standard, custom string) (*AbiDb, error) { 188 189 db := &AbiDb{make(map[string]string), make(map[string]string), custom} 190 db.customdbPath = custom 191 192 raw, err := ioutil.ReadFile(standard) 193 if err != nil { 194 return nil, err 195 } 196 json.Unmarshal(raw, &db.db) 197 // Custom file may not exist. Will be created during save, if needed 198 if _, err := os.Stat(custom); err == nil { 199 raw, err = ioutil.ReadFile(custom) 200 if err != nil { 201 return nil, err 202 } 203 json.Unmarshal(raw, &db.customdb) 204 } 205 206 return db, nil 207 } 208 209 // LookupMethodSelector checks the given 4byte-sequence against the known ABI methods. 210 // OBS: This method does not validate the match, it's assumed the caller will do so 211 func (db *AbiDb) LookupMethodSelector(id []byte) (string, error) { 212 if len(id) < 4 { 213 return "", fmt.Errorf("Expected 4-byte id, got %d", len(id)) 214 } 215 sig := common.ToHex(id[:4]) 216 if key, exists := db.db[sig]; exists { 217 return key, nil 218 } 219 if key, exists := db.customdb[sig]; exists { 220 return key, nil 221 } 222 return "", fmt.Errorf("Signature %v not found", sig) 223 } 224 func (db *AbiDb) Size() int { 225 return len(db.db) 226 } 227 228 // saveCustomAbi saves a signature ephemerally. If custom file is used, also saves to disk 229 func (db *AbiDb) saveCustomAbi(selector, signature string) error { 230 db.customdb[signature] = selector 231 if db.customdbPath == "" { 232 return nil //Not an error per se, just not used 233 } 234 d, err := json.Marshal(db.customdb) 235 if err != nil { 236 return err 237 } 238 err = ioutil.WriteFile(db.customdbPath, d, 0600) 239 return err 240 } 241 242 // AddSignature to the database, if custom database saving is enabled. 243 // OBS: This method does _not_ validate the correctness of the data, 244 // it is assumed that the caller has already done so 245 func (db *AbiDb) AddSignature(selector string, data []byte) error { 246 if len(data) < 4 { 247 return nil 248 } 249 _, err := db.LookupMethodSelector(data[:4]) 250 if err == nil { 251 return nil 252 } 253 sig := common.ToHex(data[:4]) 254 return db.saveCustomAbi(selector, sig) 255 }