aboutsummaryrefslogtreecommitdiffstats
path: root/accounts/abi/abi.go
diff options
context:
space:
mode:
authorJeffrey Wilcke <geffobscura@gmail.com>2016-02-09 20:57:00 +0800
committerJeffrey Wilcke <geffobscura@gmail.com>2016-02-11 17:16:38 +0800
commitecc876cec0b4c70bfa5ff9939ac86715bf9579de (patch)
tree54538afc6ccd8f0ff7eb1a35e0caf54920f11046 /accounts/abi/abi.go
parent856b9e9c500dae60ed84e75d9577b2deb504558e (diff)
downloadgo-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.tar
go-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.tar.gz
go-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.tar.bz2
go-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.tar.lz
go-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.tar.xz
go-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.tar.zst
go-tangerine-ecc876cec0b4c70bfa5ff9939ac86715bf9579de.zip
accounts/abi: fixed return tuple and string, bytes return type parsing
Removed old unmarshalling of return types: `abi.Call(...).([]byte)`. This is now replaced by a new syntax: ``` var a []byte err := abi.Call(&a, ...) ``` It also addresses a few issues with Bytes and Strings and can also handle both fixed and arbitrary sized byte slices, including strings.
Diffstat (limited to 'accounts/abi/abi.go')
-rw-r--r--accounts/abi/abi.go150
1 files changed, 120 insertions, 30 deletions
diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go
index b84fd463a..2dc8039f5 100644
--- a/accounts/abi/abi.go
+++ b/accounts/abi/abi.go
@@ -20,11 +20,10 @@ import (
"encoding/json"
"fmt"
"io"
- "math"
+ "reflect"
+ "strings"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/logger"
- "github.com/ethereum/go-ethereum/logger/glog"
)
// Executer is an executer method for performing state executions. It takes one
@@ -101,52 +100,143 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
}
// toGoType parses the input and casts it to the proper type defined by the ABI
-// argument in t.
-func toGoType(t Argument, input []byte) interface{} {
+// argument in T.
+func toGoType(i int, t Argument, output []byte) (interface{}, error) {
+ 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(common.BytesToBig(output[index : index+32]).Uint64())
+ 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(common.BytesToBig(output[offset : offset+32]).Uint64())
+ 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]
+ }
+
+ // cast bytes to abi return type
switch t.Type.T {
case IntTy:
- return common.BytesToBig(input)
+ return common.BytesToBig(returnOutput), nil
case UintTy:
- return common.BytesToBig(input)
+ return common.BytesToBig(returnOutput), nil
case BoolTy:
- return common.BytesToBig(input).Uint64() > 0
+ return common.BytesToBig(returnOutput).Uint64() > 0, nil
case AddressTy:
- return common.BytesToAddress(input)
+ return common.BytesToAddress(returnOutput), nil
case HashTy:
- return common.BytesToHash(input)
+ return common.BytesToHash(returnOutput), nil
+ case BytesTy, FixedBytesTy:
+ return returnOutput, nil
+ case StringTy:
+ return string(returnOutput), nil
}
- return nil
+ return nil, fmt.Errorf("abi: unknown type %v", t.Type.T)
}
-// Call executes a call and attemps to parse the return values and returns it as
-// an interface. It uses the executer method to perform the actual call since
-// the abi knows nothing of the lower level calling mechanism.
+// Call will unmarshal the output of the call in v. It will return an error if
+// invalid type is given or if the output is too short to conform to the ABI
+// spec.
//
-// Call supports all abi types and includes multiple return values. When only
-// one item is returned a single interface{} will be returned, if a contract
-// method returns multiple values an []interface{} slice is returned.
-func (abi ABI) Call(executer Executer, name string, args ...interface{}) interface{} {
+// Call supports all of the available types and accepts a struct or an interface
+// slice if the return is a tuple.
+func (abi ABI) Call(executer Executer, v interface{}, name string, args ...interface{}) error {
callData, err := abi.Pack(name, args...)
if err != nil {
- glog.V(logger.Debug).Infoln("pack error:", err)
- return nil
+ return err
}
- output := executer(callData)
+ return abi.unmarshal(v, name, executer(callData))
+}
- method := abi.Methods[name]
- ret := make([]interface{}, int(math.Max(float64(len(method.Outputs)), float64(len(output)/32))))
- for i := 0; i < len(ret); i += 32 {
- index := i / 32
- ret[index] = toGoType(method.Outputs[index], output[i:i+32])
+var interSlice = reflect.TypeOf([]interface{}{})
+
+// unmarshal output in v according to the abi specification
+func (abi ABI) unmarshal(v interface{}, name string, output []byte) error {
+ var method = abi.Methods[name]
+
+ if len(output) == 0 {
+ return fmt.Errorf("abi: unmarshalling empty output")
}
- // return single interface
- if len(ret) == 1 {
- return ret[0]
+ value := reflect.ValueOf(v).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 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())
+ }
+ }
+ }
+ }
+ case reflect.Slice:
+ if !value.Type().AssignableTo(interSlice) {
+ return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v)
+ }
+
+ // 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
+ }
+ 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())
+ }
}
- return ret
+ return nil
}
func (abi *ABI) UnmarshalJSON(data []byte) error {