go-ethereum/accounts/abi/abi_test.go

440 lines
13 KiB
Go

// Copyright 2015 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 abi
import (
"bytes"
"fmt"
"log"
"math/big"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// formatSilceOutput add padding to the value and adds a size
func formatSliceOutput(v ...[]byte) []byte {
off := common.LeftPadBytes(big.NewInt(int64(len(v))).Bytes(), 32)
output := append(off, make([]byte, 0, len(v)*32)...)
for _, value := range v {
output = append(output, common.LeftPadBytes(value, 32)...)
}
return output
}
// quick helper padding
func pad(input []byte, size int, left bool) []byte {
if left {
return common.LeftPadBytes(input, size)
}
return common.RightPadBytes(input, size)
}
const jsondata = `
[
{ "type" : "function", "name" : "balance", "constant" : true },
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
]`
const jsondata2 = `
[
{ "type" : "function", "name" : "balance", "constant" : true },
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
{ "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
{ "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
{ "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
{ "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
{ "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
{ "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
{ "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
{ "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }
]`
func TestReader(t *testing.T) {
Uint256, _ := NewType("uint256")
exp := ABI{
Methods: map[string]Method{
"balance": {
"balance", true, nil, nil,
},
"send": {
"send", false, []Argument{
{"amount", Uint256, false},
}, nil,
},
},
}
abi, err := JSON(strings.NewReader(jsondata))
if err != nil {
t.Error(err)
}
// deep equal fails for some reason
t.Skip()
if !reflect.DeepEqual(abi, exp) {
t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp)
}
}
func TestTestNumbers(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
if _, err := abi.Pack("balance"); err != nil {
t.Error(err)
}
if _, err := abi.Pack("balance", 1); err == nil {
t.Error("expected error for balance(1)")
}
if _, err := abi.Pack("doesntexist", nil); err == nil {
t.Errorf("doesntexist shouldn't exist")
}
if _, err := abi.Pack("doesntexist", 1); err == nil {
t.Errorf("doesntexist(1) shouldn't exist")
}
if _, err := abi.Pack("send", big.NewInt(1000)); err != nil {
t.Error(err)
}
i := new(int)
*i = 1000
if _, err := abi.Pack("send", i); err == nil {
t.Errorf("expected send( ptr ) to throw, requires *big.Int instead of *int")
}
if _, err := abi.Pack("test", uint32(1000)); err != nil {
t.Error(err)
}
}
func TestTestString(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
if _, err := abi.Pack("string", "hello world"); err != nil {
t.Error(err)
}
}
func TestTestBool(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
if _, err := abi.Pack("bool", true); err != nil {
t.Error(err)
}
}
func TestTestSlice(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
slice := make([]uint64, 2)
if _, err := abi.Pack("uint64[2]", slice); err != nil {
t.Error(err)
}
if _, err := abi.Pack("uint64[]", slice); err != nil {
t.Error(err)
}
}
func TestMethodSignature(t *testing.T) {
String, _ := NewType("string")
m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
exp := "foo(string,string)"
if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig())
}
idexp := crypto.Keccak256([]byte(exp))[:4]
if !bytes.Equal(m.Id(), idexp) {
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
}
uintt, _ := NewType("uint")
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
exp = "foo(uint256)"
if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig())
}
}
func TestMultiPack(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
sig := crypto.Keccak256([]byte("bar(uint32,uint16)"))[:4]
sig = append(sig, make([]byte, 64)...)
sig[35] = 10
sig[67] = 11
packed, err := abi.Pack("bar", uint32(10), uint16(11))
if err != nil {
t.Error(err)
t.FailNow()
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
}
func ExampleJSON() {
const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
log.Fatalln(err)
}
out, err := abi.Pack("isBar", common.HexToAddress("01"))
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%x\n", out)
// Output:
// 1f2c40920000000000000000000000000000000000000000000000000000000000000001
}
func TestInputVariableInputLength(t *testing.T) {
const definition = `[
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
{ "type" : "function", "name" : "strTwo", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] }
]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
// test one string
strin := "hello world"
strpack, err := abi.Pack("strOne", strin)
if err != nil {
t.Error(err)
}
offset := make([]byte, 32)
offset[31] = 32
length := make([]byte, 32)
length[31] = byte(len(strin))
value := common.RightPadBytes([]byte(strin), 32)
exp := append(offset, append(length, value...)...)
// ignore first 4 bytes of the output. This is the function identifier
strpack = strpack[4:]
if !bytes.Equal(strpack, exp) {
t.Errorf("expected %x, got %x\n", exp, strpack)
}
// test one bytes
btspack, err := abi.Pack("bytesOne", []byte(strin))
if err != nil {
t.Error(err)
}
// ignore first 4 bytes of the output. This is the function identifier
btspack = btspack[4:]
if !bytes.Equal(btspack, exp) {
t.Errorf("expected %x, got %x\n", exp, btspack)
}
// test two strings
str1 := "hello"
str2 := "world"
str2pack, err := abi.Pack("strTwo", str1, str2)
if err != nil {
t.Error(err)
}
offset1 := make([]byte, 32)
offset1[31] = 64
length1 := make([]byte, 32)
length1[31] = byte(len(str1))
value1 := common.RightPadBytes([]byte(str1), 32)
offset2 := make([]byte, 32)
offset2[31] = 128
length2 := make([]byte, 32)
length2[31] = byte(len(str2))
value2 := common.RightPadBytes([]byte(str2), 32)
exp2 := append(offset1, offset2...)
exp2 = append(exp2, append(length1, value1...)...)
exp2 = append(exp2, append(length2, value2...)...)
// ignore first 4 bytes of the output. This is the function identifier
str2pack = str2pack[4:]
if !bytes.Equal(str2pack, exp2) {
t.Errorf("expected %x, got %x\n", exp, str2pack)
}
// test two strings, first > 32, second < 32
str1 = strings.Repeat("a", 33)
str2pack, err = abi.Pack("strTwo", str1, str2)
if err != nil {
t.Error(err)
}
offset1 = make([]byte, 32)
offset1[31] = 64
length1 = make([]byte, 32)
length1[31] = byte(len(str1))
value1 = common.RightPadBytes([]byte(str1), 64)
offset2[31] = 160
exp2 = append(offset1, offset2...)
exp2 = append(exp2, append(length1, value1...)...)
exp2 = append(exp2, append(length2, value2...)...)
// ignore first 4 bytes of the output. This is the function identifier
str2pack = str2pack[4:]
if !bytes.Equal(str2pack, exp2) {
t.Errorf("expected %x, got %x\n", exp, str2pack)
}
// test two strings, first > 32, second >32
str1 = strings.Repeat("a", 33)
str2 = strings.Repeat("a", 33)
str2pack, err = abi.Pack("strTwo", str1, str2)
if err != nil {
t.Error(err)
}
offset1 = make([]byte, 32)
offset1[31] = 64
length1 = make([]byte, 32)
length1[31] = byte(len(str1))
value1 = common.RightPadBytes([]byte(str1), 64)
offset2 = make([]byte, 32)
offset2[31] = 160
length2 = make([]byte, 32)
length2[31] = byte(len(str2))
value2 = common.RightPadBytes([]byte(str2), 64)
exp2 = append(offset1, offset2...)
exp2 = append(exp2, append(length1, value1...)...)
exp2 = append(exp2, append(length2, value2...)...)
// ignore first 4 bytes of the output. This is the function identifier
str2pack = str2pack[4:]
if !bytes.Equal(str2pack, exp2) {
t.Errorf("expected %x, got %x\n", exp, str2pack)
}
}
func TestDefaultFunctionParsing(t *testing.T) {
const definition = `[{ "name" : "balance" }]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
if _, ok := abi.Methods["balance"]; !ok {
t.Error("expected 'balance' to be present")
}
}
func TestBareEvents(t *testing.T) {
const definition = `[
{ "type" : "event", "name" : "balance" },
{ "type" : "event", "name" : "anon", "anonymous" : true},
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }
]`
arg0, _ := NewType("uint256")
arg1, _ := NewType("address")
expectedEvents := map[string]struct {
Anonymous bool
Args []Argument
}{
"balance": {false, nil},
"anon": {true, nil},
"args": {false, []Argument{
{Name: "arg0", Type: arg0, Indexed: false},
{Name: "arg1", Type: arg1, Indexed: true},
}},
}
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
if len(abi.Events) != len(expectedEvents) {
t.Fatalf("invalid number of events after parsing, want %d, got %d", len(expectedEvents), len(abi.Events))
}
for name, exp := range expectedEvents {
got, ok := abi.Events[name]
if !ok {
t.Errorf("could not found event %s", name)
continue
}
if got.Anonymous != exp.Anonymous {
t.Errorf("invalid anonymous indication for event %s, want %v, got %v", name, exp.Anonymous, got.Anonymous)
}
if len(got.Inputs) != len(exp.Args) {
t.Errorf("invalid number of args, want %d, got %d", len(exp.Args), len(got.Inputs))
continue
}
for i, arg := range exp.Args {
if arg.Name != got.Inputs[i].Name {
t.Errorf("events[%s].Input[%d] has an invalid name, want %s, got %s", name, i, arg.Name, got.Inputs[i].Name)
}
if arg.Indexed != got.Inputs[i].Indexed {
t.Errorf("events[%s].Input[%d] has an invalid indexed indication, want %v, got %v", name, i, arg.Indexed, got.Inputs[i].Indexed)
}
if arg.Type.T != got.Inputs[i].Type.T {
t.Errorf("events[%s].Input[%d] has an invalid type, want %x, got %x", name, i, arg.Type.T, got.Inputs[i].Type.T)
}
}
}
}