github.com/theQRL/go-zond@v0.1.1/signer/fourbyte/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 package fourbyte 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "strings" 24 25 "github.com/theQRL/go-zond/accounts/abi" 26 "github.com/theQRL/go-zond/common" 27 ) 28 29 // decodedCallData is an internal type to represent a method call parsed according 30 // to an ABI method signature. 31 type decodedCallData struct { 32 signature string 33 name string 34 inputs []decodedArgument 35 } 36 37 // decodedArgument is an internal type to represent an argument parsed according 38 // to an ABI method signature. 39 type decodedArgument struct { 40 soltype abi.Argument 41 value interface{} 42 } 43 44 // String implements stringer interface, tries to use the underlying value-type 45 func (arg decodedArgument) String() string { 46 var value string 47 switch val := arg.value.(type) { 48 case fmt.Stringer: 49 value = val.String() 50 default: 51 value = fmt.Sprintf("%v", val) 52 } 53 return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value) 54 } 55 56 // String implements stringer interface for decodedCallData 57 func (cd decodedCallData) String() string { 58 args := make([]string, len(cd.inputs)) 59 for i, arg := range cd.inputs { 60 args[i] = arg.String() 61 } 62 return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ",")) 63 } 64 65 // verifySelector checks whether the ABI encoded data blob matches the requested 66 // function signature. 67 func verifySelector(selector string, calldata []byte) (*decodedCallData, error) { 68 // Parse the selector into an ABI JSON spec 69 abidata, err := parseSelector(selector) 70 if err != nil { 71 return nil, err 72 } 73 // Parse the call data according to the requested selector 74 return parseCallData(calldata, string(abidata)) 75 } 76 77 // parseSelector converts a method selector into an ABI JSON spec. The returned 78 // data is a valid JSON string which can be consumed by the standard abi package. 79 func parseSelector(unescapedSelector string) ([]byte, error) { 80 selector, err := abi.ParseSelector(unescapedSelector) 81 if err != nil { 82 return nil, fmt.Errorf("failed to parse selector: %v", err) 83 } 84 85 return json.Marshal([]abi.SelectorMarshaling{selector}) 86 } 87 88 // parseCallData matches the provided call data against the ABI definition and 89 // returns a struct containing the actual go-typed values. 90 func parseCallData(calldata []byte, unescapedAbidata string) (*decodedCallData, error) { 91 // Validate the call data that it has the 4byte prefix and the rest divisible by 32 bytes 92 if len(calldata) < 4 { 93 return nil, fmt.Errorf("invalid call data, incomplete method signature (%d bytes < 4)", len(calldata)) 94 } 95 sigdata := calldata[:4] 96 97 argdata := calldata[4:] 98 if len(argdata)%32 != 0 { 99 return nil, fmt.Errorf("invalid call data; length should be a multiple of 32 bytes (was %d)", len(argdata)) 100 } 101 // Validate the called method and upack the call data accordingly 102 abispec, err := abi.JSON(strings.NewReader(unescapedAbidata)) 103 if err != nil { 104 return nil, fmt.Errorf("invalid method signature (%q): %v", unescapedAbidata, err) 105 } 106 method, err := abispec.MethodById(sigdata) 107 if err != nil { 108 return nil, err 109 } 110 values, err := method.Inputs.UnpackValues(argdata) 111 if err != nil { 112 return nil, fmt.Errorf("signature %q matches, but arguments mismatch: %v", method.String(), err) 113 } 114 // Everything valid, assemble the call infos for the signer 115 decoded := decodedCallData{signature: method.Sig, name: method.RawName} 116 for i := 0; i < len(method.Inputs); i++ { 117 decoded.inputs = append(decoded.inputs, decodedArgument{ 118 soltype: method.Inputs[i], 119 value: values[i], 120 }) 121 } 122 // We're finished decoding the data. At this point, we encode the decoded data 123 // to see if it matches with the original data. If we didn't do that, it would 124 // be possible to stuff extra data into the arguments, which is not detected 125 // by merely decoding the data. 126 encoded, err := method.Inputs.PackValues(values) 127 if err != nil { 128 return nil, err 129 } 130 if !bytes.Equal(encoded, argdata) { 131 was := common.Bytes2Hex(encoded) 132 exp := common.Bytes2Hex(argdata) 133 return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig) 134 } 135 return &decoded, nil 136 }