mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
cmd/signer: methods to validate calldata against abi
This commit is contained in:
parent
b8b84fec75
commit
08301bbcc0
2 changed files with 186 additions and 0 deletions
107
cmd/signer/abihelper.go
Normal file
107
cmd/signer/abihelper.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
79
cmd/signer/abihelper_test.go
Normal file
79
cmd/signer/abihelper_test.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue