diff options
author | Yondon Fu <yondon.fu@gmail.com> | 2017-12-19 06:17:41 +0800 |
---|---|---|
committer | Yondon Fu <yondon.fu@gmail.com> | 2017-12-19 06:17:41 +0800 |
commit | 3857cdc267e3192697f561df0a0f827f65dfb6b5 (patch) | |
tree | 401c52c4972a68229ea283a394a0b0a5f3cfdc8e /accounts | |
parent | a5330fe0c569b75cb8a524f60f7e8dc06498262b (diff) | |
parent | fe070ab5c32702033489f1b9d1655ea1b894c29e (diff) | |
download | dexon-3857cdc267e3192697f561df0a0f827f65dfb6b5.tar.gz dexon-3857cdc267e3192697f561df0a0f827f65dfb6b5.tar.zst dexon-3857cdc267e3192697f561df0a0f827f65dfb6b5.zip |
Merge branch 'master' into abi-offset-fixed-arrays
Diffstat (limited to 'accounts')
-rw-r--r-- | accounts/abi/abi.go | 115 | ||||
-rw-r--r-- | accounts/abi/abi_test.go | 21 | ||||
-rw-r--r-- | accounts/abi/bind/backends/simulated.go | 39 | ||||
-rw-r--r-- | accounts/abi/bind/bind.go | 2 | ||||
-rw-r--r-- | accounts/abi/error.go | 24 | ||||
-rw-r--r-- | accounts/abi/event.go | 91 | ||||
-rw-r--r-- | accounts/abi/event_test.go | 2 | ||||
-rw-r--r-- | accounts/abi/method.go | 79 | ||||
-rw-r--r-- | accounts/abi/numbers.go | 47 | ||||
-rw-r--r-- | accounts/abi/pack.go | 7 | ||||
-rw-r--r-- | accounts/abi/pack_test.go | 7 | ||||
-rw-r--r-- | accounts/abi/reflect.go | 10 | ||||
-rw-r--r-- | accounts/abi/type.go | 210 | ||||
-rw-r--r-- | accounts/abi/type_test.go | 215 | ||||
-rw-r--r-- | accounts/abi/unpack.go | 281 | ||||
-rw-r--r-- | accounts/abi/unpack_test.go | 641 | ||||
-rw-r--r-- | accounts/keystore/account_cache.go | 113 | ||||
-rw-r--r-- | accounts/keystore/account_cache_test.go | 12 | ||||
-rw-r--r-- | accounts/keystore/file_cache.go | 102 | ||||
-rw-r--r-- | accounts/keystore/keystore_passphrase.go | 7 | ||||
-rw-r--r-- | accounts/keystore/presale.go | 3 | ||||
-rw-r--r-- | accounts/keystore/watch.go | 6 | ||||
-rw-r--r-- | accounts/manager.go | 10 |
23 files changed, 1086 insertions, 958 deletions
diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 2a06d474b..205dc300b 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -20,10 +20,6 @@ import ( "encoding/json" "fmt" "io" - "reflect" - "strings" - - "github.com/ethereum/go-ethereum/common" ) // The ABI holds information about a contract's context and available @@ -76,106 +72,27 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return append(method.Id(), arguments...), nil } -// 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)) -) - // 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] - - 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) +func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { + if err = bytesAreProper(output); err != nil { + return err } - - 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) - } - + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + var unpack unpacker + if method, ok := abi.Methods[name]; ok { + unpack = method + } else if event, ok := abi.Events[name]; ok { + unpack = event } 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 - } + return fmt.Errorf("abi: could not locate named method or event.") } - return nil + // requires a struct to unpack into for a tuple return... + if unpack.isTupleReturn() { + return unpack.tupleUnpack(v, output) + } + return unpack.singleUnpack(v, output) } func (abi *ABI) UnmarshalJSON(data []byte) error { diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 5420eb19a..2ae7488a4 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -29,25 +29,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -// formatSilceOutput add padding to the value and adds a size -func formatSliceOutput(v ...[]byte) []byte { - off := common.LeftPadBytes(big.NewInt(int64(len(v))).Bytes(), 32) - output := append(off, make([]byte, 0, len(v)*32)...) - - for _, value := range v { - output = append(output, common.LeftPadBytes(value, 32)...) - } - return output -} - -// quick helper padding -func pad(input []byte, size int, left bool) []byte { - if left { - return common.LeftPadBytes(input, size) - } - return common.RightPadBytes(input, size) -} - const jsondata = ` [ { "type" : "function", "name" : "balance", "constant" : true }, @@ -191,7 +172,7 @@ func TestMethodSignature(t *testing.T) { t.Errorf("expected ids to match %x != %x", m.Id(), idexp) } - uintt, _ := NewType("uint") + uintt, _ := NewType("uint256") m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil} exp = "foo(uint256)" if m.Sig() != exp { diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 0621e81c2..09288d401 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -41,6 +41,7 @@ import ( var _ bind.ContractBackend = (*SimulatedBackend)(nil) var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block") +var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction") // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in // the background. Its main purpose is to allow easily testing contract bindings. @@ -59,7 +60,7 @@ type SimulatedBackend struct { // for testing purposes. func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend { database, _ := ethdb.NewMemDatabase() - genesis := core.Genesis{Config: params.AllProtocolChanges, Alloc: alloc} + genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc} genesis.MustCommit(database) blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{}) backend := &SimulatedBackend{database: database, blockchain: blockchain, config: genesis.Config} @@ -203,32 +204,46 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs b.mu.Lock() defer b.mu.Unlock() - // Binary search the gas requirement, as it may be higher than the amount used + // Determine the lowest and highest possible gas limits to binary search in between var ( - lo uint64 = params.TxGas - 1 - hi uint64 + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 ) if call.Gas != nil && call.Gas.Uint64() >= params.TxGas { hi = call.Gas.Uint64() } else { hi = b.pendingBlock.GasLimit().Uint64() } - for lo+1 < hi { - // Take a guess at the gas, and check transaction validity - mid := (hi + lo) / 2 - call.Gas = new(big.Int).SetUint64(mid) + cap = hi + + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) bool { + call.Gas = new(big.Int).SetUint64(gas) snapshot := b.pendingState.Snapshot() _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) b.pendingState.RevertToSnapshot(snapshot) - // If the transaction became invalid or execution failed, raise the gas limit if err != nil || failed { + return false + } + return true + } + // Execute the binary search and hone in on an executable gas limit + for lo+1 < hi { + mid := (hi + lo) / 2 + if !executable(mid) { lo = mid - continue + } else { + hi = mid + } + } + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + if !executable(hi) { + return nil, errGasEstimationFailed } - // Otherwise assume the transaction succeeded, lower the gas limit - hi = mid } return new(big.Int).SetUint64(hi), nil } diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index f58758088..4dce79b77 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -129,7 +129,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La return string(code), nil } // For all others just return as is for now - return string(buffer.Bytes()), nil + return buffer.String(), nil } // bindType is a set of type binders that convert Solidity types to some supported diff --git a/accounts/abi/error.go b/accounts/abi/error.go index 420acf418..9d8674ad0 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -39,22 +39,23 @@ func formatSliceString(kind reflect.Kind, sliceSize int) string { // type in t. func sliceTypeCheck(t Type, val reflect.Value) error { if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { - return typeErr(formatSliceString(t.Kind, t.SliceSize), val.Type()) + return typeErr(formatSliceString(t.Kind, t.Size), val.Type()) } - if t.IsArray && val.Len() != t.SliceSize { - return typeErr(formatSliceString(t.Elem.Kind, t.SliceSize), formatSliceString(val.Type().Elem().Kind(), val.Len())) + + if t.T == ArrayTy && val.Len() != t.Size { + return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) } - if t.Elem.IsSlice { + if t.Elem.T == SliceTy { if val.Len() > 0 { return sliceTypeCheck(*t.Elem, val.Index(0)) } - } else if t.Elem.IsArray { + } else if t.Elem.T == ArrayTy { return sliceTypeCheck(*t.Elem, val.Index(0)) } if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.Kind { - return typeErr(formatSliceString(t.Elem.Kind, t.SliceSize), val.Type()) + return typeErr(formatSliceString(t.Elem.Kind, t.Size), val.Type()) } return nil } @@ -62,20 +63,19 @@ func sliceTypeCheck(t Type, val reflect.Value) error { // typeCheck checks that the given reflection value can be assigned to the reflection // type in t. func typeCheck(t Type, value reflect.Value) error { - if t.IsSlice || t.IsArray { + if t.T == SliceTy || t.T == ArrayTy { return sliceTypeCheck(t, value) } // Check base type validity. Element types will be checked later on. if t.Kind != value.Kind() { return typeErr(t.Kind, value.Kind()) + } else if t.T == FixedBytesTy && t.Size != value.Len() { + return typeErr(t.Type, value.Type()) + } else { + return nil } - return nil -} -// varErr returns a formatted error. -func varErr(expected, got reflect.Kind) error { - return typeErr(expected, got) } // typeErr returns a formatted type casting error. diff --git a/accounts/abi/event.go b/accounts/abi/event.go index 51ab84241..44ed7b8df 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -18,6 +18,7 @@ package abi import ( "fmt" + "reflect" "strings" "github.com/ethereum/go-ethereum/common" @@ -44,3 +45,93 @@ func (e Event) Id() common.Hash { } return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) } + +// unpacks an event return tuple into a struct of corresponding go types +// +// Unpacking can be done into a struct or a slice/array. +func (e Event) tupleUnpack(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 value.Kind() != reflect.Struct { + return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ) + } + + j := 0 + for i := 0; i < len(e.Inputs); i++ { + input := e.Inputs[i] + if input.Indexed { + // can't read, continue + continue + } else if input.Type.T == ArrayTy { + // need to move this up because they read sequentially + j += input.Type.Size + } + marshalledValue, err := toGoType((i+j)*32, input.Type, output) + if err != nil { + return err + } + reflectValue := reflect.ValueOf(marshalledValue) + + switch value.Kind() { + case reflect.Struct: + for j := 0; j < typ.NumField(); j++ { + field := typ.Field(j) + // TODO read tags: `abi:"fieldName"` + if field.Name == strings.ToUpper(e.Inputs[i].Name[:1])+e.Inputs[i].Name[1:] { + if err := set(value.Field(j), reflectValue, e.Inputs[i]); err != nil { + return err + } + } + } + case reflect.Slice, reflect.Array: + if value.Len() < i { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(e.Inputs), value.Len()) + } + v := value.Index(i) + if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface { + return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type()) + } + reflectValue := reflect.ValueOf(marshalledValue) + if err := set(v.Elem(), reflectValue, e.Inputs[i]); err != nil { + return err + } + default: + return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ) + } + } + return nil +} + +func (e Event) isTupleReturn() bool { return len(e.Inputs) > 1 } + +func (e Event) singleUnpack(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) + } + + if e.Inputs[0].Indexed { + return fmt.Errorf("abi: attempting to unpack indexed variable into element.") + } + + value := valueOf.Elem() + + marshalledValue, err := toGoType(0, e.Inputs[0].Type, output) + if err != nil { + return err + } + if err := set(value, reflect.ValueOf(marshalledValue), e.Inputs[0]); err != nil { + return err + } + return nil +} diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index b5054a032..7e2f13f76 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -31,7 +31,7 @@ func TestEventId(t *testing.T) { }{ { definition: `[ - { "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint" }] }, + { "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint256" }] }, { "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] } ]`, expectations: map[string]common.Hash{ diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 32077e8a6..609a71f07 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -88,6 +88,85 @@ func (method Method) pack(args ...interface{}) ([]byte, error) { return ret, nil } +// unpacks a method return tuple into a struct of corresponding go types +// +// Unpacking can be done into a struct or a slice/array. +func (method Method) tupleUnpack(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() + ) + + j := 0 + for i := 0; i < len(method.Outputs); i++ { + toUnpack := method.Outputs[i] + if toUnpack.Type.T == ArrayTy { + // need to move this up because they read sequentially + j += toUnpack.Type.Size + } + marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output) + if err != nil { + return err + } + reflectValue := reflect.ValueOf(marshalledValue) + + switch value.Kind() { + case reflect.Struct: + 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, reflect.Array: + if value.Len() < i { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(method.Outputs), value.Len()) + } + v := value.Index(i) + if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface { + return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type()) + } + reflectValue := reflect.ValueOf(marshalledValue) + if err := set(v.Elem(), reflectValue, method.Outputs[i]); err != nil { + return err + } + default: + return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ) + } + } + return nil +} + +func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 } + +func (method Method) singleUnpack(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) + } + + value := valueOf.Elem() + + marshalledValue, err := toGoType(0, method.Outputs[0].Type, output) + if err != nil { + return err + } + if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil { + return err + } + return nil +} + // Sig returns the methods string signature according to the ABI spec. // // Example diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index 5d3efff52..9ad99f90d 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -25,36 +25,23 @@ import ( ) var ( - big_t = reflect.TypeOf(big.Int{}) - ubig_t = reflect.TypeOf(big.Int{}) - byte_t = reflect.TypeOf(byte(0)) - byte_ts = reflect.TypeOf([]byte(nil)) - uint_t = reflect.TypeOf(uint(0)) - uint8_t = reflect.TypeOf(uint8(0)) - uint16_t = reflect.TypeOf(uint16(0)) - uint32_t = reflect.TypeOf(uint32(0)) - uint64_t = reflect.TypeOf(uint64(0)) - int_t = reflect.TypeOf(int(0)) - int8_t = reflect.TypeOf(int8(0)) - int16_t = reflect.TypeOf(int16(0)) - int32_t = reflect.TypeOf(int32(0)) - int64_t = reflect.TypeOf(int64(0)) - hash_t = reflect.TypeOf(common.Hash{}) - address_t = reflect.TypeOf(common.Address{}) - - uint_ts = reflect.TypeOf([]uint(nil)) - uint8_ts = reflect.TypeOf([]uint8(nil)) - uint16_ts = reflect.TypeOf([]uint16(nil)) - uint32_ts = reflect.TypeOf([]uint32(nil)) - uint64_ts = reflect.TypeOf([]uint64(nil)) - ubig_ts = reflect.TypeOf([]*big.Int(nil)) - - int_ts = reflect.TypeOf([]int(nil)) - int8_ts = reflect.TypeOf([]int8(nil)) - int16_ts = reflect.TypeOf([]int16(nil)) - int32_ts = reflect.TypeOf([]int32(nil)) - int64_ts = reflect.TypeOf([]int64(nil)) - big_ts = reflect.TypeOf([]*big.Int(nil)) + big_t = reflect.TypeOf(&big.Int{}) + derefbig_t = reflect.TypeOf(big.Int{}) + uint8_t = reflect.TypeOf(uint8(0)) + uint16_t = reflect.TypeOf(uint16(0)) + uint32_t = reflect.TypeOf(uint32(0)) + uint64_t = reflect.TypeOf(uint64(0)) + int_t = reflect.TypeOf(int(0)) + int8_t = reflect.TypeOf(int8(0)) + int16_t = reflect.TypeOf(int16(0)) + int32_t = reflect.TypeOf(int32(0)) + int64_t = reflect.TypeOf(int64(0)) + address_t = reflect.TypeOf(common.Address{}) + int_ts = reflect.TypeOf([]int(nil)) + int8_ts = reflect.TypeOf([]int8(nil)) + int16_ts = reflect.TypeOf([]int16(nil)) + int32_ts = reflect.TypeOf([]int32(nil)) + int64_ts = reflect.TypeOf([]int64(nil)) ) // U256 converts a big Int into a 256bit EVM number. diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go index 4d8a3f031..072e80536 100644 --- a/accounts/abi/pack.go +++ b/accounts/abi/pack.go @@ -61,8 +61,9 @@ func packElement(t Type, reflectValue reflect.Value) []byte { reflectValue = mustArrayToByteSlice(reflectValue) } return common.RightPadBytes(reflectValue.Bytes(), 32) + default: + panic("abi: fatal error") } - panic("abi: fatal error") } // packNum packs the given number (using the reflect value) and will cast it to appropriate number representation @@ -74,6 +75,8 @@ func packNum(value reflect.Value) []byte { return U256(big.NewInt(value.Int())) case reflect.Ptr: return U256(value.Interface().(*big.Int)) + default: + panic("abi: fatal error") } - return nil + } diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index c6cfb56ea..36401ee67 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -322,12 +322,12 @@ func TestPack(t *testing.T) { } { typ, err := NewType(test.typ) if err != nil { - t.Fatal("unexpected parse error:", err) + t.Fatalf("%v failed. Unexpected parse error: %v", i, err) } output, err := typ.pack(reflect.ValueOf(test.input)) if err != nil { - t.Fatal("unexpected pack error:", err) + t.Fatalf("%v failed. Unexpected pack error: %v", i, err) } if !bytes.Equal(output, test.output) { @@ -435,7 +435,4 @@ func TestPackNumber(t *testing.T) { t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed) } } - if packed := packNum(reflect.ValueOf("string")); packed != nil { - t.Errorf("expected 'string' to pack to nil. got %x instead", packed) - } } diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 8fa75df07..e953b77c1 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -24,7 +24,7 @@ import ( // indirect recursively dereferences the value until it either gets the value // or finds a big.Int func indirect(v reflect.Value) reflect.Value { - if v.Kind() == reflect.Ptr && v.Elem().Type() != big_t { + if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t { return indirect(v.Elem()) } return v @@ -73,15 +73,9 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value { func set(dst, src reflect.Value, output Argument) error { dstType := dst.Type() srcType := src.Type() - switch { - case dstType.AssignableTo(src.Type()): + case dstType.AssignableTo(srcType): dst.Set(src) - case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice: - if dst.Len() < output.Type.SliceSize { - return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.SliceSize, dst.Len()) - } - reflect.Copy(dst, src) case dstType.Kind() == reflect.Interface: dst.Set(src) case dstType.Kind() == reflect.Ptr: diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 5f20babb3..fba10b96d 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -21,6 +21,7 @@ import ( "reflect" "regexp" "strconv" + "strings" ) const ( @@ -29,6 +30,7 @@ const ( BoolTy StringTy SliceTy + ArrayTy AddressTy FixedBytesTy BytesTy @@ -39,9 +41,6 @@ const ( // Type is the reflection of the supported argument type type Type struct { - IsSlice, IsArray bool - SliceSize int - Elem *Type Kind reflect.Kind @@ -53,118 +52,116 @@ type Type struct { } var ( - // fullTypeRegex parses the abi types - // - // Types can be in the format of: - // - // Input = Type [ "[" [ Number ] "]" ] Name . - // Type = [ "u" ] "int" [ Number ] [ x ] [ Number ]. - // - // Examples: - // - // string int uint fixed - // string32 int8 uint8 uint[] - // address int256 uint256 fixed128x128[2] - fullTypeRegex = regexp.MustCompile(`([a-zA-Z0-9]+)(\[([0-9]*)\])?`) // typeRegex parses the abi sub types typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?") ) // NewType creates a new reflection type of abi type given in t. func NewType(t string) (typ Type, err error) { - res := fullTypeRegex.FindAllStringSubmatch(t, -1)[0] - // check if type is slice and parse type. - switch { - case res[3] != "": - // err is ignored. Already checked for number through the regexp - typ.SliceSize, _ = strconv.Atoi(res[3]) - typ.IsArray = true - case res[2] != "": - typ.IsSlice, typ.SliceSize = true, -1 - case res[0] == "": - return Type{}, fmt.Errorf("abi: type parse error: %s", t) + // check that array brackets are equal if they exist + if strings.Count(t, "[") != strings.Count(t, "]") { + return Type{}, fmt.Errorf("invalid arg type in abi") } - if typ.IsArray || typ.IsSlice { - sliceType, err := NewType(res[1]) + + typ.stringKind = t + + // if there are brackets, get ready to go into slice/array mode and + // recursively create the type + if strings.Count(t, "[") != 0 { + i := strings.LastIndex(t, "[") + // recursively embed the type + embeddedType, err := NewType(t[:i]) if err != nil { return Type{}, err } - typ.Elem = &sliceType - typ.stringKind = sliceType.stringKind + t[len(res[1]):] - // Although we know that this is an array, we cannot return - // as we don't know the type of the element, however, if it - // is still an array, then don't determine the type. - if typ.Elem.IsArray || typ.Elem.IsSlice { - return typ, nil - } - } - - // parse the type and size of the abi-type. - parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0] - // varSize is the size of the variable - var varSize int - if len(parsedType[3]) > 0 { - var err error - varSize, err = strconv.Atoi(parsedType[2]) - if err != nil { - return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + // grab the last cell and create a type from there + sliced := t[i:] + // grab the slice size with regexp + re := regexp.MustCompile("[0-9]+") + intz := re.FindAllString(sliced, -1) + + if len(intz) == 0 { + // is a slice + typ.T = SliceTy + typ.Kind = reflect.Slice + typ.Elem = &embeddedType + typ.Type = reflect.SliceOf(embeddedType.Type) + } else if len(intz) == 1 { + // is a array + typ.T = ArrayTy + typ.Kind = reflect.Array + typ.Elem = &embeddedType + typ.Size, err = strconv.Atoi(intz[0]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type) + } else { + return Type{}, fmt.Errorf("invalid formatting of array type") } - } - // varType is the parsed abi type - varType := parsedType[1] - // substitute canonical integer - if varSize == 0 && (varType == "int" || varType == "uint") { - varSize = 256 - t += "256" - } - - // only set stringKind if not array or slice, as for those, - // the correct string type has been set - if !(typ.IsArray || typ.IsSlice) { - typ.stringKind = t - } - - switch varType { - case "int": - typ.Kind, typ.Type = reflectIntKindAndType(false, varSize) - typ.Size = varSize - typ.T = IntTy - case "uint": - typ.Kind, typ.Type = reflectIntKindAndType(true, varSize) - typ.Size = varSize - typ.T = UintTy - case "bool": - typ.Kind = reflect.Bool - typ.T = BoolTy - case "address": - typ.Kind = reflect.Array - typ.Type = address_t - typ.Size = 20 - typ.T = AddressTy - case "string": - typ.Kind = reflect.String - typ.Size = -1 - typ.T = StringTy - case "bytes": - sliceType, _ := NewType("uint8") - typ.Elem = &sliceType - if varSize == 0 { - typ.IsSlice = true - typ.T = BytesTy - typ.SliceSize = -1 + return typ, err + } else { + // parse the type and size of the abi-type. + parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0] + // varSize is the size of the variable + var varSize int + if len(parsedType[3]) > 0 { + var err error + varSize, err = strconv.Atoi(parsedType[2]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } } else { - typ.IsArray = true - typ.T = FixedBytesTy - typ.SliceSize = varSize + if parsedType[0] == "uint" || parsedType[0] == "int" { + // this should fail because it means that there's something wrong with + // the abi type (the compiler should always format it to the size...always) + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + } + // varType is the parsed abi type + varType := parsedType[1] + + switch varType { + case "int": + typ.Kind, typ.Type = reflectIntKindAndType(false, varSize) + typ.Size = varSize + typ.T = IntTy + case "uint": + typ.Kind, typ.Type = reflectIntKindAndType(true, varSize) + typ.Size = varSize + typ.T = UintTy + case "bool": + typ.Kind = reflect.Bool + typ.T = BoolTy + typ.Type = reflect.TypeOf(bool(false)) + case "address": + typ.Kind = reflect.Array + typ.Type = address_t + typ.Size = 20 + typ.T = AddressTy + case "string": + typ.Kind = reflect.String + typ.Type = reflect.TypeOf("") + typ.T = StringTy + case "bytes": + if varSize == 0 { + typ.T = BytesTy + typ.Kind = reflect.Slice + typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0))) + } else { + typ.T = FixedBytesTy + typ.Kind = reflect.Array + typ.Size = varSize + typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0))) + } + case "function": + typ.Kind = reflect.Array + typ.T = FunctionTy + typ.Size = 24 + typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0))) + default: + return Type{}, fmt.Errorf("unsupported arg type: %s", t) } - case "function": - sliceType, _ := NewType("uint8") - typ.Elem = &sliceType - typ.IsArray = true - typ.T = FunctionTy - typ.SliceSize = 24 - default: - return Type{}, fmt.Errorf("unsupported arg type: %s", t) } return @@ -183,7 +180,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { return nil, err } - if (t.IsSlice || t.IsArray) && t.T != BytesTy && t.T != FixedBytesTy && t.T != FunctionTy { + if t.T == SliceTy || t.T == ArrayTy { var packed []byte for i := 0; i < v.Len(); i++ { @@ -193,18 +190,17 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { } packed = append(packed, val...) } - if t.IsSlice { + if t.T == SliceTy { return packBytesSlice(packed, v.Len()), nil - } else if t.IsArray { + } else if t.T == ArrayTy { return packed, nil } } - return packElement(t, v), nil } // requireLengthPrefix returns whether the type requires any sort of length // prefixing. func (t Type) requiresLengthPrefix() bool { - return t.T != FixedBytesTy && (t.T == StringTy || t.T == BytesTy || t.IsSlice) + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy } diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index 984a5bb4c..e55af1293 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -21,6 +21,7 @@ import ( "reflect" "testing" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" ) @@ -34,51 +35,58 @@ func TestTypeRegexp(t *testing.T) { blob string kind Type }{ - {"bool", Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}}, - {"bool[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Bool, T: BoolTy, Elem: &Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}}, - {"bool[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Bool, T: BoolTy, Elem: &Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}}, + {"bool", Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}}, + {"bool[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}}, + {"bool[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}}, + {"bool[2][]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}}, + {"bool[][]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}}, + {"bool[][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}}, + {"bool[2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}}, + {"bool[2][][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}}, + {"bool[2][2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}}, + {"bool[][][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}}, + {"bool[][2][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}}, {"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}}, {"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}}, {"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}}, {"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}}, {"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}}, - {"int8[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, - {"int8[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, - {"int16[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, - {"int16[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, - {"int32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, - {"int32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, - {"int64[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, - {"int64[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, - {"int256[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, - {"int256[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, + {"int8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, + {"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, + {"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, + {"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, + {"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, + {"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, + {"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, + {"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, + {"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, + {"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, {"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}}, {"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}}, {"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}}, {"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}}, {"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}}, - {"uint8[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, - {"uint8[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, - {"uint16[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, - {"uint16[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, - {"uint32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, - {"uint32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, - {"uint64[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, - {"uint64[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, - {"uint256[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, - {"uint256[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, - {"bytes32", Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}}, - {"bytes[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, - {"bytes[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}}, - {"bytes32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, - {"bytes32[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, - {"string", Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}}, - {"string[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[]"}}, - {"string[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[2]"}}, + {"uint8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, + {"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, + {"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, + {"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, + {"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, + {"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, + {"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, + {"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, + {"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, + {"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, + {"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}}, + {"bytes[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, + {"bytes[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}}, + {"bytes32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, + {"bytes32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, + {"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}}, + {"string[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}}, + {"string[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}}, {"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}}, - {"address[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Array, Type: address_t, T: AddressTy, Size: 20, Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, - {"address[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Array, Type: address_t, T: AddressTy, Size: 20, Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, - + {"address[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, + {"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, // TODO when fixed types are implemented properly // {"fixed", Type{}}, // {"fixed128x128", Type{}}, @@ -87,13 +95,14 @@ func TestTypeRegexp(t *testing.T) { // {"fixed128x128[]", Type{}}, // {"fixed128x128[2]", Type{}}, } - for i, tt := range tests { + + for _, tt := range tests { typ, err := NewType(tt.blob) if err != nil { - t.Errorf("type %d: failed to parse type string: %v", i, err) + t.Errorf("type %q: failed to parse type string: %v", tt.blob, err) } if !reflect.DeepEqual(typ, tt.kind) { - t.Errorf("type %d: parsed type mismatch:\n have %+v\n want %+v", i, typeWithoutStringer(typ), typeWithoutStringer(tt.kind)) + t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind))) } } } @@ -104,15 +113,90 @@ func TestTypeCheck(t *testing.T) { input interface{} err string }{ - {"uint", big.NewInt(1), ""}, - {"int", big.NewInt(1), ""}, - {"uint30", big.NewInt(1), ""}, + {"uint", big.NewInt(1), "unsupported arg type: uint"}, + {"int", big.NewInt(1), "unsupported arg type: int"}, + {"uint256", big.NewInt(1), ""}, + {"uint256[][3][]", [][3][]*big.Int{{{}}}, ""}, + {"uint256[][][3]", [3][][]*big.Int{{{}}}, ""}, + {"uint256[3][][]", [][][3]*big.Int{{{}}}, ""}, + {"uint256[3][3][3]", [3][3][3]*big.Int{{{}}}, ""}, + {"uint8[][]", [][]uint8{}, ""}, + {"int256", big.NewInt(1), ""}, + {"uint8", uint8(1), ""}, + {"uint16", uint16(1), ""}, + {"uint32", uint32(1), ""}, + {"uint64", uint64(1), ""}, + {"int8", int8(1), ""}, + {"int16", int16(1), ""}, + {"int32", int32(1), ""}, + {"int64", int64(1), ""}, + {"uint24", big.NewInt(1), ""}, + {"uint40", big.NewInt(1), ""}, + {"uint48", big.NewInt(1), ""}, + {"uint56", big.NewInt(1), ""}, + {"uint72", big.NewInt(1), ""}, + {"uint80", big.NewInt(1), ""}, + {"uint88", big.NewInt(1), ""}, + {"uint96", big.NewInt(1), ""}, + {"uint104", big.NewInt(1), ""}, + {"uint112", big.NewInt(1), ""}, + {"uint120", big.NewInt(1), ""}, + {"uint128", big.NewInt(1), ""}, + {"uint136", big.NewInt(1), ""}, + {"uint144", big.NewInt(1), ""}, + {"uint152", big.NewInt(1), ""}, + {"uint160", big.NewInt(1), ""}, + {"uint168", big.NewInt(1), ""}, + {"uint176", big.NewInt(1), ""}, + {"uint184", big.NewInt(1), ""}, + {"uint192", big.NewInt(1), ""}, + {"uint200", big.NewInt(1), ""}, + {"uint208", big.NewInt(1), ""}, + {"uint216", big.NewInt(1), ""}, + {"uint224", big.NewInt(1), ""}, + {"uint232", big.NewInt(1), ""}, + {"uint240", big.NewInt(1), ""}, + {"uint248", big.NewInt(1), ""}, + {"int24", big.NewInt(1), ""}, + {"int40", big.NewInt(1), ""}, + {"int48", big.NewInt(1), ""}, + {"int56", big.NewInt(1), ""}, + {"int72", big.NewInt(1), ""}, + {"int80", big.NewInt(1), ""}, + {"int88", big.NewInt(1), ""}, + {"int96", big.NewInt(1), ""}, + {"int104", big.NewInt(1), ""}, + {"int112", big.NewInt(1), ""}, + {"int120", big.NewInt(1), ""}, + {"int128", big.NewInt(1), ""}, + {"int136", big.NewInt(1), ""}, + {"int144", big.NewInt(1), ""}, + {"int152", big.NewInt(1), ""}, + {"int160", big.NewInt(1), ""}, + {"int168", big.NewInt(1), ""}, + {"int176", big.NewInt(1), ""}, + {"int184", big.NewInt(1), ""}, + {"int192", big.NewInt(1), ""}, + {"int200", big.NewInt(1), ""}, + {"int208", big.NewInt(1), ""}, + {"int216", big.NewInt(1), ""}, + {"int224", big.NewInt(1), ""}, + {"int232", big.NewInt(1), ""}, + {"int240", big.NewInt(1), ""}, + {"int248", big.NewInt(1), ""}, {"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"}, + {"uint8", uint16(1), "abi: cannot use uint16 as type uint8 as argument"}, + {"uint8", uint32(1), "abi: cannot use uint32 as type uint8 as argument"}, + {"uint8", uint64(1), "abi: cannot use uint64 as type uint8 as argument"}, + {"uint8", int8(1), "abi: cannot use int8 as type uint8 as argument"}, + {"uint8", int16(1), "abi: cannot use int16 as type uint8 as argument"}, + {"uint8", int32(1), "abi: cannot use int32 as type uint8 as argument"}, + {"uint8", int64(1), "abi: cannot use int64 as type uint8 as argument"}, {"uint16", uint16(1), ""}, {"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"}, {"uint16[]", []uint16{1, 2, 3}, ""}, {"uint16[]", [3]uint16{1, 2, 3}, ""}, - {"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type []uint16 as argument"}, + {"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"}, {"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"}, {"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"}, {"uint16[3]", []uint16{1, 2, 3}, ""}, @@ -122,20 +206,61 @@ func TestTypeCheck(t *testing.T) { {"address[1]", [1]common.Address{{1}}, ""}, {"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"}, {"bytes32", [32]byte{}, ""}, + {"bytes31", [31]byte{}, ""}, + {"bytes30", [30]byte{}, ""}, + {"bytes29", [29]byte{}, ""}, + {"bytes28", [28]byte{}, ""}, + {"bytes27", [27]byte{}, ""}, + {"bytes26", [26]byte{}, ""}, + {"bytes25", [25]byte{}, ""}, + {"bytes24", [24]byte{}, ""}, + {"bytes23", [23]byte{}, ""}, + {"bytes22", [22]byte{}, ""}, + {"bytes21", [21]byte{}, ""}, + {"bytes20", [20]byte{}, ""}, + {"bytes19", [19]byte{}, ""}, + {"bytes18", [18]byte{}, ""}, + {"bytes17", [17]byte{}, ""}, + {"bytes16", [16]byte{}, ""}, + {"bytes15", [15]byte{}, ""}, + {"bytes14", [14]byte{}, ""}, + {"bytes13", [13]byte{}, ""}, + {"bytes12", [12]byte{}, ""}, + {"bytes11", [11]byte{}, ""}, + {"bytes10", [10]byte{}, ""}, + {"bytes9", [9]byte{}, ""}, + {"bytes8", [8]byte{}, ""}, + {"bytes7", [7]byte{}, ""}, + {"bytes6", [6]byte{}, ""}, + {"bytes5", [5]byte{}, ""}, + {"bytes4", [4]byte{}, ""}, + {"bytes3", [3]byte{}, ""}, + {"bytes2", [2]byte{}, ""}, + {"bytes1", [1]byte{}, ""}, {"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"}, {"bytes32", common.Hash{1}, ""}, - {"bytes31", [31]byte{}, ""}, + {"bytes31", common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"}, {"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"}, {"bytes", []byte{0, 1}, ""}, - {"bytes", [2]byte{0, 1}, ""}, - {"bytes", common.Hash{1}, ""}, + {"bytes", [2]byte{0, 1}, "abi: cannot use array as type slice as argument"}, + {"bytes", common.Hash{1}, "abi: cannot use array as type slice as argument"}, {"string", "hello world", ""}, + {"string", string(""), ""}, + {"string", []byte{}, "abi: cannot use slice as type string as argument"}, {"bytes32[]", [][32]byte{{}}, ""}, {"function", [24]byte{}, ""}, + {"bytes20", common.Address{}, ""}, + {"address", [20]byte{}, ""}, + {"address", common.Address{}, ""}, } { typ, err := NewType(test.typ) - if err != nil { + if err != nil && len(test.err) == 0 { t.Fatal("unexpected parse error:", err) + } else if err != nil && len(test.err) != 0 { + if err.Error() != test.err { + t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + } + continue } err = typeCheck(typ, reflect.ValueOf(test.input)) diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index fc41c88ac..57732797b 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -25,122 +25,20 @@ import ( "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 - err error - ) - // set inter to the correct type (cast) - switch elem.T { - case IntTy, UintTy: - inter = readInteger(t.Type.Kind, returnOutput) - case BoolTy: - inter, err = readBool(returnOutput) - if err != nil { - return nil, err - } - 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 +// unpacker is a utility interface that enables us to have +// abstraction between events and methods and also to properly +// "unpack" them; e.g. events use Inputs, methods use Outputs. +type unpacker interface { + tupleUnpack(v interface{}, output []byte) error + singleUnpack(v interface{}, output []byte) error + isTupleReturn() bool } +// reads the integer based on its kind func readInteger(kind reflect.Kind, b []byte) interface{} { switch kind { case reflect.Uint8: - return uint8(b[len(b)-1]) + return b[len(b)-1] case reflect.Uint16: return binary.BigEndian.Uint16(b[len(b)-2:]) case reflect.Uint32: @@ -160,13 +58,10 @@ func readInteger(kind reflect.Kind, b []byte) interface{} { } } +// reads a bool func readBool(word []byte) (bool, error) { - if len(word) != 32 { - return false, fmt.Errorf("abi: fatal error: incorrect word length") - } - - for i, b := range word { - if b != 0 && i != 31 { + for _, b := range word[:31] { + if b != 0 { return false, errBadBool } } @@ -178,58 +73,144 @@ func readBool(word []byte) (bool, error) { default: return false, errBadBool } +} +// A function type is simply the address with the function selection signature at the end. +// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { + if t.T != FunctionTy { + return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array.") + } + if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { + err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) + } else { + copy(funcTy[:], word[0:24]) + } + return } -// 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) +// through reflection, creates a fixed array to be read from +func readFixedBytes(t Type, word []byte) (interface{}, error) { + if t.T != FixedBytesTy { + return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array.") } + // convert + array := reflect.New(t.Type).Elem() - 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) + reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) + return array.Interface(), nil + +} + +// iteratively unpack elements +func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { + if start+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), start+32*size) } - // 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) + // this value will become our slice or our array, depending on the type + var refSlice reflect.Value + slice := output[start : start+size*32] + + if t.T == SliceTy { + // declare our slice + refSlice = reflect.MakeSlice(t.Type, size, size) + } else if t.T == ArrayTy { + // declare our array + refSlice = reflect.New(t.Type).Elem() + } else { + return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") + } + + for i, j := start, 0; j*32 < len(slice); i, j = i+32, j+1 { + // this corrects the arrangement so that we get all the underlying array values + if t.Elem.T == ArrayTy && j != 0 { + i = start + t.Elem.Size*32*j } - // 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) + inter, err := toGoType(i, *t.Elem, output) + if err != nil { + return nil, err } + // append the item to our reflect slice + refSlice.Index(j).Set(reflect.ValueOf(inter)) + } - // get the bytes for this return value - returnOutput = output[offset+32 : offset+32+size] - default: + // return the interface + return refSlice.Interface(), nil +} + +// toGoType parses the output bytes and recursively assigns the value of these bytes +// into a go type with accordance with the ABI spec. +func toGoType(index int, t Type, output []byte) (interface{}, error) { + 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) + } + + var ( + returnOutput []byte + begin, end int + err error + ) + + // if we require a length prefix, find the beginning word and size returned. + if t.requiresLengthPrefix() { + begin, end, err = lengthPrefixPointsTo(index, output) + if err != nil { + return nil, err + } + } else { returnOutput = output[index : index+32] } - // convert the bytes to whatever is specified by the ABI. - switch t.Type.T { + switch t.T { + case SliceTy: + return forEachUnpack(t, output, begin, end) + case ArrayTy: + return forEachUnpack(t, output, index, t.Size) + case StringTy: // variable arrays are written at the end of the return bytes + return string(output[begin : begin+end]), nil case IntTy, UintTy: - return readInteger(t.Type.Kind, returnOutput), nil + return readInteger(t.Kind, returnOutput), nil case BoolTy: return readBool(returnOutput) 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 + case BytesTy: + return output[begin : begin+end], nil + case FixedBytesTy: + return readFixedBytes(t, returnOutput) + case FunctionTy: + return readFunctionType(t, returnOutput) + default: + return nil, fmt.Errorf("abi: unknown type %v", t.T) + } +} + +// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. +func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { + offset := int(binary.BigEndian.Uint64(output[index+24 : index+32])) + if offset+32 > len(output) { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32) + } + length = int(binary.BigEndian.Uint64(output[offset+24 : offset+32])) + if offset+32+length > len(output) { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+length) + } + start = offset + 32 + + //fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start) + return +} + +// checks for proper formatting of byte output +func bytesAreProper(output []byte) error { + if len(output) == 0 { + return fmt.Errorf("abi: unmarshalling empty output") + } else if len(output)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output") + } else { + return nil } - return nil, fmt.Errorf("abi: unknown type %v", t.Type.T) } diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 8e3afee4e..1e21aafc0 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -18,6 +18,7 @@ package abi import ( "bytes" + "encoding/hex" "fmt" "math/big" "reflect" @@ -27,263 +28,261 @@ import ( "github.com/ethereum/go-ethereum/common" ) -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 string // empty or error if expected - }{ - { - `[ { "type": "bool" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - bool(true), - "bool", - "", - }, - { - `[ { "type": "uint32" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - uint32(1), - "uint32", - "", - }, - { - `[ { "type": "uint32" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - nil, - "uint16", - "abi: cannot unmarshal uint32 in to uint16", - }, - { - `[ { "type": "uint17" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - nil, - "uint16", - "abi: cannot unmarshal *big.Int in to uint16", - }, - { - `[ { "type": "uint17" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - big.NewInt(1), - "*big.Int", - "", - }, - - { - `[ { "type": "int32" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - int32(1), - "int32", - "", - }, - { - `[ { "type": "int32" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - nil, - "int16", - "abi: cannot unmarshal int32 in to int16", - }, - { - `[ { "type": "int17" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - nil, - "int16", - "abi: cannot unmarshal *big.Int in to int16", - }, - { - `[ { "type": "int17" } ]`, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), - big.NewInt(1), - "*big.Int", - "", - }, - - { - `[ { "type": "address" } ]`, - common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"), - common.Address{1}, - "address", - "", - }, - { - `[ { "type": "bytes32" } ]`, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - "bytes", - "", - }, - { - `[ { "type": "bytes32" } ]`, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - "hash", - "", - }, - { - `[ { "type": "bytes32" } ]`, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - "interface", - "", - }, - { - `[ { "type": "function" } ]`, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - [24]byte{1}, - "function", - "", - }, - } { - 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 - } +type unpackTest struct { + def string // ABI definition JSON + enc string // evm return data + want interface{} // the expected output + err string // empty or error if expected +} - var outvar interface{} - switch test.outVar { - case "bool": - var v bool - err = abi.Unpack(&v, "method", test.marshalledOutput) - outvar = v - 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 "int8": - var v int8 - err = abi.Unpack(&v, "method", test.marshalledOutput) - outvar = v - case "int16": - var v int16 - err = abi.Unpack(&v, "method", test.marshalledOutput) - outvar = v - case "int32": - var v int32 - err = abi.Unpack(&v, "method", test.marshalledOutput) - outvar = v - case "int64": - var v int64 - 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.Bytes()[:] - case "function": - var v [24]byte - err = abi.Unpack(&v, "method", test.marshalledOutput) - outvar = v - case "interface": - err = abi.Unpack(&outvar, "method", test.marshalledOutput) - default: - t.Errorf("unsupported type '%v' please add it to the switch statement in this test", test.outVar) - continue +func (test unpackTest) checkError(err error) error { + if err != nil { + if len(test.err) == 0 { + return fmt.Errorf("expected no err but got: %v", err) + } else if err.Error() != test.err { + return fmt.Errorf("expected err: '%v' got err: %q", test.err, err) } + } else if len(test.err) > 0 { + return fmt.Errorf("expected err: %v but got none", test.err) + } + return nil +} - if err != nil && len(test.err) == 0 { - t.Errorf("%d failed. Expected no err but got: %v", i, err) - continue +var unpackTests = []unpackTest{ + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: true, + }, + { + def: `[{"type": "uint32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint32(1), + }, + { + def: `[{"type": "uint32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint16(0), + err: "abi: cannot unmarshal uint32 in to uint16", + }, + { + def: `[{"type": "uint17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint16(0), + err: "abi: cannot unmarshal *big.Int in to uint16", + }, + { + def: `[{"type": "uint17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: big.NewInt(1), + }, + { + def: `[{"type": "int32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int32(1), + }, + { + def: `[{"type": "int32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int16(0), + err: "abi: cannot unmarshal int32 in to int16", + }, + { + def: `[{"type": "int17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int16(0), + err: "abi: cannot unmarshal *big.Int in to int16", + }, + { + def: `[{"type": "int17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: big.NewInt(1), + }, + { + def: `[{"type": "address"}]`, + enc: "0000000000000000000000000100000000000000000000000000000000000000", + want: common.Address{1}, + }, + { + def: `[{"type": "bytes32"}]`, + enc: "0100000000000000000000000000000000000000000000000000000000000000", + want: [32]byte{1, 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}, + }, + { + def: `[{"type": "bytes"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + def: `[{"type": "bytes"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: [32]byte{}, + err: "abi: cannot unmarshal []uint8 in to [32]uint8", + }, + { + def: `[{"type": "bytes32"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: []byte(nil), + err: "abi: cannot unmarshal [32]uint8 in to []uint8", + }, + { + def: `[{"type": "bytes32"}]`, + enc: "0100000000000000000000000000000000000000000000000000000000000000", + want: common.HexToHash("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + def: `[{"type": "function"}]`, + enc: "0100000000000000000000000000000000000000000000000000000000000000", + want: [24]byte{1}, + }, + // slices + { + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint8{1, 2}, + }, + { + def: `[{"type": "uint8[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint8{1, 2}, + }, + // multi dimensional, if these pass, all types that don't require length prefix should pass + { + def: `[{"type": "uint8[][]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000E0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [][]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[2][2]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][2]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + want: [2][]uint8{{1}, {1}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [][2]uint8{{1, 2}}, + }, + { + def: `[{"type": "uint16[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint16{1, 2}, + }, + { + def: `[{"type": "uint16[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint16{1, 2}, + }, + { + def: `[{"type": "uint32[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint32{1, 2}, + }, + { + def: `[{"type": "uint32[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint32{1, 2}, + }, + { + def: `[{"type": "uint64[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint64{1, 2}, + }, + { + def: `[{"type": "uint64[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint64{1, 2}, + }, + { + def: `[{"type": "uint256[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "uint256[3]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + { + def: `[{"type": "int8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int8{1, 2}, + }, + { + def: `[{"type": "int8[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int8{1, 2}, + }, + { + def: `[{"type": "int16[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int16{1, 2}, + }, + { + def: `[{"type": "int16[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int16{1, 2}, + }, + { + def: `[{"type": "int32[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int32{1, 2}, + }, + { + def: `[{"type": "int32[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int32{1, 2}, + }, + { + def: `[{"type": "int64[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int64{1, 2}, + }, + { + def: `[{"type": "int64[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int64{1, 2}, + }, + { + def: `[{"type": "int256[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "int256[3]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, +} + +func TestUnpack(t *testing.T) { + for i, test := range unpackTests { + def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) } - if err == nil && len(test.err) != 0 { - t.Errorf("%d failed. Expected err: %v but got none", i, test.err) - continue + encb, err := hex.DecodeString(test.enc) + if err != nil { + t.Fatalf("invalid hex: %s" + test.enc) } - if err != nil && len(test.err) != 0 && err.Error() != test.err { - t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + outptr := reflect.New(reflect.TypeOf(test.want)) + err = abi.Unpack(outptr.Interface(), "method", encb) + if err := test.checkError(err); err != nil { + t.Errorf("test %d (%v) failed: %v", i, test.def, err) continue } - - if err == nil { - if !reflect.DeepEqual(test.expectedOut, outvar) { - t.Errorf("%d failed. Output error: expected %v, got %v", i, test.expectedOut, outvar) - } + out := outptr.Elem().Interface() + if !reflect.DeepEqual(test.want, out) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out) } } } -func TestUnpackSetInterfaceSlice(t *testing.T) { - var ( - var1 = new(uint8) - var2 = new(uint8) - ) - out := []interface{}{var1, var2} - abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint8"}, {"type":"uint8"}]}]`)) - if err != nil { - t.Fatal(err) - } - marshalledReturn := append(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")...) - err = abi.Unpack(&out, "ints", marshalledReturn) - if err != nil { - t.Fatal(err) - } - if *var1 != 1 { - t.Error("expected var1 to be 1, got", *var1) - } - if *var2 != 2 { - t.Error("expected var2 to be 2, got", *var2) - } - - out = []interface{}{var1} - err = abi.Unpack(&out, "ints", marshalledReturn) - - expErr := "abi: cannot marshal in to slices of unequal size (require: 2, got: 1)" - if err == nil || err.Error() != expErr { - t.Error("expected err:", expErr, "Got:", err) - } -} - -func TestUnpackSetInterfaceArrayOutput(t *testing.T) { - var ( - var1 = new([1]uint32) - var2 = new([1]uint32) - ) - out := []interface{}{var1, var2} - abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint32[1]"}, {"type":"uint32[1]"}]}]`)) - if err != nil { - t.Fatal(err) - } - marshalledReturn := append(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")...) - err = abi.Unpack(&out, "ints", marshalledReturn) - if err != nil { - t.Fatal(err) - } - - if *var1 != [1]uint32{1} { - t.Error("expected var1 to be [1], got", *var1) - } - if *var2 != [1]uint32{2} { - t.Error("expected var2 to be [2], got", *var2) - } -} - func TestMultiReturnWithStruct(t *testing.T) { const definition = `[ { "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` @@ -337,101 +336,6 @@ func TestMultiReturnWithStruct(t *testing.T) { } } -func TestMultiReturnWithSlice(t *testing.T) { - const definition = `[ - { "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` - - abi, err := JSON(strings.NewReader(definition)) - if err != nil { - t.Fatal(err) - } - - // using buff to make the code readable - buff := new(bytes.Buffer) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) - stringOut := "hello" - buff.Write(common.RightPadBytes([]byte(stringOut), 32)) - - var inter []interface{} - err = abi.Unpack(&inter, "multi", buff.Bytes()) - if err != nil { - t.Error(err) - } - - if len(inter) != 2 { - t.Fatal("expected 2 results got", len(inter)) - } - - if num, ok := inter[0].(*big.Int); !ok || num.Cmp(big.NewInt(1)) != 0 { - t.Error("expected index 0 to be 1 got", num) - } - - if str, ok := inter[1].(string); !ok || str != stringOut { - t.Error("expected index 1 to be", stringOut, "got", str) - } -} - -func TestMarshalArrays(t *testing.T) { - const definition = `[ - { "name" : "bytes32", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, - { "name" : "bytes10", "constant" : 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.Unpack(&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.Unpack(&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.Unpack(&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.Unpack(&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.Unpack(&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) { const definition = `[ { "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, @@ -450,6 +354,29 @@ func TestUnmarshal(t *testing.T) { } buff := new(bytes.Buffer) + // marshall mixed bytes (mixedBytes) + p0, p0Exp := []byte{}, common.Hex2Bytes("01020000000000000000") + p1, p1Exp := [32]byte{}, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff") + mixedBytes := []interface{}{&p0, &p1} + + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) + buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) + + err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes()) + if err != nil { + t.Error(err) + } else { + if !bytes.Equal(p0, p0Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0) + } + + if !bytes.Equal(p1[:], p1Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1) + } + } + // marshal int var Int *big.Int err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) @@ -473,6 +400,7 @@ func TestUnmarshal(t *testing.T) { } // marshal dynamic bytes max length 32 + buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) bytesOut := common.RightPadBytes([]byte("hello"), 32) @@ -504,11 +432,11 @@ func TestUnmarshal(t *testing.T) { t.Errorf("expected %x got %x", bytesOut, Bytes) } - // marshall dynamic bytes max length 63 + // marshall dynamic bytes max length 64 buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f")) - bytesOut = common.RightPadBytes([]byte("hello"), 63) + bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) @@ -516,8 +444,8 @@ func TestUnmarshal(t *testing.T) { t.Error(err) } - if !bytes.Equal(Bytes, bytesOut) { - t.Errorf("expected %x got %x", bytesOut, Bytes) + if !bytes.Equal(Bytes, bytesOut[:len(bytesOut)-1]) { + t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], Bytes) } // marshal dynamic bytes output empty @@ -569,29 +497,6 @@ func TestUnmarshal(t *testing.T) { t.Error("expected error") } - // marshal mixed bytes - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) - fixed := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001") - buff.Write(fixed) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - bytesOut = common.RightPadBytes([]byte("hello"), 32) - buff.Write(bytesOut) - - var out []interface{} - err = abi.Unpack(&out, "mixedBytes", buff.Bytes()) - if err != nil { - t.Fatal("didn't expect error:", err) - } - - if !bytes.Equal(bytesOut, out[0].([]byte)) { - t.Errorf("expected %x, got %x", bytesOut, out[0]) - } - - if !bytes.Equal(fixed, out[1].([]byte)) { - t.Errorf("expected %x, got %x", fixed, out[1]) - } - buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 4b08cc202..71f698ece 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -20,7 +20,6 @@ import ( "bufio" "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "sort" @@ -75,13 +74,6 @@ type accountCache struct { fileC fileCache } -// fileCache is a cache of files seen during scan of keystore -type fileCache struct { - all *set.SetNonTS // list of all files - mtime time.Time // latest mtime seen - mu sync.RWMutex -} - func newAccountCache(keydir string) (*accountCache, chan struct{}) { ac := &accountCache{ keydir: keydir, @@ -236,66 +228,22 @@ func (ac *accountCache) close() { ac.mu.Unlock() } -// scanFiles performs a new scan on the given directory, compares against the already -// cached filenames, and returns file sets: new, missing , modified -func (fc *fileCache) scanFiles(keyDir string) (set.Interface, set.Interface, set.Interface, error) { - t0 := time.Now() - files, err := ioutil.ReadDir(keyDir) - t1 := time.Now() - if err != nil { - return nil, nil, nil, err - } - fc.mu.RLock() - prevMtime := fc.mtime - fc.mu.RUnlock() - - filesNow := set.NewNonTS() - moddedFiles := set.NewNonTS() - var newMtime time.Time - for _, fi := range files { - modTime := fi.ModTime() - path := filepath.Join(keyDir, fi.Name()) - if skipKeyFile(fi) { - log.Trace("Ignoring file on account scan", "path", path) - continue - } - filesNow.Add(path) - if modTime.After(prevMtime) { - moddedFiles.Add(path) - } - if modTime.After(newMtime) { - newMtime = modTime - } - } - t2 := time.Now() - - fc.mu.Lock() - // Missing = previous - current - missing := set.Difference(fc.all, filesNow) - // New = current - previous - newFiles := set.Difference(filesNow, fc.all) - // Modified = modified - new - modified := set.Difference(moddedFiles, newFiles) - fc.all = filesNow - fc.mtime = newMtime - fc.mu.Unlock() - t3 := time.Now() - log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) - return newFiles, missing, modified, nil -} - // scanAccounts checks if any changes have occurred on the filesystem, and // updates the account cache accordingly func (ac *accountCache) scanAccounts() error { - newFiles, missingFiles, modified, err := ac.fileC.scanFiles(ac.keydir) - t1 := time.Now() + // Scan the entire folder metadata for file changes + creates, deletes, updates, err := ac.fileC.scan(ac.keydir) if err != nil { log.Debug("Failed to reload keystore contents", "err", err) return err } + if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 { + return nil + } + // Create a helper method to scan the contents of the key files var ( - buf = new(bufio.Reader) - keyJSON struct { + buf = new(bufio.Reader) + key struct { Address string `json:"address"` } ) @@ -308,9 +256,9 @@ func (ac *accountCache) scanAccounts() error { defer fd.Close() buf.Reset(fd) // Parse the address. - keyJSON.Address = "" - err = json.NewDecoder(buf).Decode(&keyJSON) - addr := common.HexToAddress(keyJSON.Address) + key.Address = "" + err = json.NewDecoder(buf).Decode(&key) + addr := common.HexToAddress(key.Address) switch { case err != nil: log.Debug("Failed to decode keystore key", "path", path, "err", err) @@ -321,47 +269,30 @@ func (ac *accountCache) scanAccounts() error { } return nil } + // Process all the file diffs + start := time.Now() - for _, p := range newFiles.List() { - path, _ := p.(string) - a := readAccount(path) - if a != nil { + for _, p := range creates.List() { + if a := readAccount(p.(string)); a != nil { ac.add(*a) } } - for _, p := range missingFiles.List() { - path, _ := p.(string) - ac.deleteByFile(path) + for _, p := range deletes.List() { + ac.deleteByFile(p.(string)) } - - for _, p := range modified.List() { - path, _ := p.(string) - a := readAccount(path) + for _, p := range updates.List() { + path := p.(string) ac.deleteByFile(path) - if a != nil { + if a := readAccount(path); a != nil { ac.add(*a) } } - - t2 := time.Now() + end := time.Now() select { case ac.notify <- struct{}{}: default: } - log.Trace("Handled keystore changes", "time", t2.Sub(t1)) - + log.Trace("Handled keystore changes", "time", end.Sub(start)) return nil } - -func skipKeyFile(fi os.FileInfo) bool { - // Skip editor backups and UNIX-style hidden files. - if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { - return true - } - // Skip misc special files, directories (yes, symlinks too). - if fi.IsDir() || fi.Mode()&os.ModeType != 0 { - return true - } - return false -} diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go index e3dc31065..fe9233c04 100644 --- a/accounts/keystore/account_cache_test.go +++ b/accounts/keystore/account_cache_test.go @@ -59,7 +59,7 @@ func TestWatchNewFile(t *testing.T) { // Ensure the watcher is started before adding any files. ks.Accounts() - time.Sleep(200 * time.Millisecond) + time.Sleep(1000 * time.Millisecond) // Move in the files. wantAccounts := make([]accounts.Account, len(cachetestAccounts)) @@ -349,6 +349,9 @@ func TestUpdatedKeyfileContents(t *testing.T) { return } + // needed so that modTime of `file` is different to its current value after forceCopyFile + time.Sleep(1000 * time.Millisecond) + // Now replace file contents if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { t.Fatal(err) @@ -362,6 +365,9 @@ func TestUpdatedKeyfileContents(t *testing.T) { return } + // needed so that modTime of `file` is different to its current value after forceCopyFile + time.Sleep(1000 * time.Millisecond) + // Now replace file contents again if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { t.Fatal(err) @@ -374,6 +380,10 @@ func TestUpdatedKeyfileContents(t *testing.T) { t.Error(err) return } + + // needed so that modTime of `file` is different to its current value after ioutil.WriteFile + time.Sleep(1000 * time.Millisecond) + // Now replace file contents with crap if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil { t.Fatal(err) diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go new file mode 100644 index 000000000..c91b7b7b6 --- /dev/null +++ b/accounts/keystore/file_cache.go @@ -0,0 +1,102 @@ +// Copyright 2017 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 <http://www.gnu.org/licenses/>. + +package keystore + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + set "gopkg.in/fatih/set.v0" +) + +// fileCache is a cache of files seen during scan of keystore. +type fileCache struct { + all *set.SetNonTS // Set of all files from the keystore folder + lastMod time.Time // Last time instance when a file was modified + mu sync.RWMutex +} + +// scan performs a new scan on the given directory, compares against the already +// cached filenames, and returns file sets: creates, deletes, updates. +func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) { + t0 := time.Now() + + // List all the failes from the keystore folder + files, err := ioutil.ReadDir(keyDir) + if err != nil { + return nil, nil, nil, err + } + t1 := time.Now() + + fc.mu.Lock() + defer fc.mu.Unlock() + + // Iterate all the files and gather their metadata + all := set.NewNonTS() + mods := set.NewNonTS() + + var newLastMod time.Time + for _, fi := range files { + // Skip any non-key files from the folder + path := filepath.Join(keyDir, fi.Name()) + if skipKeyFile(fi) { + log.Trace("Ignoring file on account scan", "path", path) + continue + } + // Gather the set of all and fresly modified files + all.Add(path) + + modified := fi.ModTime() + if modified.After(fc.lastMod) { + mods.Add(path) + } + if modified.After(newLastMod) { + newLastMod = modified + } + } + t2 := time.Now() + + // Update the tracked files and return the three sets + deletes := set.Difference(fc.all, all) // Deletes = previous - current + creates := set.Difference(all, fc.all) // Creates = current - previous + updates := set.Difference(mods, creates) // Updates = modified - creates + + fc.all, fc.lastMod = all, newLastMod + t3 := time.Now() + + // Report on the scanning stats and return + log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) + return creates, deletes, updates, nil +} + +// skipKeyFile ignores editor backups, hidden files and folders/symlinks. +func skipKeyFile(fi os.FileInfo) bool { + // Skip editor backups and UNIX-style hidden files. + if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { + return true + } + // Skip misc special files, directories (yes, symlinks too). + if fi.IsDir() || fi.Mode()&os.ModeType != 0 { + return true + } + return false +} diff --git a/accounts/keystore/keystore_passphrase.go b/accounts/keystore/keystore_passphrase.go index 535608a60..eaec39f7d 100644 --- a/accounts/keystore/keystore_passphrase.go +++ b/accounts/keystore/keystore_passphrase.go @@ -28,6 +28,7 @@ package keystore import ( "bytes" "crypto/aes" + crand "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" @@ -90,6 +91,12 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) return key, nil } +// StoreKey generates a key, encrypts with 'auth' and stores in the given directory +func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) { + _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth) + return a.Address, err +} + func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) if err != nil { diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go index ed900ad08..1554294e1 100644 --- a/accounts/keystore/presale.go +++ b/accounts/keystore/presale.go @@ -58,6 +58,9 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error if err != nil { return nil, errors.New("invalid hex in encSeed") } + if len(encSeedBytes) < 16 { + return nil, errors.New("invalid encSeed, too short") + } iv := encSeedBytes[:16] cipherText := encSeedBytes[16:] /* diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go index 602300b10..bbcfb9925 100644 --- a/accounts/keystore/watch.go +++ b/accounts/keystore/watch.go @@ -81,10 +81,14 @@ func (w *watcher) loop() { // When an event occurs, the reload call is delayed a bit so that // multiple events arriving quickly only cause a single reload. var ( - debounce = time.NewTimer(0) debounceDuration = 500 * time.Millisecond rescanTriggered = false + debounce = time.NewTimer(0) ) + // Ignore initial trigger + if !debounce.Stop() { + <-debounce.C + } defer debounce.Stop() for { select { diff --git a/accounts/manager.go b/accounts/manager.go index 78ddb1368..96ca298fc 100644 --- a/accounts/manager.go +++ b/accounts/manager.go @@ -41,6 +41,11 @@ type Manager struct { // NewManager creates a generic account manager to sign transaction via various // supported backends. func NewManager(backends ...Backend) *Manager { + // Retrieve the initial list of wallets from the backends and sort by URL + var wallets []Wallet + for _, backend := range backends { + wallets = merge(wallets, backend.Wallets()...) + } // Subscribe to wallet notifications from all backends updates := make(chan WalletEvent, 4*len(backends)) @@ -48,11 +53,6 @@ func NewManager(backends ...Backend) *Manager { for i, backend := range backends { subs[i] = backend.Subscribe(updates) } - // Retrieve the initial list of wallets from the backends and sort by URL - var wallets []Wallet - for _, backend := range backends { - wallets = merge(wallets, backend.Wallets()...) - } // Assemble the account manager and return am := &Manager{ backends: make(map[reflect.Type][]Backend), |