Эх сурвалжийг харах

Merge pull request #2210 from obscuren/abi-typed-array

accounts/abi: support for typed array
Jeffrey Wilcke 9 жил өмнө
parent
commit
4f28c5b69d

+ 39 - 12
accounts/abi/abi.go

@@ -165,7 +165,14 @@ func (abi ABI) Call(executer Executer, v interface{}, name string, args ...inter
 	return abi.unmarshal(v, name, executer(callData))
 	return abi.unmarshal(v, name, executer(callData))
 }
 }
 
 
-var interSlice = reflect.TypeOf([]interface{}{})
+// these variable are used to determine certain types during type assertion for
+// assignment.
+var (
+	r_interSlice = reflect.TypeOf([]interface{}{})
+	r_hash       = reflect.TypeOf(common.Hash{})
+	r_bytes      = reflect.TypeOf([]byte{})
+	r_byte       = reflect.TypeOf(byte(0))
+)
 
 
 // unmarshal output in v according to the abi specification
 // unmarshal output in v according to the abi specification
 func (abi ABI) unmarshal(v interface{}, name string, output []byte) error {
 func (abi ABI) unmarshal(v interface{}, name string, output []byte) error {
@@ -194,17 +201,14 @@ func (abi ABI) unmarshal(v interface{}, name string, output []byte) error {
 					field := typ.Field(j)
 					field := typ.Field(j)
 					// TODO read tags: `abi:"fieldName"`
 					// TODO read tags: `abi:"fieldName"`
 					if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
 					if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
-						if field.Type.AssignableTo(reflectValue.Type()) {
-							value.Field(j).Set(reflectValue)
-							break
-						} else {
-							return fmt.Errorf("abi: cannot unmarshal %v in to %v", field.Type, reflectValue.Type())
+						if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
+							return err
 						}
 						}
 					}
 					}
 				}
 				}
 			}
 			}
 		case reflect.Slice:
 		case reflect.Slice:
-			if !value.Type().AssignableTo(interSlice) {
+			if !value.Type().AssignableTo(r_interSlice) {
 				return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v)
 				return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v)
 			}
 			}
 
 
@@ -228,17 +232,40 @@ func (abi ABI) unmarshal(v interface{}, name string, output []byte) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		reflectValue := reflect.ValueOf(marshalledValue)
-		if typ.AssignableTo(reflectValue.Type()) {
-			value.Set(reflectValue)
-		} else {
-			return fmt.Errorf("abi: cannot unmarshal %v in to %v", reflectValue.Type(), value.Type())
+		if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
+			return err
 		}
 		}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
