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  }