abi: accept input slices of all supported types

This commit is contained in:
Jeffrey Wilcke 2016-03-30 16:22:02 +02:00
parent 96c7c39ae4
commit 022cbd6800
5 changed files with 118 additions and 157 deletions

View File

@ -63,9 +63,8 @@ func (abi ABI) pack(method Method, args ...interface{}) ([]byte, error) {
return nil, fmt.Errorf("`%s` %v", method.Name, err) return nil, fmt.Errorf("`%s` %v", method.Name, err)
} }
// check for a string or bytes input type // check for a slice type (string, bytes, slice)
switch input.Type.T { if input.Type.T == StringTy || input.Type.T == BytesTy || input.Type.IsSlice {
case StringTy, BytesTy:
// calculate the offset // calculate the offset
offset := len(method.Inputs)*32 + len(variableInput) offset := len(method.Inputs)*32 + len(variableInput)
// set the offset // set the offset
@ -73,7 +72,7 @@ func (abi ABI) pack(method Method, args ...interface{}) ([]byte, error) {
// Append the packed output to the variable input. The variable input // Append the packed output to the variable input. The variable input
// will be appended at the end of the input. // will be appended at the end of the input.
variableInput = append(variableInput, packed...) variableInput = append(variableInput, packed...)
default: } else {
// append the packed value to the input // append the packed value to the input
ret = append(ret, packed...) ret = append(ret, packed...)
} }

View File

@ -49,7 +49,9 @@ const jsondata2 = `
{ "type" : "function", "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, { "type" : "function", "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
{ "type" : "function", "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, { "type" : "function", "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
{ "type" : "function", "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, { "type" : "function", "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
{ "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } { "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "sliceAddress", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
{ "type" : "function", "name" : "sliceMultiAddress", "const" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }
]` ]`
func TestType(t *testing.T) { func TestType(t *testing.T) {
@ -68,7 +70,7 @@ func TestType(t *testing.T) {
if typ.Kind != reflect.Slice { if typ.Kind != reflect.Slice {
t.Error("expected uint32[] to have type slice") t.Error("expected uint32[] to have type slice")
} }
if typ.Type != ubig_ts { if typ.Type != ubig_t {
t.Error("expcted uith32[] to have type uint64") t.Error("expcted uith32[] to have type uint64")
} }
@ -79,7 +81,7 @@ func TestType(t *testing.T) {
if typ.Kind != reflect.Slice { if typ.Kind != reflect.Slice {
t.Error("expected uint32[2] to have kind slice") t.Error("expected uint32[2] to have kind slice")
} }
if typ.Type != ubig_ts { if typ.Type != ubig_t {
t.Error("expcted uith32[2] to have type uint64") t.Error("expcted uith32[2] to have type uint64")
} }
if typ.Size != 2 { if typ.Size != 2 {
@ -202,16 +204,6 @@ func TestTestSlice(t *testing.T) {
t.FailNow() t.FailNow()
} }
addr := make([]byte, 20)
if _, err := abi.Pack("address", addr); err != nil {
t.Error(err)
}
addr = make([]byte, 21)
if _, err := abi.Pack("address", addr); err == nil {
t.Error("expected address of 21 width to throw")
}
slice := make([]byte, 2) slice := make([]byte, 2)
if _, err := abi.Pack("uint64[2]", slice); err != nil { if _, err := abi.Pack("uint64[2]", slice); err != nil {
t.Error(err) t.Error(err)
@ -222,19 +214,6 @@ func TestTestSlice(t *testing.T) {
} }
} }
func TestTestAddress(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
addr := make([]byte, 20)
if _, err := abi.Pack("address", addr); err != nil {
t.Error(err)
}
}
func TestMethodSignature(t *testing.T) { func TestMethodSignature(t *testing.T) {
String, _ := NewType("string") String, _ := NewType("string")
String32, _ := NewType("string32") String32, _ := NewType("string32")
@ -310,44 +289,69 @@ func TestPackSlice(t *testing.T) {
} }
sig := crypto.Keccak256([]byte("slice(uint32[2])"))[:4] sig := crypto.Keccak256([]byte("slice(uint32[2])"))[:4]
sig = append(sig, make([]byte, 64)...) sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
sig[35] = 1 sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig[67] = 2 sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
packed, err := abi.Pack("slice", []uint32{1, 2}) packed, err := abi.Pack("slice", []uint32{1, 2})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.FailNow() }
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
var addrA, addrB = common.Address{1}, common.Address{2}
sig = abi.Methods["sliceAddress"].Id()
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
var addrC, addrD = common.Address{3}, common.Address{4}
sig = abi.Methods["sliceMultiAddress"].Id()
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
sig = crypto.Keccak256([]byte("slice256(uint256[2])"))[:4]
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
if err != nil {
t.Error(err)
} }
if !bytes.Equal(packed, sig) { if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed) t.Errorf("expected %x got %x", sig, packed)
} }
} }
func TestPackSliceBig(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Error(err)
t.FailNow()
}
sig := crypto.Keccak256([]byte("slice256(uint256[2])"))[:4]
sig = append(sig, make([]byte, 64)...)
sig[35] = 1
sig[67] = 2
packed, err := abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
if err != nil {
t.Error(err)
t.FailNow()
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
}
func ExampleJSON() { func ExampleJSON() {
const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]` const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]`
@ -493,35 +497,6 @@ func TestInputVariableInputLength(t *testing.T) {
} }
} }
func TestBytes(t *testing.T) {
const definition = `[
{ "type" : "function", "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] },
{ "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
ok := make([]byte, 20)
_, err = abi.Pack("balance", ok)
if err != nil {
t.Error(err)
}
toosmall := make([]byte, 19)
_, err = abi.Pack("balance", toosmall)
if err != nil {
t.Error(err)
}
toobig := make([]byte, 21)
_, err = abi.Pack("balance", toobig)
if err == nil {
t.Error("expected error")
}
}
func TestDefaultFunctionParsing(t *testing.T) { func TestDefaultFunctionParsing(t *testing.T) {
const definition = `[{ "name" : "balance" }]` const definition = `[{ "name" : "balance" }]`

View File

@ -117,8 +117,6 @@ func packNum(value reflect.Value, to byte) []byte {
// checks whether the given reflect value is signed. This also works for slices with a number type // checks whether the given reflect value is signed. This also works for slices with a number type
func isSigned(v reflect.Value) bool { func isSigned(v reflect.Value) bool {
switch v.Type() { switch v.Type() {
case ubig_ts, big_ts, big_t, ubig_t:
return true
case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t: case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t:
return true return true
} }

View File

@ -81,8 +81,4 @@ func TestSigned(t *testing.T) {
if !isSigned(reflect.ValueOf(int(10))) { if !isSigned(reflect.ValueOf(int(10))) {
t.Error() t.Error()
} }
if !isSigned(reflect.ValueOf(big.NewInt(10))) {
t.Error()
}
} }

View File

@ -96,18 +96,6 @@ func NewType(t string) (typ Type, err error) {
t += "256" t += "256"
} }
if isslice {
typ.Kind = reflect.Slice
typ.Size = size
switch vtype {
case "int":
typ.Type = big_ts
case "uint":
typ.Type = ubig_ts
default:
return Type{}, fmt.Errorf("unsupported arg slice type: %s", t)
}
} else {
switch vtype { switch vtype {
case "int": case "int":
typ.Kind = reflect.Ptr typ.Kind = reflect.Ptr
@ -153,6 +141,12 @@ func NewType(t string) (typ Type, err error) {
default: default:
return Type{}, fmt.Errorf("unsupported arg type: %s", t) return Type{}, fmt.Errorf("unsupported arg type: %s", t)
} }
// if the type is a slice we must set Kind to a reflect.Slice
// so that serialisation can be determined based on this kind.
if isslice {
typ.Kind = reflect.Slice
typ.Size = size
} }
typ.stringKind = t typ.stringKind = t
@ -203,7 +197,7 @@ func (t Type) pack(v interface{}) ([]byte, error) {
return packBytesSlice([]byte(value.String()), value.Len()), nil return packBytesSlice([]byte(value.String()), value.Len()), nil
case reflect.Slice: case reflect.Slice:
// if the param is a bytes type, pack the slice up as a string // Byte slice is a special case, it gets treated as a single value
if t.T == BytesTy { if t.T == BytesTy {
return packBytesSlice(value.Bytes(), value.Len()), nil return packBytesSlice(value.Bytes(), value.Len()), nil
} }
@ -212,21 +206,20 @@ func (t Type) pack(v interface{}) ([]byte, error) {
return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size) return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
} }
// Address is a special slice. The slice acts as one rather than a list of elements.
if t.T == AddressTy {
return common.LeftPadBytes(v.([]byte), 32), nil
}
// Signed / Unsigned check // Signed / Unsigned check
if (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) { if value.Type() == big_t && (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) {
return nil, fmt.Errorf("slice of incompatible types.") return nil, fmt.Errorf("slice of incompatible types.")
} }
var packed []byte var packed []byte
for i := 0; i < value.Len(); i++ { for i := 0; i < value.Len(); i++ {
packed = append(packed, packNum(value.Index(i), t.T)...) val, err := t.pack(value.Index(i).Interface())
if err != nil {
return nil, err
} }
return packed, nil packed = append(packed, val...)
}
return packBytesSlice(packed, value.Len()), nil
case reflect.Bool: case reflect.Bool:
if value.Bool() { if value.Bool() {
return common.LeftPadBytes(common.Big1.Bytes(), 32), nil return common.LeftPadBytes(common.Big1.Bytes(), 32), nil