diff --git a/cmd/signer/abihelper.go b/cmd/signer/abihelper.go new file mode 100644 index 0000000000..7b8a0f525e --- /dev/null +++ b/cmd/signer/abihelper.go @@ -0,0 +1,107 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi" + "strings" + "bytes" + "github.com/ethereum/go-ethereum/common" +) + +type decodedArgument struct { + soltype string + value interface{} +} +type decodedCallData struct { + signature string + name string + inputs []decodedArgument +} + +func (arg decodedArgument) String() string { + return fmt.Sprintf("%v: %v", arg.soltype, arg.value) +} +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, ",")) +} + +// parseCallData matches the provided call data against the abi definition, +// and returns a struct containing the actual go-typed values +func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) { + + if len(calldata) < 4 { + return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata)) + } + + abispec, err := abi.JSON(strings.NewReader(abidata)) + if err != nil { + return nil, fmt.Errorf("Failed parsing JSON ABI: %v", err) + } + + sigdata, argdata := calldata[:4], calldata[4:] + if len(argdata)%32 != 0 { + return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata)) + } + + method := abispec.MethodById(sigdata) + if method == nil { + return nil, fmt.Errorf("Supplied ABI spec does not contain method signature in data: 0x%x", sigdata) + } + + decoded := decodedCallData{signature: method.Sig(), name: method.Name} + + for n, argument := range method.Inputs { + value, err := abi.ToGoType(n*32, argument.Type, argdata) + + if err != nil { + return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err) + } else { + decodedArg := decodedArgument{ + soltype: argument.Type.String(), + value: value, + } + decoded.inputs = append(decoded.inputs, decodedArg) + } + } + + // 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 e.g. be possible to stuff extra data into the arguments, which + // is not detected by merely decoding the data. + + var ( + gotypedArguments = make([]interface{}, len(decoded.inputs)) + encoded []byte + ) + for i, arg := range decoded.inputs { + gotypedArguments[i] = arg.value + } + encoded, err = abispec.Pack(method.Name, gotypedArguments...) + + if !bytes.Equal(encoded, calldata){ + exp := common.Bytes2Hex(encoded) + was := common.Bytes2Hex(calldata) + return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. %v \nWant %s\nHave %s", decoded,was, exp) + } + + return &decoded, nil +} diff --git a/cmd/signer/abihelper_test.go b/cmd/signer/abihelper_test.go new file mode 100644 index 0000000000..daad0f17a7 --- /dev/null +++ b/cmd/signer/abihelper_test.go @@ -0,0 +1,79 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "github.com/ethereum/go-ethereum/common" + "testing" +) + +func TestMartin(t *testing.T) { + + // send(uint256) : a52c101e + // compareAndApprove(address,uint256,uint256) : 751e1079 + // issue(address[],uint256) : 42958b54 + jsondata := ` +[ + {"type":"function","name":"send","inputs":[{"name":"a","type":"uint256"}]}, + {"type":"function","name":"compareAndApprove","inputs":[{"name":"a","type":"address"},{"name":"a","type":"uint256"},{"name":"a","type":"uint256"}]}, + {"type":"function","name":"issue","inputs":[{"name":"a","type":"address[]"},{"name":"a","type":"uint256"}]}, + {"type":"function","name":"sam","inputs":[{"name":"a","type":"bytes"},{"name":"a","type":"bool"},{"name":"a","type":"uint256[]"}]} +]` + //Expected failures + for _, hexdata := range []string{ + "a52c101e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + "a52c101e000000000000000000000000000000000000000000000000000000000000001200", + "a52c101e00000000000000000000000000000000000000000000000000000000000000", + "a52c101e", + "a52c10", + "", + "751e10790000000000000000000000000000000000000000000000000000000000000012", + "751e1079FFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "deadbeef00000000000000000000000000000000000000000000000000000000000000", + "42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + "a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + } { + _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) + if err == nil { + t.Errorf("Expected decoding to fail: %s", hexdata) + } + } + + //Expected success + for _, hexdata := range []string{ + // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + "a52c101e0000000000000000000000000000000000000000000000000000000000000012", + "a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "751e1079000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "42958b54" + + // start of dynamic type + "0000000000000000000000000000000000000000000000000000000000000040" + + //uint256 + "0000000000000000000000000000000000000000000000000000000000000001" + + // length of array + "0000000000000000000000000000000000000000000000000000000000000002" + + // array values + "000000000000000000000000000000000000000000000000000000000000dead" + + "000000000000000000000000000000000000000000000000000000000000beef", + } { + _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) + if err != nil { + t.Errorf("Unexpected failure on input %s:\n %v (%d bytes) ", hexdata, err, len(common.Hex2Bytes(hexdata))) + } + } +}