From e0dc45fce2276fcabae8dca61dc766f98dde23e2 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 20 Apr 2016 21:16:21 +0200 Subject: [PATCH] accounts/abi: fixed strict go-like unpacking --- accounts/abi/abi.go | 31 ++++++-- accounts/abi/abi_test.go | 154 +++++++++++++++++++++++++++++++++------ 2 files changed, 158 insertions(+), 27 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 82ea4a0d5c..e7cf4aac76 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -180,12 +180,33 @@ func toGoType(i int, t Argument, output []byte) (interface{}, error) { returnOutput = output[index : index+32] } - // cast bytes to abi return type + // convert the bytes to whatever is specified by the ABI. switch t.Type.T { - case IntTy: - return common.BytesToBig(returnOutput), nil - case UintTy: - return common.BytesToBig(returnOutput), nil + case IntTy, UintTy: + bigNum := common.BytesToBig(returnOutput) + + // If the type is a integer convert to the integer type + // specified by the ABI. + switch t.Type.Kind { + case reflect.Uint8: + return uint8(bigNum.Uint64()), nil + case reflect.Uint16: + return uint16(bigNum.Uint64()), nil + case reflect.Uint32: + return uint32(bigNum.Uint64()), nil + case reflect.Uint64: + return uint64(bigNum.Uint64()), nil + case reflect.Int8: + return uint8(bigNum.Int64()), nil + case reflect.Int16: + return uint16(bigNum.Int64()), nil + case reflect.Int32: + return uint32(bigNum.Int64()), nil + case reflect.Int64: + return uint64(bigNum.Int64()), nil + case reflect.Ptr: + return bigNum, nil + } case BoolTy: return common.BytesToBig(returnOutput).Uint64() > 0, nil case AddressTy: diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 323718a72b..249d37dca9 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -18,6 +18,7 @@ package abi import ( "bytes" + "errors" "fmt" "log" "math/big" @@ -103,6 +104,137 @@ func TestTypeCheck(t *testing.T) { } } +func TestSimpleMethodUnpack(t *testing.T) { + for i, test := range []struct { + def string // definition of the **output** ABI params + marshalledOutput []byte // evm return data + expectedOut interface{} // the expected output + outVar string // the output variable (e.g. uint32, *big.Int, etc) + err error // nil or error if expected + }{ + { + `[ { "type": "uint32" } ]`, + pad([]byte{1}, 32, true), + uint32(1), + "uint32", + nil, + }, + { + `[ { "type": "uint32" } ]`, + pad([]byte{1}, 32, true), + nil, + "uint16", + errors.New("abi: cannot unmarshal uint32 in to uint16"), + }, + { + `[ { "type": "uint17" } ]`, + pad([]byte{1}, 32, true), + nil, + "uint16", + errors.New("abi: cannot unmarshal *big.Int in to uint16"), + }, + { + `[ { "type": "uint17" } ]`, + pad([]byte{1}, 32, true), + big.NewInt(1), + "*big.Int", + nil, + }, + { + `[ { "type": "address" } ]`, + pad(pad([]byte{1}, 20, false), 32, true), + common.Address{1}, + "address", + nil, + }, + { + `[ { "type": "bytes32" } ]`, + pad([]byte{1}, 32, false), + pad([]byte{1}, 32, false), + "bytes", + nil, + }, + { + `[ { "type": "bytes32" } ]`, + pad([]byte{1}, 32, false), + pad([]byte{1}, 32, false), + "hash", + nil, + }, + } { + abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(abiDefinition)) + if err != nil { + t.Errorf("%d failed. %v", i, err) + continue + } + + var outvar interface{} + switch test.outVar { + case "uint8": + var v uint8 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "uint16": + var v uint16 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "uint32": + var v uint32 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "uint64": + var v uint64 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "*big.Int": + var v *big.Int + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "address": + var v common.Address + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "bytes": + var v []byte + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "hash": + var v common.Hash + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + default: + t.Errorf("unsupported type '%v' please add it to the switch statement in this test", test.outVar) + continue + } + + if err != nil && test.err == nil { + t.Errorf("%d failed. Expected no err but got: %v", i, err) + continue + } + if err == nil && test.err != nil { + t.Errorf("%d failed. Expected err: %v but got none", i, test.err) + continue + } + if err != nil && test.err != nil && err.Error() != test.err.Error() { + t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + continue + } + + if err == nil { + // bit of an ugly hack for hash type but I don't feel like finding a proper solution + if test.outVar == "hash" { + tmp := outvar.(common.Hash) // without assignment it's unaddressable + outvar = tmp[:] + } + + if !reflect.DeepEqual(test.expectedOut, outvar) { + t.Errorf("%d failed. Output error: expected %v, got %v", i, test.expectedOut, outvar) + } + } + } +} + func TestPack(t *testing.T) { for i, test := range []struct { typ string @@ -354,28 +486,6 @@ func TestMethodSignature(t *testing.T) { } } -func TestOldPack(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) - if err != nil { - t.Error(err) - t.FailNow() - } - - sig := crypto.Keccak256([]byte("foo(uint32)"))[:4] - sig = append(sig, make([]byte, 32)...) - sig[35] = 10 - - packed, err := abi.Pack("foo", uint32(10)) - if err != nil { - t.Error(err) - t.FailNow() - } - - if !bytes.Equal(packed, sig) { - t.Errorf("expected %x got %x", sig, packed) - } -} - func TestMultiPack(t *testing.T) { abi, err := JSON(strings.NewReader(jsondata2)) if err != nil {