rpcbinding: sort named types to stabilize output

Same input -> same output, otherwise tests fail and @AnnaShaleva is annoyed.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
Roman Khimov 2023-11-21 21:46:21 +03:00
parent eade327b9b
commit 4c9cd438f8
5 changed files with 226 additions and 16 deletions

View file

@ -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"]

View file

@ -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
}

View file

@ -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
}

View file

@ -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}
}

View file

@ -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"
}
}