From 5ef1e97b7c317ff90c55f6bed5a2e6e38690512f Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Mon, 22 May 2017 16:38:38 -0500 Subject: [PATCH 1/5] accounts/abi,common/math: much test driven development Signed-off-by: RJ Catalano --- accounts/abi/abi.go | 3 ++- accounts/abi/abi_test.go | 20 +++++++++++++++++++- accounts/abi/packing.go | 2 ++ common/math/big_test.go | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 3d1010229c..7eadb664b4 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -34,7 +34,8 @@ import ( type ABI struct { Constructor Method Methods map[string]Method - Events map[string]Event + // todo: make events loggable from an interface that specifies very abstract functions. + Events map[string]Event } // JSON returns a parsed ABI interface and error if it failed. diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index a45bd6cc0f..9b37ec9750 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -370,6 +370,7 @@ func TestPack(t *testing.T) { {"address[]", []common.Address{{1}, {2}}, formatSliceOutput(pad([]byte{1}, 20, false), pad([]byte{2}, 20, false))}, {"bytes32[]", []common.Hash{{1}, {2}}, formatSliceOutput(pad([]byte{1}, 32, false), pad([]byte{2}, 32, false))}, {"function", [24]byte{1}, pad([]byte{1}, 32, false)}, + {"bool[]", []bool{false, false}, formatSliceOutput(pad([]byte{0}, 32, true), pad([]byte{0}, 32, true))}, } { typ, err := NewType(test.typ) if err != nil { @@ -471,7 +472,8 @@ const jsondata2 = ` { "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" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "", "type" : "uint16" } ] }, + { "type" : "function", "name" : "boolMultiPack", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" }, { "name" : "", "type" : "bool" } ] }, { "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[]" } ] }, @@ -626,6 +628,22 @@ func TestMultiPack(t *testing.T) { if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } + + sig = crypto.Keccak256([]byte("boolMultiPack(bool,bool)"))[:4] + sig = append(sig, make([]byte, 64)...) + sig[35] = 0 + sig[67] = 0 + + packed, err = abi.Pack("boolMultiPack", []interface{}{false, false}...) + if err != nil { + t.Error(err) + t.FailNow() + } + + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + } func ExampleJSON() { diff --git a/accounts/abi/packing.go b/accounts/abi/packing.go index 1d7f85e2ba..579e4365da 100644 --- a/accounts/abi/packing.go +++ b/accounts/abi/packing.go @@ -17,6 +17,7 @@ package abi import ( + "fmt" "reflect" "github.com/ethereum/go-ethereum/common" @@ -48,6 +49,7 @@ func packElement(t Type, reflectValue reflect.Value) []byte { if reflectValue.Bool() { return math.PaddedBigBytes(common.Big1, 32) } else { + fmt.Printf("Got this for value: %v\n", math.PaddedBigBytes(common.Big0, 32)) return math.PaddedBigBytes(common.Big0, 32) } case BytesTy: diff --git a/common/math/big_test.go b/common/math/big_test.go index e789bd18e1..c9839e972d 100644 --- a/common/math/big_test.go +++ b/common/math/big_test.go @@ -125,6 +125,7 @@ func TestPaddedBigBytes(t *testing.T) { {num: big.NewInt(1), n: 4, result: []byte{0, 0, 0, 1}}, {num: big.NewInt(512), n: 4, result: []byte{0, 0, 2, 0}}, {num: BigPow(2, 32), n: 4, result: []byte{1, 0, 0, 0, 0}}, + {num: big.NewInt(0), n: 32, result: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, } for _, test := range tests { if result := PaddedBigBytes(test.num, test.n); !bytes.Equal(result, test.result) { From a5be068035e92f1f316329e0517ecfb1e5e83c70 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Wed, 7 Jun 2017 15:51:45 -0500 Subject: [PATCH 2/5] accounts/abi: testing that need to be repositioned once architecting is finished Signed-off-by: RJ Catalano --- accounts/abi/abi.go | 14 ++++++++- accounts/abi/event.go | 7 +++++ accounts/abi/event_test.go | 63 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 7eadb664b4..93a7280349 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -283,7 +283,19 @@ var ( // Unpack output in v according to the abi specification func (abi ABI) Unpack(v interface{}, name string, output []byte) error { - var method = abi.Methods[name] + var isEvent bool + + // first look for name in the methods map, otherwise look in the events map, since there will + // be a name collision for one ABI if there are two of the same names. + // If we can't find it, error. + method, ok := abi.Methods[name] + if !ok { + method, ok = abi.Events[name] + if !ok { + return fmt.Errorf("abi: could not find requested method or event %v", name) + } + isEvent = true + } if len(output) == 0 { return fmt.Errorf("abi: unmarshalling empty output") diff --git a/accounts/abi/event.go b/accounts/abi/event.go index 51ab842418..97648062cc 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -44,3 +44,10 @@ func (e Event) Id() common.Hash { } return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) } + +/*func (e Event) UnpackLog(v interface{}, data []byte) error { + // if not indexed, should be able to unpack normal log + + // if it is indexed, we will not be able to get back the value, rather we will only be able to + // search for it. +}*/ diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index b5054a0329..89e8ae3aff 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -54,3 +54,66 @@ func TestEventId(t *testing.T) { } } } + +/*func TestEventUnpacking(t *testing.T) { + + var isErrCorrect = func(expectedErr string, receivedError error) bool { + if expectedErr != receivedError.Error() { + t.Errorf("Expected error %v, receieved error %v", expectedErr, receivedError) + } + return true + } + + for i, test := range []struct { + definition string //abi definition + data []byte //log data gotten from the event + expectedOutput interface{} // the expected output + err string // empty or error if expected + }{ + { + `[{"anonymous":false,"inputs":[{"indexed":false,"name":"a","type":"uint256"},{"indexed":true,"name":"b","type":"uint256"},{"indexed":false,"name":"c","type":"uint256"}],"name":"testEvent","type":"event"}]`, + append(pad([]byte{1}, 32, true), pad([]byte{3}, 32, true)...), + []interface{}{*big.Int.SetUInt64(int(1)), *big.Int{3}}, + "", + }, + { + `[{"anonymous":false,"inputs":[{"indexed":false,"name":"a","type":"int256"},{"indexed":true,"name":"b","type":"int256"},{"indexed":false,"name":"c","type":"int256"}],"name":"testEvent","type":"event"}]`, + append(pad([]byte{1}, 32, true), pad([]byte{3}, 32, true)...), + []interface{}{*big.Int{1}, *big.Int{3}}, + "", + }, + { + `[{"anonymous":false,"inputs":[{"indexed":false,"name":"a","type":"bool"},{"indexed":false,"name":"b","type":"int256"},{"indexed":false,"name":"c","type":"uint256"}],"name":"testEvent","type":"event"}]`, + append(pad([]byte{0}, 32, true), append(pad([]byte{1}, 32, true), pad([]byte{3}, 32, true)...)...), + []interface{}{false, *big.Int{1}, *big.Int{3}}, + "", + }, + { + `[{"anonymous":false,"inputs":[{"indexed":false,"name":"a","type":"string"},{"indexed":false,"name":"b","type":"string"},{"indexed":false,"name":"c","type":"string"}],"name":"testEvent","type":"event"}]`, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076675636b696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000"), + []interface{}{"bring", "me", "ether"}, + "", + }, + { + `[{"anonymous":false,"inputs":[{"indexed":true,"name":"a","type":"string"},{"indexed":true,"name":"b","type":"string"},{"indexed":true,"name":"c","type":"string"}],"name":"testEvent","type":"event"}]`, + []byte(nil), + []interface{}{}, + "", + }, + } { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + var v interface{} + if err = abi.Events["method"].UnpackLog(&v, data); err == nil { + if !reflect.DeepEqual(test.expectedOutput, v) { + t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp) + } + } else { + t.Errorf("abi: Unexpected error %v during event unpacking.") + } + + } +}*/ From 2615dc38870a9bce8863e92bbd728f58504f2db7 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Fri, 9 Jun 2017 15:19:54 -0500 Subject: [PATCH 3/5] accounts/abi: separate unpacking functions into own file and separate unpacking to methods and events Signed-off-by: RJ Catalano --- accounts/abi/abi.go | 299 ++------------------------------------ accounts/abi/method.go | 87 ++++++++++- accounts/abi/reflect.go | 3 +- accounts/abi/unpacking.go | 202 +++++++++++++++++++++++++ 4 files changed, 301 insertions(+), 290 deletions(-) create mode 100644 accounts/abi/unpacking.go diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 93a7280349..1682e8e2c1 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -68,7 +68,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { } method = m } - arguments, err := method.pack(method, args...) + arguments, err := method.pack(args...) if err != nil { return nil, err } @@ -79,199 +79,6 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return append(method.Id(), arguments...), nil } -// toGoSliceType parses the input and casts it to the proper slice defined by the ABI -// argument in T. -func toGoSlice(i int, t Argument, output []byte) (interface{}, error) { - index := i * 32 - // The slice must, at very least be large enough for the index+32 which is exactly the size required - // for the [offset in output, size of offset]. - if index+32 > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32) - } - elem := t.Type.Elem - - // first we need to create a slice of the type - var refSlice reflect.Value - switch elem.T { - case IntTy, UintTy, BoolTy: - // create a new reference slice matching the element type - switch t.Type.Kind { - case reflect.Bool: - refSlice = reflect.ValueOf([]bool(nil)) - case reflect.Uint8: - refSlice = reflect.ValueOf([]uint8(nil)) - case reflect.Uint16: - refSlice = reflect.ValueOf([]uint16(nil)) - case reflect.Uint32: - refSlice = reflect.ValueOf([]uint32(nil)) - case reflect.Uint64: - refSlice = reflect.ValueOf([]uint64(nil)) - case reflect.Int8: - refSlice = reflect.ValueOf([]int8(nil)) - case reflect.Int16: - refSlice = reflect.ValueOf([]int16(nil)) - case reflect.Int32: - refSlice = reflect.ValueOf([]int32(nil)) - case reflect.Int64: - refSlice = reflect.ValueOf([]int64(nil)) - default: - refSlice = reflect.ValueOf([]*big.Int(nil)) - } - case AddressTy: // address must be of slice Address - refSlice = reflect.ValueOf([]common.Address(nil)) - case HashTy: // hash must be of slice hash - refSlice = reflect.ValueOf([]common.Hash(nil)) - case FixedBytesTy: - refSlice = reflect.ValueOf([][]byte(nil)) - default: // no other types are supported - return nil, fmt.Errorf("abi: unsupported slice type %v", elem.T) - } - - var slice []byte - var size int - var offset int - if t.Type.IsSlice { - // get the offset which determines the start of this array ... - offset = int(binary.BigEndian.Uint64(output[index+24 : index+32])) - if offset+32 > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32) - } - - slice = output[offset:] - // ... starting with the size of the array in elements ... - size = int(binary.BigEndian.Uint64(slice[24:32])) - slice = slice[32:] - // ... and make sure that we've at the very least the amount of bytes - // available in the buffer. - if size*32 > len(slice) { - return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), offset+32+size*32) - } - - // reslice to match the required size - slice = slice[:size*32] - } else if t.Type.IsArray { - //get the number of elements in the array - size = t.Type.SliceSize - - //check to make sure array size matches up - if index+32*size > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), index+32*size) - } - //slice is there for a fixed amount of times - slice = output[index : index+size*32] - } - - for i := 0; i < size; i++ { - var ( - inter interface{} // interface type - returnOutput = slice[i*32 : i*32+32] // the return output - ) - // set inter to the correct type (cast) - switch elem.T { - case IntTy, UintTy: - inter = readInteger(t.Type.Kind, returnOutput) - case BoolTy: - inter = !allZero(returnOutput) - case AddressTy: - inter = common.BytesToAddress(returnOutput) - case HashTy: - inter = common.BytesToHash(returnOutput) - case FixedBytesTy: - inter = returnOutput - } - // append the item to our reflect slice - refSlice = reflect.Append(refSlice, reflect.ValueOf(inter)) - } - - // return the interface - return refSlice.Interface(), nil -} - -func readInteger(kind reflect.Kind, b []byte) interface{} { - switch kind { - case reflect.Uint8: - return uint8(b[len(b)-1]) - case reflect.Uint16: - return binary.BigEndian.Uint16(b[len(b)-2:]) - case reflect.Uint32: - return binary.BigEndian.Uint32(b[len(b)-4:]) - case reflect.Uint64: - return binary.BigEndian.Uint64(b[len(b)-8:]) - case reflect.Int8: - return int8(b[len(b)-1]) - case reflect.Int16: - return int16(binary.BigEndian.Uint16(b[len(b)-2:])) - case reflect.Int32: - return int32(binary.BigEndian.Uint32(b[len(b)-4:])) - case reflect.Int64: - return int64(binary.BigEndian.Uint64(b[len(b)-8:])) - default: - return new(big.Int).SetBytes(b) - } -} - -func allZero(b []byte) bool { - for _, byte := range b { - if byte != 0 { - return false - } - } - return true -} - -// toGoType parses the input and casts it to the proper type defined by the ABI -// argument in T. -func toGoType(i int, t Argument, output []byte) (interface{}, error) { - // we need to treat slices differently - if (t.Type.IsSlice || t.Type.IsArray) && t.Type.T != BytesTy && t.Type.T != StringTy && t.Type.T != FixedBytesTy && t.Type.T != FunctionTy { - return toGoSlice(i, t, output) - } - - index := i * 32 - if index+32 > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) - } - - // Parse the given index output and check whether we need to read - // a different offset and length based on the type (i.e. string, bytes) - var returnOutput []byte - switch t.Type.T { - case StringTy, BytesTy: // variable arrays are written at the end of the return bytes - // parse offset from which we should start reading - offset := int(binary.BigEndian.Uint64(output[index+24 : index+32])) - if offset+32 > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32) - } - // parse the size up until we should be reading - size := int(binary.BigEndian.Uint64(output[offset+24 : offset+32])) - if offset+32+size > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+size) - } - - // get the bytes for this return value - returnOutput = output[offset+32 : offset+32+size] - default: - returnOutput = output[index : index+32] - } - - // convert the bytes to whatever is specified by the ABI. - switch t.Type.T { - case IntTy, UintTy: - return readInteger(t.Type.Kind, returnOutput), nil - case BoolTy: - return !allZero(returnOutput), nil - case AddressTy: - return common.BytesToAddress(returnOutput), nil - case HashTy: - return common.BytesToHash(returnOutput), nil - case BytesTy, FixedBytesTy, FunctionTy: - return returnOutput, nil - case StringTy: - return string(returnOutput), nil - } - return nil, fmt.Errorf("abi: unknown type %v", t.Type.T) -} - // these variable are used to determine certain types during type assertion for // assignment. var ( @@ -282,108 +89,24 @@ var ( ) // Unpack output in v according to the abi specification -func (abi ABI) Unpack(v interface{}, name string, output []byte) error { - var isEvent bool - - // first look for name in the methods map, otherwise look in the events map, since there will - // be a name collision for one ABI if there are two of the same names. - // If we can't find it, error. - method, ok := abi.Methods[name] - if !ok { - method, ok = abi.Events[name] - if !ok { - return fmt.Errorf("abi: could not find requested method or event %v", name) - } - isEvent = true - } +func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { if len(output) == 0 { return fmt.Errorf("abi: unmarshalling empty output") } - // make sure the passed value is a pointer - valueOf := reflect.ValueOf(v) - if reflect.Ptr != valueOf.Kind() { - return fmt.Errorf("abi: Unpack(non-pointer %T)", v) - } - - var ( - value = valueOf.Elem() - typ = value.Type() - ) - - if len(method.Outputs) > 1 { - switch value.Kind() { - // struct will match named return values to the struct's field - // names - case reflect.Struct: - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) - if err != nil { - return err - } - reflectValue := reflect.ValueOf(marshalledValue) - - for j := 0; j < typ.NumField(); j++ { - field := typ.Field(j) - // TODO read tags: `abi:"fieldName"` - if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] { - if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil { - return err - } - } - } - } - case reflect.Slice: - if !value.Type().AssignableTo(r_interSlice) { - return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v) - } - - // if the slice already contains values, set those instead of the interface slice itself. - if value.Len() > 0 { - if len(method.Outputs) > value.Len() { - return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(method.Outputs), value.Len()) - } - - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) - if err != nil { - return err - } - reflectValue := reflect.ValueOf(marshalledValue) - if err := set(value.Index(i).Elem(), reflectValue, method.Outputs[i]); err != nil { - return err - } - } - return nil - } - - // create a new slice and start appending the unmarshalled - // values to the new interface slice. - z := reflect.MakeSlice(typ, 0, len(method.Outputs)) - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) - if err != nil { - return err - } - z = reflect.Append(z, reflect.ValueOf(marshalledValue)) - } - value.Set(z) - default: - return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ) - } - + // first look for name in the methods map, otherwise look in the events map, since there will + // be a name collision for one ABI if there are two of the same names. + // If we can't find it, error. + if method, ok := abi.Methods[name]; ok { + err = method.unpack(v, output) + } else if event, ok := abi.Events[name]; ok { + err = event.unpackLog(v, output) } else { - marshalledValue, err := toGoType(0, method.Outputs[0], output) - if err != nil { - return err - } - if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil { - return err - } + err = fmt.Errorf("abi: could not find requested method or event %v", name) } - return nil + return } func (abi *ABI) UnmarshalJSON(data []byte) error { diff --git a/accounts/abi/method.go b/accounts/abi/method.go index d56f3bc3dd..b4dcbbafe0 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -39,7 +39,7 @@ type Method struct { Outputs []Argument } -func (m Method) pack(method Method, args ...interface{}) ([]byte, error) { +func (method Method) pack(args ...interface{}) ([]byte, error) { // Make sure arguments match up and pack them if len(args) != len(method.Inputs) { return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs)) @@ -77,6 +77,91 @@ func (m Method) pack(method Method, args ...interface{}) ([]byte, error) { return ret, nil } +func (method Method) unpack(v interface{}, output []byte) error { + // make sure the passed value is a pointer + valueOf := reflect.ValueOf(v) + if reflect.Ptr != valueOf.Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + + var ( + value = valueOf.Elem() + typ = value.Type() + ) + + if len(method.Outputs) > 1 { + switch value.Kind() { + // struct will match named return values to the struct's field + // names + case reflect.Struct: + for i := 0; i < len(method.Outputs); i++ { + marshalledValue, err := toGoType(i, method.Outputs[i], output) + if err != nil { + return err + } + reflectValue := reflect.ValueOf(marshalledValue) + + for j := 0; j < typ.NumField(); j++ { + field := typ.Field(j) + // TODO read tags: `abi:"fieldName"` + if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] { + if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil { + return err + } + } + } + } + case reflect.Slice: + if !value.Type().AssignableTo(r_interSlice) { + return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v) + } + + // if the slice already contains values, set those instead of the interface slice itself. + if value.Len() > 0 { + if len(method.Outputs) > value.Len() { + return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(method.Outputs), value.Len()) + } + + for i := 0; i < len(method.Outputs); i++ { + marshalledValue, err := toGoType(i, method.Outputs[i], output) + if err != nil { + return err + } + reflectValue := reflect.ValueOf(marshalledValue) + if err := set(value.Index(i).Elem(), reflectValue, method.Outputs[i]); err != nil { + return err + } + } + return nil + } + + // create a new slice and start appending the unmarshalled + // values to the new interface slice. + z := reflect.MakeSlice(typ, 0, len(method.Outputs)) + for i := 0; i < len(method.Outputs); i++ { + marshalledValue, err := toGoType(i, method.Outputs[i], output) + if err != nil { + return err + } + z = reflect.Append(z, reflect.ValueOf(marshalledValue)) + } + value.Set(z) + default: + return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ) + } + + } else { + marshalledValue, err := toGoType(0, method.Outputs[0], output) + if err != nil { + return err + } + if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil { + return err + } + } + +} + // Sig returns the methods string signature according to the ABI spec. // // Example diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 7970ba8ac1..969c8d17c3 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -54,8 +54,9 @@ func reflectIntKind(unsigned bool, size int) reflect.Kind { return reflect.Uint64 } return reflect.Int64 + default: + return reflect.Ptr } - return reflect.Ptr } // mustArrayToBytesSlice creates a new byte slice with the exact same size as value diff --git a/accounts/abi/unpacking.go b/accounts/abi/unpacking.go new file mode 100644 index 0000000000..a617b48199 --- /dev/null +++ b/accounts/abi/unpacking.go @@ -0,0 +1,202 @@ +package abi + +import ( + "encoding/binary" + "fmt" + "reflect" + + "github.com/ethereum/go-ethereum/common" +) + +// toGoSliceType parses the input and casts it to the proper slice defined by the ABI +// argument in T. +func toGoSlice(i int, t Argument, output []byte) (interface{}, error) { + index := i * 32 + // The slice must, at very least be large enough for the index+32 which is exactly the size required + // for the [offset in output, size of offset]. + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32) + } + elem := t.Type.Elem + + // first we need to create a slice of the type + var refSlice reflect.Value + switch elem.T { + case IntTy, UintTy, BoolTy: + // create a new reference slice matching the element type + switch t.Type.Kind { + case reflect.Bool: + refSlice = reflect.ValueOf([]bool(nil)) + case reflect.Uint8: + refSlice = reflect.ValueOf([]uint8(nil)) + case reflect.Uint16: + refSlice = reflect.ValueOf([]uint16(nil)) + case reflect.Uint32: + refSlice = reflect.ValueOf([]uint32(nil)) + case reflect.Uint64: + refSlice = reflect.ValueOf([]uint64(nil)) + case reflect.Int8: + refSlice = reflect.ValueOf([]int8(nil)) + case reflect.Int16: + refSlice = reflect.ValueOf([]int16(nil)) + case reflect.Int32: + refSlice = reflect.ValueOf([]int32(nil)) + case reflect.Int64: + refSlice = reflect.ValueOf([]int64(nil)) + default: + refSlice = reflect.ValueOf([]*big.Int(nil)) + } + case AddressTy: // address must be of slice Address + refSlice = reflect.ValueOf([]common.Address(nil)) + case HashTy: // hash must be of slice hash + refSlice = reflect.ValueOf([]common.Hash(nil)) + case FixedBytesTy: + refSlice = reflect.ValueOf([][]byte(nil)) + default: // no other types are supported + return nil, fmt.Errorf("abi: unsupported slice type %v", elem.T) + } + + var slice []byte + var size int + var offset int + if t.Type.IsSlice { + // get the offset which determines the start of this array ... + offset = int(binary.BigEndian.Uint64(output[index+24 : index+32])) + if offset+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32) + } + + slice = output[offset:] + // ... starting with the size of the array in elements ... + size = int(binary.BigEndian.Uint64(slice[24:32])) + slice = slice[32:] + // ... and make sure that we've at the very least the amount of bytes + // available in the buffer. + if size*32 > len(slice) { + return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), offset+32+size*32) + } + + // reslice to match the required size + slice = slice[:size*32] + } else if t.Type.IsArray { + //get the number of elements in the array + size = t.Type.SliceSize + + //check to make sure array size matches up + if index+32*size > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), index+32*size) + } + //slice is there for a fixed amount of times + slice = output[index : index+size*32] + } + + for i := 0; i < size; i++ { + var ( + inter interface{} // interface type + returnOutput = slice[i*32 : i*32+32] // the return output + ) + // set inter to the correct type (cast) + switch elem.T { + case IntTy, UintTy: + inter = readInteger(t.Type.Kind, returnOutput) + case BoolTy: + inter = !allZero(returnOutput) + case AddressTy: + inter = common.BytesToAddress(returnOutput) + case HashTy: + inter = common.BytesToHash(returnOutput) + case FixedBytesTy: + inter = returnOutput + } + // append the item to our reflect slice + refSlice = reflect.Append(refSlice, reflect.ValueOf(inter)) + } + + // return the interface + return refSlice.Interface(), nil +} + +func readInteger(kind reflect.Kind, b []byte) interface{} { + switch kind { + case reflect.Uint8: + return uint8(b[len(b)-1]) + case reflect.Uint16: + return binary.BigEndian.Uint16(b[len(b)-2:]) + case reflect.Uint32: + return binary.BigEndian.Uint32(b[len(b)-4:]) + case reflect.Uint64: + return binary.BigEndian.Uint64(b[len(b)-8:]) + case reflect.Int8: + return int8(b[len(b)-1]) + case reflect.Int16: + return int16(binary.BigEndian.Uint16(b[len(b)-2:])) + case reflect.Int32: + return int32(binary.BigEndian.Uint32(b[len(b)-4:])) + case reflect.Int64: + return int64(binary.BigEndian.Uint64(b[len(b)-8:])) + default: + return new(big.Int).SetBytes(b) + } +} + +func allZero(b []byte) bool { + for _, byte := range b { + if byte != 0 { + return false + } + } + return true +} + +// toGoType parses the input and casts it to the proper type defined by the ABI +// argument in T. +func toGoType(i int, t Argument, output []byte) (interface{}, error) { + // we need to treat slices differently + if (t.Type.IsSlice || t.Type.IsArray) && t.Type.T != BytesTy && t.Type.T != StringTy && t.Type.T != FixedBytesTy && t.Type.T != FunctionTy { + return toGoSlice(i, t, output) + } + + index := i * 32 + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) + } + + // Parse the given index output and check whether we need to read + // a different offset and length based on the type (i.e. string, bytes) + var returnOutput []byte + switch t.Type.T { + case StringTy, BytesTy: // variable arrays are written at the end of the return bytes + // parse offset from which we should start reading + offset := int(binary.BigEndian.Uint64(output[index+24 : index+32])) + if offset+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32) + } + // parse the size up until we should be reading + size := int(binary.BigEndian.Uint64(output[offset+24 : offset+32])) + if offset+32+size > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+size) + } + + // get the bytes for this return value + returnOutput = output[offset+32 : offset+32+size] + default: + returnOutput = output[index : index+32] + } + + // convert the bytes to whatever is specified by the ABI. + switch t.Type.T { + case IntTy, UintTy: + return readInteger(t.Type.Kind, returnOutput), nil + case BoolTy: + return !allZero(returnOutput), nil + case AddressTy: + return common.BytesToAddress(returnOutput), nil + case HashTy: + return common.BytesToHash(returnOutput), nil + case BytesTy, FixedBytesTy, FunctionTy: + return returnOutput, nil + case StringTy: + return string(returnOutput), nil + } + return nil, fmt.Errorf("abi: unknown type %v", t.Type.T) +} From e4dce0b1312f88b9cc496f2481f946d5dbfd5154 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Sun, 11 Jun 2017 12:20:05 -0500 Subject: [PATCH 4/5] accounts/abi: added a parsing file and added some notes and building blocks. Might be slightly inaccurate, needs testing. Signed-off-by: RJ Catalano --- accounts/abi/parsing.go | 48 +++++++++++++++++++++++++++++++++++++++ accounts/abi/type.go | 2 +- accounts/abi/unpacking.go | 1 + 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 accounts/abi/parsing.go diff --git a/accounts/abi/parsing.go b/accounts/abi/parsing.go new file mode 100644 index 0000000000..66911154c6 --- /dev/null +++ b/accounts/abi/parsing.go @@ -0,0 +1,48 @@ +// Copyright 2016 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 . + +package abi + +import ( + "encoding/binary" + "fmt" +) + +// separates byte slice into a slice of 32 byte slices. +func chunkBytes(output []byte) (chunked [][32]byte) { + for i, j := 0, 0; i < len(output); i, j = i+32, j+1 { + chunked[j] = output[i : i+32] + } + return +} + +// check to see that the bytes are correctly formatted to 32 byte slices +func bytesAreProper(output []byte) error { + if len(output)%32 != 0 { + return fmt.Errorf("abi: improper length of byte slice detected in output.") + } + return nil +} + +// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. +func offsetPointsTo(offset [32]byte) uint { + return uint(binary.BigEndian.Uint64(offset[24:32])) / 32 +} + +// gives the starting and ending indices in the chunked byte slice for the values of an array +func parseSliceSize(size [32]byte, currentLength uint) (start uint, end uint) { + return currentLength + 1, currentLength + uint(binary.BigEndian.Uint64(size[24:32])/32) +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go index f2832aef56..a9b26e0cae 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -33,7 +33,7 @@ const ( FixedBytesTy BytesTy HashTy - FixedpointTy + FixedPointTy FunctionTy ) diff --git a/accounts/abi/unpacking.go b/accounts/abi/unpacking.go index a617b48199..d2c7b0802f 100644 --- a/accounts/abi/unpacking.go +++ b/accounts/abi/unpacking.go @@ -139,6 +139,7 @@ func readInteger(kind reflect.Kind, b []byte) interface{} { } } +// todo: this is inefficient for a bool, just look at the last cell, save yourself 32 iterations func allZero(b []byte) bool { for _, byte := range b { if byte != 0 { From 886b51c97658d25c71dbdd9f1810fdd286257474 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Sun, 11 Jun 2017 14:49:44 -0500 Subject: [PATCH 5/5] accounts/abi: various fixes throughout the package on the path to compilation. Signed-off-by: RJ Catalano --- accounts/abi/abi.go | 6 ++---- accounts/abi/parsing.go | 2 +- accounts/abi/type.go | 19 ++++++++++++------- accounts/abi/unpacking.go | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 1682e8e2c1..ada9b82286 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -17,13 +17,10 @@ package abi import ( - "encoding/binary" "encoding/json" "fmt" "io" - "math/big" "reflect" - "strings" "github.com/ethereum/go-ethereum/common" ) @@ -101,7 +98,8 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { if method, ok := abi.Methods[name]; ok { err = method.unpack(v, output) } else if event, ok := abi.Events[name]; ok { - err = event.unpackLog(v, output) + //err = event.unpackLog(v, output) // to create + err = fmt.Errorf("abi: could not find requested method or event %v", name) } else { err = fmt.Errorf("abi: could not find requested method or event %v", name) } diff --git a/accounts/abi/parsing.go b/accounts/abi/parsing.go index 66911154c6..f6d12f8d77 100644 --- a/accounts/abi/parsing.go +++ b/accounts/abi/parsing.go @@ -24,7 +24,7 @@ import ( // separates byte slice into a slice of 32 byte slices. func chunkBytes(output []byte) (chunked [][32]byte) { for i, j := 0, 0; i < len(output); i, j = i+32, j+1 { - chunked[j] = output[i : i+32] + copy(chunked[j][:], output[i:i+32]) } return } diff --git a/accounts/abi/type.go b/accounts/abi/type.go index a9b26e0cae..005f284972 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -29,6 +29,7 @@ const ( BoolTy StringTy SliceTy + //StructTy AddressTy FixedBytesTy BytesTy @@ -39,15 +40,19 @@ const ( // Type is the reflection of the supported argument type type Type struct { - IsSlice, IsArray bool - SliceSize int + // Slice descriptions + IsArray bool //todo: change to IsStatic + IsSlice bool //todo: change to IsDynamic + IsDoublySliced bool //todo: find a better name + SliceSize int + // If applicable (struct, slice), the underlying type Elem *Type - Kind reflect.Kind - Type reflect.Type - Size int - T byte // Our own type checking + Kind reflect.Kind // corresponding go Kind. + Type reflect.Type // corresponding go Type. + Size int // type size (denotes uint256, uint248, etc.) + T byte // Our own type checking stringKind string // holds the unparsed string for deriving signatures } @@ -65,7 +70,7 @@ var ( // string int uint fixed // string32 int8 uint8 uint[] // address int256 uint256 fixed128x128[2] - fullTypeRegex = regexp.MustCompile(`([a-zA-Z0-9]+)(\[([0-9]*)\])?`) + fullTypeRegex = regexp.MustCompile(`([a-zA-Z0-9]+)(\[([0-9]*)\])?(\[([0-9]*)\])?`) // typeRegex parses the abi sub types typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?") ) diff --git a/accounts/abi/unpacking.go b/accounts/abi/unpacking.go index d2c7b0802f..6189cc37b1 100644 --- a/accounts/abi/unpacking.go +++ b/accounts/abi/unpacking.go @@ -3,6 +3,7 @@ package abi import ( "encoding/binary" "fmt" + "math/big" "reflect" "github.com/ethereum/go-ethereum/common"