From ce67e6795e573d070c86b9721cd68ed263734c10 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 1 Dec 2022 22:16:37 +0300 Subject: [PATCH] rpcbinding: properly support maps I'm not sure that map with a `*big.Int` key is a good one, but it can work this way. --- .../testdata/structs/rpcbindings.out | 87 ++++++++++++++++--- pkg/smartcontract/rpcbinding/binding.go | 26 ++++-- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/cli/smartcontract/testdata/structs/rpcbindings.out b/cli/smartcontract/testdata/structs/rpcbindings.out index 24e03b316..60c8e07ef 100644 --- a/cli/smartcontract/testdata/structs/rpcbindings.out +++ b/cli/smartcontract/testdata/structs/rpcbindings.out @@ -109,7 +109,7 @@ type ManagementGroup struct { type ManagementManifest struct { Name string Groups []*ManagementGroup - Features *stackitem.Map + Features map[string]string SupportedStandards []string ABI *ManagementABI Permissions []*ManagementPermission @@ -151,7 +151,7 @@ type StructsInternal struct { Sign []byte ArrOfBytes [][]byte ArrOfH160 []util.Uint160 - Map *stackitem.Map + Map map[*big.Int]keys.PublicKeys Struct *StructsInternal } // Invoker is used by ContractReader to call various safe methods. @@ -959,11 +959,42 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife } index++ - res.Features, err = func (item stackitem.Item) (*stackitem.Map, error) { - if t := item.Type(); t != stackitem.MapT { - return nil, fmt.Errorf("%s is not a map", t.String()) + res.Features, err = func (item stackitem.Item) (map[string]string, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) } - return item.(*stackitem.Map), nil + res := make(map[string]string) + for i := range m { + k, err := func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (m[i].Key) + if err != nil { + return nil, err + } + v, err := func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (m[i].Value) + if err != nil { + return nil, err + } + res[k] = v + } + return res, nil } (arr[index]) if err != nil { return nil, err @@ -1402,11 +1433,47 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er } index++ - res.Map, err = func (item stackitem.Item) (*stackitem.Map, error) { - if t := item.Type(); t != stackitem.MapT { - return nil, fmt.Errorf("%s is not a map", t.String()) + res.Map, err = func (item stackitem.Item) (map[*big.Int]keys.PublicKeys, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) } - return item.(*stackitem.Map), nil + res := make(map[*big.Int]keys.PublicKeys) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, err + } + v, err := func (item stackitem.Item) (keys.PublicKeys, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make(keys.PublicKeys, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + } (arr[i]) + if err != nil { + return nil, err + } + } + return res, nil + } (m[i].Value) + if err != nil { + return nil, err + } + res[k] = v + } + return res, nil } (arr[index]) if err != nil { return nil, err diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 1e9b40f14..6b10a5163 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -378,7 +378,9 @@ func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.Extended return "[]interface{}", "" case smartcontract.MapType: - return "*stackitem.Map", "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + kt, _ := extendedTypeToGo(binding.ExtendedType{Base: et.Key}, named) + vt, _ := extendedTypeToGo(*et.Value, named) + return "map[" + kt + "]" + vt, "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" case smartcontract.InteropInterfaceType: return "interface{}", "" case smartcontract.VoidType: @@ -472,11 +474,25 @@ func etTypeConverter(et binding.ExtendedType, v string) string { }, v) case smartcontract.MapType: - return `func (item stackitem.Item) (*stackitem.Map, error) { - if t := item.Type(); t != stackitem.MapT { - return nil, fmt.Errorf("%s is not a map", t.String()) + at, _ := extendedTypeToGo(et, nil) + return `func (item stackitem.Item) (` + at + `, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) } - return item.(*stackitem.Map), nil + res := make(` + at + `) + for i := range m { + k, err := ` + strings.ReplaceAll(etTypeConverter(binding.ExtendedType{Base: et.Key}, "m[i].Key"), "\n", "\n\t\t") + ` + if err != nil { + return nil, err + } + v, err := ` + strings.ReplaceAll(etTypeConverter(*et.Value, "m[i].Value"), "\n", "\n\t\t") + ` + if err != nil { + return nil, err + } + res[k] = v + } + return res, nil } (` + v + `)` case smartcontract.InteropInterfaceType: return "item.Value(), nil"