From 4c9cd438f8c8f6a9dec95a6ff0787ebfcef38ff7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 21 Nov 2023 21:46:21 +0300 Subject: [PATCH] rpcbinding: sort named types to stabilize output Same input -> same output, otherwise tests fail and @AnnaShaleva is annoyed. Signed-off-by: Roman Khimov --- .../testdata/rpcbindings/types/config.yml | 2 +- .../rpcbindings/types/rpcbindings.out | 95 +++++++++++++++++++ .../types/rpcbindings_dynamic_hash.out | 95 +++++++++++++++++++ .../testdata/rpcbindings/types/types.go | 14 +++ pkg/smartcontract/rpcbinding/binding.go | 36 ++++--- 5 files changed, 226 insertions(+), 16 deletions(-) diff --git a/cli/smartcontract/testdata/rpcbindings/types/config.yml b/cli/smartcontract/testdata/rpcbindings/types/config.yml index 98cb40d18..863043bc3 100644 --- a/cli/smartcontract/testdata/rpcbindings/types/config.yml +++ b/cli/smartcontract/testdata/rpcbindings/types/config.yml @@ -1,3 +1,3 @@ name: "Types" sourceurl: https://github.com/nspcc-dev/neo-go/ -safemethods: ["bool", "int", "bytes", "string", "any", "hash160", "hash256", "publicKey", "signature", "bools", "ints", "bytess", "strings", "hash160s", "hash256s", "publicKeys", "signatures", "aAAStrings", "maps", "crazyMaps", "anyMaps"] +safemethods: ["bool", "int", "bytes", "string", "any", "hash160", "hash256", "publicKey", "signature", "bools", "ints", "bytess", "strings", "hash160s", "hash256s", "publicKeys", "signatures", "aAAStrings", "maps", "crazyMaps", "anyMaps", "unnamedStructs", "unnamedStructsX"] diff --git a/cli/smartcontract/testdata/rpcbindings/types/rpcbindings.out b/cli/smartcontract/testdata/rpcbindings/types/rpcbindings.out index 4a46652ab..2bfab59a5 100644 --- a/cli/smartcontract/testdata/rpcbindings/types/rpcbindings.out +++ b/cli/smartcontract/testdata/rpcbindings/types/rpcbindings.out @@ -18,6 +18,17 @@ import ( // Hash contains contract hash. var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} +// Unnamed is a contract-specific unnamed type used by its methods. +type Unnamed struct { + I *big.Int +} + +// UnnamedX is a contract-specific unnamedX type used by its methods. +type UnnamedX struct { + I *big.Int + B bool +} + // Invoker is used by ContractReader to call various safe methods. type Invoker interface { Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) @@ -347,3 +358,87 @@ func (c *ContractReader) String(s string) (string, error) { func (c *ContractReader) Strings(s []string) ([]string, error) { return unwrap.ArrayOfUTF8Strings(c.invoker.Call(c.hash, "strings", s)) } + +// UnnamedStructs invokes `unnamedStructs` method of contract. +func (c *ContractReader) UnnamedStructs() (*Unnamed, error) { + return itemToUnnamed(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructs"))) +} + +// UnnamedStructsX invokes `unnamedStructsX` method of contract. +func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) { + return itemToUnnamedX(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructsX"))) +} + +// itemToUnnamed converts stack item into *Unnamed. +func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) { + if err != nil { + return nil, err + } + var res = new(Unnamed) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of Unnamed from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *Unnamed) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + return nil +} + +// itemToUnnamedX converts stack item into *UnnamedX. +func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) { + if err != nil { + return nil, err + } + var res = new(UnnamedX) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of UnnamedX from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *UnnamedX) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/rpcbindings/types/rpcbindings_dynamic_hash.out b/cli/smartcontract/testdata/rpcbindings/types/rpcbindings_dynamic_hash.out index fbd348691..749c12b8d 100755 --- a/cli/smartcontract/testdata/rpcbindings/types/rpcbindings_dynamic_hash.out +++ b/cli/smartcontract/testdata/rpcbindings/types/rpcbindings_dynamic_hash.out @@ -15,6 +15,17 @@ import ( "unicode/utf8" ) +// Unnamed is a contract-specific unnamed type used by its methods. +type Unnamed struct { + I *big.Int +} + +// UnnamedX is a contract-specific unnamedX type used by its methods. +type UnnamedX struct { + I *big.Int + B bool +} + // Invoker is used by ContractReader to call various safe methods. type Invoker interface { Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) @@ -343,3 +354,87 @@ func (c *ContractReader) String(s string) (string, error) { func (c *ContractReader) Strings(s []string) ([]string, error) { return unwrap.ArrayOfUTF8Strings(c.invoker.Call(c.hash, "strings", s)) } + +// UnnamedStructs invokes `unnamedStructs` method of contract. +func (c *ContractReader) UnnamedStructs() (*Unnamed, error) { + return itemToUnnamed(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructs"))) +} + +// UnnamedStructsX invokes `unnamedStructsX` method of contract. +func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) { + return itemToUnnamedX(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructsX"))) +} + +// itemToUnnamed converts stack item into *Unnamed. +func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) { + if err != nil { + return nil, err + } + var res = new(Unnamed) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of Unnamed from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *Unnamed) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + return nil +} + +// itemToUnnamedX converts stack item into *UnnamedX. +func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) { + if err != nil { + return nil, err + } + var res = new(UnnamedX) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of UnnamedX from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *UnnamedX) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/rpcbindings/types/types.go b/cli/smartcontract/testdata/rpcbindings/types/types.go index fcf91a0be..fdba19917 100644 --- a/cli/smartcontract/testdata/rpcbindings/types/types.go +++ b/cli/smartcontract/testdata/rpcbindings/types/types.go @@ -87,3 +87,17 @@ func CrazyMaps(m map[int][]map[string][]interop.Hash160) map[int][]map[string][] func AnyMaps(m map[int]any) map[int]any { return m } + +func UnnamedStructs() struct{ I int } { + return struct{ I int }{I: 123} +} + +func UnnamedStructsX() struct { + I int + B bool +} { + return struct { + I int + B bool + }{I: 123, B: true} +} diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index c35d9c179..de801a5de 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -124,9 +124,9 @@ import ( // Hash contains contract hash. var Hash = {{ .Hash }} {{end -}} -{{- range $name, $typ := .NamedTypes }} -// {{toTypeName $name}} is a contract-specific {{$name}} type used by its methods. -type {{toTypeName $name}} struct { +{{- range $index, $typ := .NamedTypes }} +// {{toTypeName $typ.Name}} is a contract-specific {{$typ.Name}} type used by its methods. +type {{toTypeName $typ.Name}} struct { {{- range $m := $typ.Fields}} {{ upperFirst .Field}} {{etTypeToStr .ExtendedType}} {{- end}} @@ -236,20 +236,20 @@ func New(actor Actor{{- if not (len .Hash) -}}, hash util.Uint160{{- end -}}) *C {{end -}} {{- range $m := .SafeMethods }}{{template "SAFEMETHOD" $m }}{{ end -}} {{- range $m := .Methods -}}{{template "METHOD" $m }}{{ end -}} -{{- range $name, $typ := .NamedTypes }} -// itemTo{{toTypeName $name}} converts stack item into *{{toTypeName $name}}. -func itemTo{{toTypeName $name}}(item stackitem.Item, err error) (*{{toTypeName $name}}, error) { +{{- range $index, $typ := .NamedTypes }} +// itemTo{{toTypeName $typ.Name}} converts stack item into *{{toTypeName $typ.Name}}. +func itemTo{{toTypeName $typ.Name}}(item stackitem.Item, err error) (*{{toTypeName $typ.Name}}, error) { if err != nil { return nil, err } - var res = new({{toTypeName $name}}) + var res = new({{toTypeName $typ.Name}}) err = res.FromStackItem(item) return res, err } -// FromStackItem retrieves fields of {{toTypeName $name}} from the given +// FromStackItem retrieves fields of {{toTypeName $typ.Name}} from the given // [stackitem.Item] or returns an error if it's not possible to do to so. -func (res *{{toTypeName $name}}) FromStackItem(item stackitem.Item) error { +func (res *{{toTypeName $typ.Name}}) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not an array") @@ -341,7 +341,7 @@ type ( SafeMethods []SafeMethodTmpl CustomEvents []CustomEventTemplate - NamedTypes map[string]binding.ExtendedType + NamedTypes []binding.ExtendedType IsNep11D bool IsNep11ND bool @@ -430,7 +430,13 @@ func Generate(cfg binding.Config) error { ctr.ContractTmpl = binding.TemplateFromManifest(cfg, scTypeToGo) ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo) - ctr.NamedTypes = cfg.NamedTypes + ctr.NamedTypes = make([]binding.ExtendedType, 0, len(cfg.NamedTypes)) + for k := range cfg.NamedTypes { + ctr.NamedTypes = append(ctr.NamedTypes, cfg.NamedTypes[k]) + } + sort.Slice(ctr.NamedTypes, func(i, j int) bool { + return strings.Compare(ctr.NamedTypes[i].Name, ctr.NamedTypes[j].Name) < 0 + }) // Check resulting named types and events don't have duplicating field names. for _, t := range ctr.NamedTypes { @@ -458,7 +464,7 @@ func Generate(cfg binding.Config) error { "addIndent": addIndent, "etTypeConverter": etTypeConverter, "etTypeToStr": func(et binding.ExtendedType) string { - r, _ := extendedTypeToGo(et, ctr.NamedTypes) + r, _ := extendedTypeToGo(et, cfg.NamedTypes) return r }, "toTypeName": toTypeName, @@ -719,7 +725,7 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st } } for _, et := range cfg.NamedTypes { - addETImports(et, ctr.NamedTypes, imports) + addETImports(et, cfg.NamedTypes, imports) } if len(cfg.NamedTypes) > 0 { imports["errors"] = struct{}{} @@ -746,7 +752,7 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st extType = binding.ExtendedType{ Base: abiEvent.Parameters[i].Type, } - addETImports(extType, ctr.NamedTypes, imports) + addETImports(extType, cfg.NamedTypes, imports) } eTmp.Parameters = append(eTmp.Parameters, EventParamTmpl{ ParamTmpl: binding.ParamTmpl{ @@ -817,7 +823,7 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st case "keys.PublicKeys": ctr.SafeMethods[i].Unwrapper = "ArrayOfPublicKeys" default: - addETImports(ctr.SafeMethods[i].ExtendedReturn, ctr.NamedTypes, imports) + addETImports(ctr.SafeMethods[i].ExtendedReturn, cfg.NamedTypes, imports) ctr.SafeMethods[i].Unwrapper = "Item" } }