123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- // Copyright 2019 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- package fourbyte
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "regexp"
- "strings"
- "github.com/ethereum/go-ethereum/accounts/abi"
- "github.com/ethereum/go-ethereum/common"
- )
- // decodedCallData is an internal type to represent a method call parsed according
- // to an ABI method signature.
- type decodedCallData struct {
- signature string
- name string
- inputs []decodedArgument
- }
- // decodedArgument is an internal type to represent an argument parsed according
- // to an ABI method signature.
- type decodedArgument struct {
- soltype abi.Argument
- value interface{}
- }
- // String implements stringer interface, tries to use the underlying value-type
- func (arg decodedArgument) String() string {
- var value string
- switch val := arg.value.(type) {
- case fmt.Stringer:
- value = val.String()
- default:
- value = fmt.Sprintf("%v", val)
- }
- return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value)
- }
- // String implements stringer interface for decodedCallData
- func (cd decodedCallData) String() string {
- args := make([]string, len(cd.inputs))
- for i, arg := range cd.inputs {
- args[i] = arg.String()
- }
- return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ","))
- }
- // verifySelector checks whether the ABI encoded data blob matches the requested
- // function signature.
- func verifySelector(selector string, calldata []byte) (*decodedCallData, error) {
- // Parse the selector into an ABI JSON spec
- abidata, err := parseSelector(selector)
- if err != nil {
- return nil, err
- }
- // Parse the call data according to the requested selector
- return parseCallData(calldata, string(abidata))
- }
- // selectorRegexp is used to validate that a 4byte database selector corresponds
- // to a valid ABI function declaration.
- //
- // Note, although uppercase letters are not part of the ABI spec, this regexp
- // still accepts it as the general format is valid. It will be rejected later
- // by the type checker.
- var selectorRegexp = regexp.MustCompile(`^([^\)]+)\(([A-Za-z0-9,\[\]]*)\)`)
- // parseSelector converts a method selector into an ABI JSON spec. The returned
- // data is a valid JSON string which can be consumed by the standard abi package.
- func parseSelector(unescapedSelector string) ([]byte, error) {
- // Define a tiny fake ABI struct for JSON marshalling
- type fakeArg struct {
- Type string `json:"type"`
- }
- type fakeABI struct {
- Name string `json:"name"`
- Type string `json:"type"`
- Inputs []fakeArg `json:"inputs"`
- }
- // Validate the unescapedSelector and extract it's components
- groups := selectorRegexp.FindStringSubmatch(unescapedSelector)
- if len(groups) != 3 {
- return nil, fmt.Errorf("invalid selector %q (%v matches)", unescapedSelector, len(groups))
- }
- name := groups[1]
- args := groups[2]
- // Reassemble the fake ABI and constuct the JSON
- arguments := make([]fakeArg, 0)
- if len(args) > 0 {
- for _, arg := range strings.Split(args, ",") {
- arguments = append(arguments, fakeArg{arg})
- }
- }
- return json.Marshal([]fakeABI{{name, "function", arguments}})
- }
- // parseCallData matches the provided call data against the ABI definition and
- // returns a struct containing the actual go-typed values.
- func parseCallData(calldata []byte, unescapedAbidata string) (*decodedCallData, error) {
- // Validate the call data that it has the 4byte prefix and the rest divisible by 32 bytes
- if len(calldata) < 4 {
- return nil, fmt.Errorf("invalid call data, incomplete method signature (%d bytes < 4)", len(calldata))
- }
- sigdata := calldata[:4]
- argdata := calldata[4:]
- if len(argdata)%32 != 0 {
- return nil, fmt.Errorf("invalid call data; length should be a multiple of 32 bytes (was %d)", len(argdata))
- }
- // Validate the called method and upack the call data accordingly
- abispec, err := abi.JSON(strings.NewReader(unescapedAbidata))
- if err != nil {
- return nil, fmt.Errorf("invalid method signature (%q): %v", unescapedAbidata, err)
- }
- method, err := abispec.MethodById(sigdata)
- if err != nil {
- return nil, err
- }
- values, err := method.Inputs.UnpackValues(argdata)
- if err != nil {
- return nil, fmt.Errorf("signature %q matches, but arguments mismatch: %v", method.String(), err)
- }
- // Everything valid, assemble the call infos for the signer
- decoded := decodedCallData{signature: method.Sig, name: method.RawName}
- for i := 0; i < len(method.Inputs); i++ {
- decoded.inputs = append(decoded.inputs, decodedArgument{
- soltype: method.Inputs[i],
- value: values[i],
- })
- }
- // We're finished decoding the data. At this point, we encode the decoded data
- // to see if it matches with the original data. If we didn't do that, it would
- // be possible to stuff extra data into the arguments, which is not detected
- // by merely decoding the data.
- encoded, err := method.Inputs.PackValues(values)
- if err != nil {
- return nil, err
- }
- if !bytes.Equal(encoded, argdata) {
- was := common.Bytes2Hex(encoded)
- exp := common.Bytes2Hex(argdata)
- return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig)
- }
- return &decoded, nil
- }
|