+// set attempts to assign src to dst by either setting, copying or otherwise.
+//
+// set is a bit more lenient when it comes to assignment and doesn't force an as
+// strict ruleset as bare `reflect` does.
+func set(dst, src reflect.Value, output Argument) error {
+	dstType := dst.Type()
+	srcType := src.Type()
+
+	switch {
+	case dstType.AssignableTo(src.Type()):
+		dst.Set(src)
+	case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice:
+		if !dstType.Elem().AssignableTo(r_byte) {
+			return fmt.Errorf("abi: cannot unmarshal %v in to array of elem %v", src.Type(), dstType.Elem())
+		}
+
+		if dst.Len() < output.Type.Size {
+			return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.Size, dst.Len())
+		}
+		reflect.Copy(dst, src)
+	default:
+		return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
+	}
+	return nil
+}
+
 func (abi *ABI) UnmarshalJSON(data []byte) error {
 func (abi *ABI) UnmarshalJSON(data []byte) error {
 	var fields []struct {
 	var fields []struct {
 		Type    string
 		Type    string

+ 75 - 31
accounts/abi/abi_test.go

@@ -394,37 +394,6 @@ func TestBytes(t *testing.T) {
 	}
 	}
 }
 }
 
 
-/*
-func TestReturn(t *testing.T) {
-	const definition = `[
-	{ "type" : "function", "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] },
-	{ "type" : "function", "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] }]`
-
-	abi, err := JSON(strings.NewReader(definition))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	r := abi.Call(func([]byte) []byte {
-		t := make([]byte, 32)
-		t[0] = 1
-		return t
-	}, "balance")
-	if _, ok := r.(common.Hash); !ok {
-		t.Errorf("expected type common.Hash, got %T", r)
-	}
-
-	r = abi.Call(func([]byte) []byte {
-		t := make([]byte, 32)
-		t[0] = 1
-		return t
-	}, "name")
-	if _, ok := r.(common.Address); !ok {
-		t.Errorf("expected type common.Address, got %T", r)
-	}
-}
-*/
-
 func TestDefaultFunctionParsing(t *testing.T) {
 func TestDefaultFunctionParsing(t *testing.T) {
 	const definition = `[{ "name" : "balance" }]`
 	const definition = `[{ "name" : "balance" }]`
 
 
@@ -550,11 +519,71 @@ func TestMultiReturnWithSlice(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestMarshalArrays(t *testing.T) {
+	const definition = `[
+	{ "name" : "bytes32", "const" : false, "outputs": [ { "type": "bytes32" } ] },
+	{ "name" : "bytes10", "const" : false, "outputs": [ { "type": "bytes10" } ] }
+	]`
+
+	abi, err := JSON(strings.NewReader(definition))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	output := common.LeftPadBytes([]byte{1}, 32)
+
+	var bytes10 [10]byte
+	err = abi.unmarshal(&bytes10, "bytes32", output)
+	if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
+		t.Error("expected error or bytes32 not be assignable to bytes10:", err)
+	}
+
+	var bytes32 [32]byte
+	err = abi.unmarshal(&bytes32, "bytes32", output)
+	if err != nil {
+		t.Error("didn't expect error:", err)
+	}
+	if !bytes.Equal(bytes32[:], output) {
+		t.Error("expected bytes32[31] to be 1 got", bytes32[31])
+	}
+
+	type (
+		B10 [10]byte
+		B32 [32]byte
+	)
+
+	var b10 B10
+	err = abi.unmarshal(&b10, "bytes32", output)
+	if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
+		t.Error("expected error or bytes32 not be assignable to bytes10:", err)
+	}
+
+	var b32 B32
+	err = abi.unmarshal(&b32, "bytes32", output)
+	if err != nil {
+		t.Error("didn't expect error:", err)
+	}
+	if !bytes.Equal(b32[:], output) {
+		t.Error("expected bytes32[31] to be 1 got", bytes32[31])
+	}
+
+	output[10] = 1
+	var shortAssignLong [32]byte
+	err = abi.unmarshal(&shortAssignLong, "bytes10", output)
+	if err != nil {
+		t.Error("didn't expect error:", err)
+	}
+	if !bytes.Equal(output, shortAssignLong[:]) {
+		t.Errorf("expected %x to be %x", shortAssignLong, output)
+	}
+}
+
 func TestUnmarshal(t *testing.T) {
 func TestUnmarshal(t *testing.T) {
 	const definition = `[
 	const definition = `[
 	{ "name" : "int", "const" : false, "outputs": [ { "type": "uint256" } ] },
 	{ "name" : "int", "const" : false, "outputs": [ { "type": "uint256" } ] },
 	{ "name" : "bool", "const" : false, "outputs": [ { "type": "bool" } ] },
 	{ "name" : "bool", "const" : false, "outputs": [ { "type": "bool" } ] },
 	{ "name" : "bytes", "const" : false, "outputs": [ { "type": "bytes" } ] },
 	{ "name" : "bytes", "const" : false, "outputs": [ { "type": "bytes" } ] },
+	{ "name" : "fixed", "const" : false, "outputs": [ { "type": "bytes32" } ] },
 	{ "name" : "multi", "const" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
 	{ "name" : "multi", "const" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
 	{ "name" : "mixedBytes", "const" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
 	{ "name" : "mixedBytes", "const" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
 
 
@@ -655,6 +684,21 @@ func TestUnmarshal(t *testing.T) {
 		t.Errorf("expected %x got %x", bytesOut, Bytes)
 		t.Errorf("expected %x got %x", bytesOut, Bytes)
 	}
 	}
 
 
+	// marshal dynamic bytes length 5
+	buff.Reset()
+	buff.Write(common.RightPadBytes([]byte("hello"), 32))
+
+	var hash common.Hash
+	err = abi.unmarshal(&hash, "fixed", buff.Bytes())
+	if err != nil {
+		t.Error(err)
+	}
+
+	helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32))
+	if hash != helloHash {
+		t.Errorf("Expected %x to equal %x", hash, helloHash)
+	}
+
 	// marshal error
 	// marshal error
 	buff.Reset()
 	buff.Reset()
 	buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
 	buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))