github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/rpcbinding/binding.go (about)

     1  package rpcbinding
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"text/template"
     8  	"unicode"
     9  
    10  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    11  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
    12  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    13  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
    14  	"github.com/nspcc-dev/neo-go/pkg/util"
    15  )
    16  
    17  // The set of constants containing parts of RPC binding template. Each block of code
    18  // including template definition and var/type/method definitions contain new line at the
    19  // start and ends with a new line. On adding new block of code to the template, please,
    20  // ensure that this block has new line at the start and in the end of the block.
    21  const (
    22  	eventDefinition = `{{ define "EVENT" }}
    23  // {{.Name}} represents "{{.ManifestName}}" event emitted by the contract.
    24  type {{.Name}} struct {
    25  	{{- range $index, $arg := .Parameters}}
    26  	{{ upperFirst .Name}} {{.Type}}
    27  	{{- end}}
    28  }
    29  {{ end }}`
    30  
    31  	safemethodDefinition = `{{ define "SAFEMETHOD" }}
    32  // {{.Name}} {{.Comment}}
    33  func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}}
    34  	{{- if ne $index 0}}, {{end}}
    35  		{{- .Name}} {{.Type}}
    36  	{{- end}}) {{if .ReturnType }}({{ .ReturnType }}, error) {
    37  	return {{if and (not .ItemTo) (eq .Unwrapper "Item")}}func(item stackitem.Item, err error) ({{ .ReturnType }}, error) {
    38  		if err != nil {
    39  			return nil, err
    40  		}
    41  		return {{addIndent (etTypeConverter .ExtendedReturn "item") "\t"}}
    42  	} ( {{- end -}} {{if .ItemTo -}} itemTo{{ .ItemTo }}( {{- end -}}
    43  			unwrap.{{.Unwrapper}}(c.invoker.Call(c.hash, "{{ .NameABI }}"
    44  		{{- range $arg := .Arguments -}}, {{.Name}}{{end -}} )) {{- if or .ItemTo (eq .Unwrapper "Item") -}} ) {{- end}}
    45  	{{- else -}} (*result.Invoke, error) {
    46  	c.invoker.Call(c.hash, "{{ .NameABI }}"
    47  		{{- range $arg := .Arguments -}}, {{.Name}}{{end}})
    48  	{{- end}}
    49  }
    50  {{ if eq .Unwrapper "SessionIterator" }}
    51  // {{.Name}}Expanded is similar to {{.Name}} (uses the same contract
    52  // method), but can be useful if the server used doesn't support sessions and
    53  // doesn't expand iterators. It creates a script that will get the specified
    54  // number of result items from the iterator right in the VM and return them to
    55  // you. It's only limited by VM stack and GAS available for RPC invocations.
    56  func (c *ContractReader) {{.Name}}Expanded({{range $index, $arg := .Arguments}}{{.Name}} {{.Type}}, {{end}}_numOfIteratorItems int) ([]stackitem.Item, error) {
    57  	return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "{{.NameABI}}", _numOfIteratorItems{{range $arg := .Arguments}}, {{.Name}}{{end}}))
    58  }
    59  {{ end }}{{ end }}`
    60  	methodDefinition = `{{ define "METHOD" }}{{ if eq .ReturnType "bool"}}
    61  func (c *Contract) scriptFor{{.Name}}({{range $index, $arg := .Arguments -}}
    62  	{{- if ne $index 0}}, {{end}}
    63  		{{- .Name}} {{.Type}}
    64  	{{- end}}) ([]byte, error) {
    65  	return smartcontract.CreateCallWithAssertScript(c.hash, "{{ .NameABI }}"{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}})
    66  }
    67  {{ end }}
    68  // {{.Name}} {{.Comment}}
    69  // This transaction is signed and immediately sent to the network.
    70  // The values returned are its hash, ValidUntilBlock value and error if any.
    71  func (c *Contract) {{.Name}}({{range $index, $arg := .Arguments -}}
    72  	{{- if ne $index 0}}, {{end}}
    73  		{{- .Name}} {{.Type}}
    74  	{{- end}}) (util.Uint256, uint32, error) {
    75  	{{if ne .ReturnType "bool"}}return c.actor.SendCall(c.hash, "{{ .NameABI }}"
    76  	{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := c.scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}})
    77  	if err != nil {
    78  		return util.Uint256{}, 0, err
    79  	}
    80  	return c.actor.SendRun(script){{end}}
    81  }
    82  
    83  // {{.Name}}Transaction {{.Comment}}
    84  // This transaction is signed, but not sent to the network, instead it's
    85  // returned to the caller.
    86  func (c *Contract) {{.Name}}Transaction({{range $index, $arg := .Arguments -}}
    87  	{{- if ne $index 0}}, {{end}}
    88  		{{- .Name}} {{.Type}}
    89  	{{- end}}) (*transaction.Transaction, error) {
    90  	{{if ne .ReturnType "bool"}}return c.actor.MakeCall(c.hash, "{{ .NameABI }}"
    91  	{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := c.scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return c.actor.MakeRun(script){{end}}
    96  }
    97  
    98  // {{.Name}}Unsigned {{.Comment}}
    99  // This transaction is not signed, it's simply returned to the caller.
   100  // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
   101  // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
   102  func (c *Contract) {{.Name}}Unsigned({{range $index, $arg := .Arguments -}}
   103  	{{- if ne $index 0}}, {{end}}
   104  		{{- .Name}} {{.Type}}
   105  	{{- end}}) (*transaction.Transaction, error) {
   106  	{{if ne .ReturnType "bool"}}return c.actor.MakeUnsignedCall(c.hash, "{{ .NameABI }}", nil
   107  	{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := c.scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}})
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	return c.actor.MakeUnsignedRun(script, nil){{end}}
   112  }
   113  {{end}}`
   114  
   115  	bindingDefinition = `// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
   116  
   117  // Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract.
   118  package {{.PackageName}}
   119  
   120  import (
   121  {{range $m := .Imports}}	"{{ $m }}"
   122  {{end}})
   123  {{if len .Hash}}
   124  // Hash contains contract hash.
   125  var Hash = {{ .Hash }}
   126  {{end -}}
   127  {{- range $index, $typ := .NamedTypes }}
   128  // {{toTypeName $typ.Name}} is a contract-specific {{$typ.Name}} type used by its methods.
   129  type {{toTypeName $typ.Name}} struct {
   130  {{- range $m := $typ.Fields}}
   131  	{{ upperFirst .Field}} {{etTypeToStr .ExtendedType}}
   132  {{- end}}
   133  }
   134  {{end}}
   135  {{- range $e := .CustomEvents }}{{template "EVENT" $e }}{{ end -}}
   136  {{- if .HasReader}}
   137  // Invoker is used by ContractReader to call various safe methods.
   138  type Invoker interface {
   139  {{if or .IsNep11D .IsNep11ND}}	nep11.Invoker
   140  {{else -}}
   141  {{ if .IsNep17}}	nep17.Invoker
   142  {{else if len .SafeMethods}}	Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
   143  {{end -}}
   144  {{if .HasIterator}}	CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error)
   145  	TerminateSession(sessionID uuid.UUID) error
   146  	TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
   147  {{end -}}
   148  {{end -}}
   149  }
   150  {{end -}}
   151  {{- if .HasWriter}}
   152  // Actor is used by Contract to call state-changing methods.
   153  type Actor interface {
   154  {{- if .HasReader}}
   155  	Invoker
   156  {{end}}
   157  {{- if or .IsNep11D .IsNep11ND}}
   158  	nep11.Actor
   159  {{else if .IsNep17}}
   160  	nep17.Actor
   161  {{end}}
   162  {{- if len .Methods}}
   163  	MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
   164  	MakeRun(script []byte) (*transaction.Transaction, error)
   165  	MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
   166  	MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
   167  	SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
   168  	SendRun(script []byte) (util.Uint256, uint32, error)
   169  {{end -}}
   170  }
   171  {{end -}}
   172  {{- if .HasReader}}
   173  // ContractReader implements safe contract methods.
   174  type ContractReader struct {
   175  	{{if .IsNep11D}}nep11.DivisibleReader
   176  	{{end -}}
   177  	{{if .IsNep11ND}}nep11.NonDivisibleReader
   178  	{{end -}}
   179  	{{if .IsNep17}}nep17.TokenReader
   180  	{{end -}}
   181  	invoker Invoker
   182  	hash util.Uint160
   183  }
   184  {{end -}}
   185  {{- if .HasWriter}}
   186  // Contract implements all contract methods.
   187  type Contract struct {
   188  	{{if .HasReader}}ContractReader
   189  	{{end -}}
   190  	{{if .IsNep11D}}nep11.DivisibleWriter
   191  	{{end -}}
   192  	{{if .IsNep11ND}}nep11.BaseWriter
   193  	{{end -}}
   194  	{{if .IsNep17}}nep17.TokenWriter
   195  	{{end -}}
   196  	actor Actor
   197  	hash util.Uint160
   198  }
   199  {{end -}}
   200  {{- if .HasReader}}
   201  // NewReader creates an instance of ContractReader using {{if len .Hash -}}Hash{{- else -}}provided contract hash{{- end}} and the given Invoker.
   202  func NewReader(invoker Invoker{{- if not (len .Hash) -}}, hash util.Uint160{{- end -}}) *ContractReader {
   203  	{{if len .Hash -}}
   204  	var hash = Hash
   205  	{{end -}}
   206  	return &ContractReader{
   207  		{{- if .IsNep11D}}*nep11.NewDivisibleReader(invoker, hash), {{end}}
   208  		{{- if .IsNep11ND}}*nep11.NewNonDivisibleReader(invoker, hash), {{end}}
   209  		{{- if .IsNep17}}*nep17.NewReader(invoker, hash), {{end -}}
   210  		invoker, hash}
   211  }
   212  {{end -}}
   213  {{- if .HasWriter}}
   214  // New creates an instance of Contract using {{if len .Hash -}}Hash{{- else -}}provided contract hash{{- end}} and the given Actor.
   215  func New(actor Actor{{- if not (len .Hash) -}}, hash util.Uint160{{- end -}}) *Contract {
   216  	{{if len .Hash -}}
   217  	var hash = Hash
   218  	{{end -}}
   219  	{{if .IsNep11D}}var nep11dt = nep11.NewDivisible(actor, hash)
   220  	{{end -}}
   221  	{{if .IsNep11ND}}var nep11ndt = nep11.NewNonDivisible(actor, hash)
   222  	{{end -}}
   223  	{{if .IsNep17}}var nep17t = nep17.New(actor, hash)
   224  	{{end -}}
   225  	return &Contract{
   226  		{{- if .HasReader}}ContractReader{
   227  		{{- if .IsNep11D}}nep11dt.DivisibleReader, {{end -}}
   228  		{{- if .IsNep11ND}}nep11ndt.NonDivisibleReader, {{end -}}
   229  		{{- if .IsNep17}}nep17t.TokenReader, {{end -}}
   230  		actor, hash}, {{end -}}
   231  		{{- if .IsNep11D}}nep11dt.DivisibleWriter, {{end -}}
   232  		{{- if .IsNep11ND}}nep11ndt.BaseWriter, {{end -}}
   233  		{{- if .IsNep17}}nep17t.TokenWriter, {{end -}}
   234  		actor, hash}
   235  }
   236  {{end -}}
   237  {{- range $m := .SafeMethods }}{{template "SAFEMETHOD" $m }}{{ end -}}
   238  {{- range $m := .Methods -}}{{template "METHOD" $m }}{{ end -}}
   239  {{- range $index, $typ := .NamedTypes }}
   240  // itemTo{{toTypeName $typ.Name}} converts stack item into *{{toTypeName $typ.Name}}.
   241  func itemTo{{toTypeName $typ.Name}}(item stackitem.Item, err error) (*{{toTypeName $typ.Name}}, error) {
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	var res = new({{toTypeName $typ.Name}})
   246  	err = res.FromStackItem(item)
   247  	return res, err
   248  }
   249  
   250  // FromStackItem retrieves fields of {{toTypeName $typ.Name}} from the given
   251  // [stackitem.Item] or returns an error if it's not possible to do to so.
   252  func (res *{{toTypeName $typ.Name}}) FromStackItem(item stackitem.Item) error {
   253  	arr, ok := item.Value().([]stackitem.Item)
   254  	if !ok {
   255  		return errors.New("not an array")
   256  	}
   257  	if len(arr) != {{len $typ.Fields}} {
   258  		return errors.New("wrong number of structure elements")
   259  	}
   260  {{if len .Fields}}
   261  	var (
   262  		index = -1
   263  		err   error
   264  	)
   265  {{- range $m := $typ.Fields}}
   266  	index++
   267  	res.{{ upperFirst .Field}}, err = {{etTypeConverter .ExtendedType "arr[index]"}}
   268  	if err != nil {
   269  		return fmt.Errorf("field {{ upperFirst .Field}}: %w", err)
   270  	}
   271  {{end}}
   272  {{- end}}
   273  	return nil
   274  }
   275  {{ end -}}
   276  {{- range $e := .CustomEvents }}
   277  // {{$e.Name}}sFromApplicationLog retrieves a set of all emitted events
   278  // with "{{$e.ManifestName}}" name from the provided [result.ApplicationLog].
   279  func {{$e.Name}}sFromApplicationLog(log *result.ApplicationLog) ([]*{{$e.Name}}, error) {
   280  	if log == nil {
   281  		return nil, errors.New("nil application log")
   282  	}
   283  
   284  	var res []*{{$e.Name}}
   285  	for i, ex := range log.Executions {
   286  		for j, e := range ex.Events {
   287  			if e.Name != "{{$e.ManifestName}}" {
   288  				continue
   289  			}
   290  			event := new({{$e.Name}})
   291  			err := event.FromStackItem(e.Item)
   292  			if err != nil {
   293  				return nil, fmt.Errorf("failed to deserialize {{$e.Name}} from stackitem (execution #%d, event #%d): %w", i, j, err)
   294  			}
   295  			res = append(res, event)
   296  		}
   297  	}
   298  
   299  	return res, nil
   300  }
   301  
   302  // FromStackItem converts provided [stackitem.Array] to {{$e.Name}} or
   303  // returns an error if it's not possible to do to so.
   304  func (e *{{$e.Name}}) FromStackItem(item *stackitem.Array) error {
   305  	if item == nil {
   306  		return errors.New("nil item")
   307  	}
   308  	arr, ok := item.Value().([]stackitem.Item)
   309  	if !ok {
   310  		return errors.New("not an array")
   311  	}
   312  	if len(arr) != {{len $e.Parameters}} {
   313  		return errors.New("wrong number of structure elements")
   314  	}
   315  
   316  	{{if len $e.Parameters}}var (
   317  		index = -1
   318  		err   error
   319  	)
   320  	{{- range $p := $e.Parameters}}
   321  	index++
   322  	e.{{ upperFirst .Name}}, err = {{etTypeConverter .ExtType "arr[index]"}}
   323  	if err != nil {
   324  		return fmt.Errorf("field {{ upperFirst .Name}}: %w", err)
   325  	}
   326  {{end}}
   327  {{- end}}
   328  	return nil
   329  }
   330  {{end -}}`
   331  
   332  	srcTmpl = bindingDefinition +
   333  		eventDefinition +
   334  		safemethodDefinition +
   335  		methodDefinition
   336  )
   337  
   338  type (
   339  	ContractTmpl struct {
   340  		binding.ContractTmpl
   341  
   342  		SafeMethods  []SafeMethodTmpl
   343  		CustomEvents []CustomEventTemplate
   344  		NamedTypes   []binding.ExtendedType
   345  
   346  		IsNep11D  bool
   347  		IsNep11ND bool
   348  		IsNep17   bool
   349  
   350  		HasReader   bool
   351  		HasWriter   bool
   352  		HasIterator bool
   353  	}
   354  
   355  	SafeMethodTmpl struct {
   356  		binding.MethodTmpl
   357  		Unwrapper      string
   358  		ItemTo         string
   359  		ExtendedReturn binding.ExtendedType
   360  	}
   361  
   362  	CustomEventTemplate struct {
   363  		// Name is the event's name that will be used as the event structure name in
   364  		// the resulting RPC binding. It is a valid go structure name and may differ
   365  		// from ManifestName.
   366  		Name string
   367  		// ManifestName is the event's name declared in the contract manifest.
   368  		// It may contain any UTF8 character.
   369  		ManifestName string
   370  		Parameters   []EventParamTmpl
   371  	}
   372  
   373  	EventParamTmpl struct {
   374  		binding.ParamTmpl
   375  
   376  		// ExtType holds the event parameter's type information provided by Manifest,
   377  		// i.e. simple types only.
   378  		ExtType binding.ExtendedType
   379  	}
   380  )
   381  
   382  // NewConfig initializes and returns a new config instance.
   383  func NewConfig() binding.Config {
   384  	return binding.NewConfig()
   385  }
   386  
   387  // Generate writes Go file containing smartcontract bindings to the `cfg.Output`.
   388  // It doesn't check manifest from Config for validity, incorrect manifest can
   389  // lead to unexpected results.
   390  func Generate(cfg binding.Config) error {
   391  	// Avoid changing *cfg.Manifest.
   392  	mfst := *cfg.Manifest
   393  	mfst.ABI.Methods = make([]manifest.Method, len(mfst.ABI.Methods))
   394  	copy(mfst.ABI.Methods, cfg.Manifest.ABI.Methods)
   395  	cfg.Manifest = &mfst
   396  
   397  	var imports = make(map[string]struct{})
   398  	var ctr ContractTmpl
   399  
   400  	// Strip standard methods from NEP-XX packages.
   401  	for _, std := range cfg.Manifest.SupportedStandards {
   402  		if std == manifest.NEP11StandardName {
   403  			imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"] = struct{}{}
   404  			if standard.ComplyABI(cfg.Manifest, standard.Nep11Divisible) == nil {
   405  				mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11Divisible)
   406  				ctr.IsNep11D = true
   407  			} else if standard.ComplyABI(cfg.Manifest, standard.Nep11NonDivisible) == nil {
   408  				mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11NonDivisible)
   409  				ctr.IsNep11ND = true
   410  			}
   411  			mfst.ABI.Events = dropStdEvents(mfst.ABI.Events, standard.Nep11Base)
   412  			break // Can't be NEP-17 at the same time.
   413  		}
   414  		if std == manifest.NEP17StandardName && standard.ComplyABI(cfg.Manifest, standard.Nep17) == nil {
   415  			mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep17)
   416  			imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"] = struct{}{}
   417  			ctr.IsNep17 = true
   418  			mfst.ABI.Events = dropStdEvents(mfst.ABI.Events, standard.Nep17)
   419  			break // Can't be NEP-11 at the same time.
   420  		}
   421  	}
   422  
   423  	// OnNepXXPayment handlers normally can't be called directly.
   424  	if standard.ComplyABI(cfg.Manifest, standard.Nep11Payable) == nil {
   425  		mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11Payable)
   426  	}
   427  	if standard.ComplyABI(cfg.Manifest, standard.Nep17Payable) == nil {
   428  		mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep17Payable)
   429  	}
   430  
   431  	ctr.ContractTmpl = binding.TemplateFromManifest(cfg, scTypeToGo)
   432  	ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo)
   433  	ctr.NamedTypes = make([]binding.ExtendedType, 0, len(cfg.NamedTypes))
   434  	for k := range cfg.NamedTypes {
   435  		ctr.NamedTypes = append(ctr.NamedTypes, cfg.NamedTypes[k])
   436  	}
   437  	sort.Slice(ctr.NamedTypes, func(i, j int) bool {
   438  		return strings.Compare(ctr.NamedTypes[i].Name, ctr.NamedTypes[j].Name) < 0
   439  	})
   440  
   441  	// Check resulting named types and events don't have duplicating field names.
   442  	for _, t := range ctr.NamedTypes {
   443  		fDict := make(map[string]struct{})
   444  		for _, n := range t.Fields {
   445  			name := upperFirst(n.Field)
   446  			if _, ok := fDict[name]; ok {
   447  				return fmt.Errorf("named type `%s` has two fields with identical resulting binding name `%s`", t.Name, name)
   448  			}
   449  			fDict[name] = struct{}{}
   450  		}
   451  	}
   452  	for _, e := range ctr.CustomEvents {
   453  		fDict := make(map[string]struct{})
   454  		for _, n := range e.Parameters {
   455  			name := upperFirst(n.Name)
   456  			if _, ok := fDict[name]; ok {
   457  				return fmt.Errorf("event `%s` has two fields with identical resulting binding name `%s`", e.Name, name)
   458  			}
   459  			fDict[name] = struct{}{}
   460  		}
   461  	}
   462  
   463  	var srcTemplate = template.Must(template.New("generate").Funcs(template.FuncMap{
   464  		"addIndent":       addIndent,
   465  		"etTypeConverter": etTypeConverter,
   466  		"etTypeToStr": func(et binding.ExtendedType) string {
   467  			r, _ := extendedTypeToGo(et, cfg.NamedTypes)
   468  			return r
   469  		},
   470  		"toTypeName": toTypeName,
   471  		"cutPointer": cutPointer,
   472  		"upperFirst": upperFirst,
   473  	}).Parse(srcTmpl))
   474  
   475  	return binding.FExecute(srcTemplate, cfg.Output, ctr)
   476  }
   477  
   478  func dropManifestMethods(meths []manifest.Method, manifested []manifest.Method) []manifest.Method {
   479  	for _, m := range manifested {
   480  		for i := 0; i < len(meths); i++ {
   481  			if meths[i].Name == m.Name && len(meths[i].Parameters) == len(m.Parameters) {
   482  				meths = append(meths[:i], meths[i+1:]...)
   483  				i--
   484  			}
   485  		}
   486  	}
   487  	return meths
   488  }
   489  
   490  func dropManifestEvents(events []manifest.Event, manifested []manifest.Event) []manifest.Event {
   491  	for _, e := range manifested {
   492  		for i := 0; i < len(events); i++ {
   493  			if events[i].Name == e.Name && len(events[i].Parameters) == len(e.Parameters) {
   494  				events = append(events[:i], events[i+1:]...)
   495  				i--
   496  			}
   497  		}
   498  	}
   499  	return events
   500  }
   501  
   502  func dropStdMethods(meths []manifest.Method, std *standard.Standard) []manifest.Method {
   503  	meths = dropManifestMethods(meths, std.Manifest.ABI.Methods)
   504  	if std.Optional != nil {
   505  		meths = dropManifestMethods(meths, std.Optional)
   506  	}
   507  	if std.Base != nil {
   508  		return dropStdMethods(meths, std.Base)
   509  	}
   510  	return meths
   511  }
   512  
   513  func dropStdEvents(events []manifest.Event, std *standard.Standard) []manifest.Event {
   514  	events = dropManifestEvents(events, std.Manifest.ABI.Events)
   515  	if std.Base != nil {
   516  		return dropStdEvents(events, std.Base)
   517  	}
   518  	return events
   519  }
   520  
   521  func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.ExtendedType) (string, string) {
   522  	switch et.Base {
   523  	case smartcontract.AnyType:
   524  		return "any", ""
   525  	case smartcontract.BoolType:
   526  		return "bool", ""
   527  	case smartcontract.IntegerType:
   528  		return "*big.Int", "math/big"
   529  	case smartcontract.ByteArrayType:
   530  		return "[]byte", ""
   531  	case smartcontract.StringType:
   532  		return "string", ""
   533  	case smartcontract.Hash160Type:
   534  		return "util.Uint160", "github.com/nspcc-dev/neo-go/pkg/util"
   535  	case smartcontract.Hash256Type:
   536  		return "util.Uint256", "github.com/nspcc-dev/neo-go/pkg/util"
   537  	case smartcontract.PublicKeyType:
   538  		return "*keys.PublicKey", "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
   539  	case smartcontract.SignatureType:
   540  		return "[]byte", ""
   541  	case smartcontract.ArrayType:
   542  		if len(et.Name) > 0 {
   543  			return "*" + toTypeName(et.Name), "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
   544  		} else if et.Value != nil {
   545  			if et.Value.Base == smartcontract.PublicKeyType { // Special array wrapper.
   546  				return "keys.PublicKeys", "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
   547  			}
   548  			sub, pkg := extendedTypeToGo(*et.Value, named)
   549  			return "[]" + sub, pkg
   550  		}
   551  		return "[]any", ""
   552  
   553  	case smartcontract.MapType:
   554  		kt, _ := extendedTypeToGo(binding.ExtendedType{Base: et.Key}, named)
   555  		var vt string
   556  		if et.Value != nil {
   557  			vt, _ = extendedTypeToGo(*et.Value, named)
   558  		} else {
   559  			vt = "any"
   560  		}
   561  		return "map[" + kt + "]" + vt, "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
   562  	case smartcontract.InteropInterfaceType:
   563  		return "any", ""
   564  	case smartcontract.VoidType:
   565  		return "", ""
   566  	}
   567  	panic("unreachable")
   568  }
   569  
   570  func etTypeConverter(et binding.ExtendedType, v string) string {
   571  	switch et.Base {
   572  	case smartcontract.AnyType:
   573  		return v + ".Value(), error(nil)"
   574  	case smartcontract.BoolType:
   575  		return v + ".TryBool()"
   576  	case smartcontract.IntegerType:
   577  		return v + ".TryInteger()"
   578  	case smartcontract.ByteArrayType, smartcontract.SignatureType:
   579  		return v + ".TryBytes()"
   580  	case smartcontract.StringType:
   581  		return `func(item stackitem.Item) (string, error) {
   582  		b, err := item.TryBytes()
   583  		if err != nil {
   584  			return "", err
   585  		}
   586  		if !utf8.Valid(b) {
   587  			return "", errors.New("not a UTF-8 string")
   588  		}
   589  		return string(b), nil
   590  	}(` + v + `)`
   591  	case smartcontract.Hash160Type:
   592  		return `func(item stackitem.Item) (util.Uint160, error) {
   593  		b, err := item.TryBytes()
   594  		if err != nil {
   595  			return util.Uint160{}, err
   596  		}
   597  		u, err := util.Uint160DecodeBytesBE(b)
   598  		if err != nil {
   599  			return util.Uint160{}, err
   600  		}
   601  		return u, nil
   602  	}(` + v + `)`
   603  	case smartcontract.Hash256Type:
   604  		return `func(item stackitem.Item) (util.Uint256, error) {
   605  		b, err := item.TryBytes()
   606  		if err != nil {
   607  			return util.Uint256{}, err
   608  		}
   609  		u, err := util.Uint256DecodeBytesBE(b)
   610  		if err != nil {
   611  			return util.Uint256{}, err
   612  		}
   613  		return u, nil
   614  	}(` + v + `)`
   615  	case smartcontract.PublicKeyType:
   616  		return `func(item stackitem.Item) (*keys.PublicKey, error) {
   617  		b, err := item.TryBytes()
   618  		if err != nil {
   619  			return nil, err
   620  		}
   621  		k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256())
   622  		if err != nil {
   623  			return nil, err
   624  		}
   625  		return k, nil
   626  	}(` + v + `)`
   627  	case smartcontract.ArrayType:
   628  		if len(et.Name) > 0 {
   629  			return "itemTo" + toTypeName(et.Name) + "(" + v + ", nil)"
   630  		} else if et.Value != nil {
   631  			at, _ := extendedTypeToGo(et, nil)
   632  			return `func(item stackitem.Item) (` + at + `, error) {
   633  		arr, ok := item.Value().([]stackitem.Item)
   634  		if !ok {
   635  			return nil, errors.New("not an array")
   636  		}
   637  		res := make(` + at + `, len(arr))
   638  		for i := range res {
   639  			res[i], err = ` + addIndent(etTypeConverter(*et.Value, "arr[i]"), "\t\t") + `
   640  			if err != nil {
   641  				return nil, fmt.Errorf("item %d: %w", i, err)
   642  			}
   643  		}
   644  		return res, nil
   645  	}(` + v + `)`
   646  		}
   647  		return etTypeConverter(binding.ExtendedType{
   648  			Base: smartcontract.ArrayType,
   649  			Value: &binding.ExtendedType{
   650  				Base: smartcontract.AnyType,
   651  			},
   652  		}, v)
   653  
   654  	case smartcontract.MapType:
   655  		if et.Value != nil {
   656  			at, _ := extendedTypeToGo(et, nil)
   657  			return `func(item stackitem.Item) (` + at + `, error) {
   658  		m, ok := item.Value().([]stackitem.MapElement)
   659  		if !ok {
   660  			return nil, fmt.Errorf("%s is not a map", item.Type().String())
   661  		}
   662  		res := make(` + at + `)
   663  		for i := range m {
   664  			k, err := ` + addIndent(etTypeConverter(binding.ExtendedType{Base: et.Key}, "m[i].Key"), "\t\t") + `
   665  			if err != nil {
   666  				return nil, fmt.Errorf("key %d: %w", i, err)
   667  			}
   668  			v, err := ` + addIndent(etTypeConverter(*et.Value, "m[i].Value"), "\t\t") + `
   669  			if err != nil {
   670  				return nil, fmt.Errorf("value %d: %w", i, err)
   671  			}
   672  			res[k] = v
   673  		}
   674  		return res, nil
   675  	}(` + v + `)`
   676  		}
   677  		return etTypeConverter(binding.ExtendedType{
   678  			Base: smartcontract.MapType,
   679  			Key:  et.Key,
   680  			Value: &binding.ExtendedType{
   681  				Base: smartcontract.AnyType,
   682  			},
   683  		}, v)
   684  	case smartcontract.InteropInterfaceType:
   685  		return "item.Value(), nil"
   686  	case smartcontract.VoidType:
   687  		return ""
   688  	}
   689  	panic("unreachable")
   690  }
   691  
   692  func scTypeToGo(name string, typ smartcontract.ParamType, cfg *binding.Config) (string, string) {
   693  	et, ok := cfg.Types[name]
   694  	if !ok {
   695  		et = binding.ExtendedType{Base: typ}
   696  	}
   697  	return extendedTypeToGo(et, cfg.NamedTypes)
   698  }
   699  
   700  func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]struct{}, scTypeConverter func(string, smartcontract.ParamType, *binding.Config) (string, string)) ContractTmpl {
   701  	for i := range ctr.Imports {
   702  		imports[ctr.Imports[i]] = struct{}{}
   703  	}
   704  	if !cfg.Hash.Equals(util.Uint160{}) {
   705  		ctr.Hash = fmt.Sprintf("%#v", cfg.Hash)
   706  	}
   707  	for i := 0; i < len(ctr.Methods); i++ {
   708  		abim := cfg.Manifest.ABI.GetMethod(ctr.Methods[i].NameABI, len(ctr.Methods[i].Arguments))
   709  		if abim.Safe {
   710  			ctr.SafeMethods = append(ctr.SafeMethods, SafeMethodTmpl{MethodTmpl: ctr.Methods[i]})
   711  			et, ok := cfg.Types[abim.Name]
   712  			if ok {
   713  				ctr.SafeMethods[len(ctr.SafeMethods)-1].ExtendedReturn = et
   714  				if abim.ReturnType == smartcontract.ArrayType && len(et.Name) > 0 {
   715  					ctr.SafeMethods[len(ctr.SafeMethods)-1].ItemTo = cutPointer(ctr.Methods[i].ReturnType)
   716  				}
   717  			}
   718  			ctr.Methods = append(ctr.Methods[:i], ctr.Methods[i+1:]...)
   719  			i--
   720  		} else {
   721  			ctr.Methods[i].Comment = fmt.Sprintf("creates a transaction invoking `%s` method of the contract.", ctr.Methods[i].NameABI)
   722  			if ctr.Methods[i].ReturnType == "bool" {
   723  				imports["github.com/nspcc-dev/neo-go/pkg/smartcontract"] = struct{}{}
   724  			}
   725  		}
   726  	}
   727  	for _, et := range cfg.NamedTypes {
   728  		addETImports(et, cfg.NamedTypes, imports)
   729  	}
   730  	if len(cfg.NamedTypes) > 0 {
   731  		imports["errors"] = struct{}{}
   732  	}
   733  	for _, abiEvent := range cfg.Manifest.ABI.Events {
   734  		eBindingName := ToEventBindingName(abiEvent.Name)
   735  		eTmp := CustomEventTemplate{
   736  			Name:         eBindingName,
   737  			ManifestName: abiEvent.Name,
   738  		}
   739  		for i := range abiEvent.Parameters {
   740  			pBindingName := ToParameterBindingName(abiEvent.Parameters[i].Name)
   741  			fullPName := eBindingName + "." + pBindingName
   742  			typeStr, pkg := scTypeConverter(fullPName, abiEvent.Parameters[i].Type, &cfg)
   743  			if pkg != "" {
   744  				imports[pkg] = struct{}{}
   745  			}
   746  
   747  			var (
   748  				extType binding.ExtendedType
   749  				ok      bool
   750  			)
   751  			if extType, ok = cfg.Types[fullPName]; !ok {
   752  				extType = binding.ExtendedType{
   753  					Base: abiEvent.Parameters[i].Type,
   754  				}
   755  				addETImports(extType, cfg.NamedTypes, imports)
   756  			}
   757  			eTmp.Parameters = append(eTmp.Parameters, EventParamTmpl{
   758  				ParamTmpl: binding.ParamTmpl{
   759  					Name: pBindingName,
   760  					Type: typeStr,
   761  				},
   762  				ExtType: extType,
   763  			})
   764  		}
   765  		ctr.CustomEvents = append(ctr.CustomEvents, eTmp)
   766  	}
   767  
   768  	if len(ctr.CustomEvents) > 0 {
   769  		imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{}
   770  		imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{}
   771  		imports["fmt"] = struct{}{}
   772  		imports["errors"] = struct{}{}
   773  	}
   774  
   775  	for i := range ctr.SafeMethods {
   776  		switch ctr.SafeMethods[i].ReturnType {
   777  		case "any":
   778  			abim := cfg.Manifest.ABI.GetMethod(ctr.SafeMethods[i].NameABI, len(ctr.SafeMethods[i].Arguments))
   779  			if abim.ReturnType == smartcontract.InteropInterfaceType {
   780  				imports["github.com/google/uuid"] = struct{}{}
   781  				imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{}
   782  				imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{}
   783  				ctr.SafeMethods[i].ReturnType = "uuid.UUID, result.Iterator"
   784  				ctr.SafeMethods[i].Unwrapper = "SessionIterator"
   785  				ctr.HasIterator = true
   786  			} else {
   787  				imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{}
   788  				ctr.SafeMethods[i].ReturnType = "any"
   789  				ctr.SafeMethods[i].Unwrapper = "Item"
   790  			}
   791  		case "bool":
   792  			ctr.SafeMethods[i].Unwrapper = "Bool"
   793  		case "*big.Int":
   794  			ctr.SafeMethods[i].Unwrapper = "BigInt"
   795  		case "string":
   796  			ctr.SafeMethods[i].Unwrapper = "UTF8String"
   797  		case "util.Uint160":
   798  			ctr.SafeMethods[i].Unwrapper = "Uint160"
   799  		case "util.Uint256":
   800  			ctr.SafeMethods[i].Unwrapper = "Uint256"
   801  		case "*keys.PublicKey":
   802  			ctr.SafeMethods[i].Unwrapper = "PublicKey"
   803  		case "[]byte":
   804  			ctr.SafeMethods[i].Unwrapper = "Bytes"
   805  		case "[]any":
   806  			imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{}
   807  			ctr.SafeMethods[i].ReturnType = "[]stackitem.Item"
   808  			ctr.SafeMethods[i].Unwrapper = "Array"
   809  		case "*stackitem.Map":
   810  			ctr.SafeMethods[i].Unwrapper = "Map"
   811  		case "[]bool":
   812  			ctr.SafeMethods[i].Unwrapper = "ArrayOfBools"
   813  		case "[]*big.Int":
   814  			ctr.SafeMethods[i].Unwrapper = "ArrayOfBigInts"
   815  		case "[][]byte":
   816  			ctr.SafeMethods[i].Unwrapper = "ArrayOfBytes"
   817  		case "[]string":
   818  			ctr.SafeMethods[i].Unwrapper = "ArrayOfUTF8Strings"
   819  		case "[]util.Uint160":
   820  			ctr.SafeMethods[i].Unwrapper = "ArrayOfUint160"
   821  		case "[]util.Uint256":
   822  			ctr.SafeMethods[i].Unwrapper = "ArrayOfUint256"
   823  		case "keys.PublicKeys":
   824  			ctr.SafeMethods[i].Unwrapper = "ArrayOfPublicKeys"
   825  		default:
   826  			addETImports(ctr.SafeMethods[i].ExtendedReturn, cfg.NamedTypes, imports)
   827  			ctr.SafeMethods[i].Unwrapper = "Item"
   828  		}
   829  	}
   830  
   831  	imports["github.com/nspcc-dev/neo-go/pkg/util"] = struct{}{}
   832  	if len(ctr.SafeMethods) > 0 {
   833  		imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"] = struct{}{}
   834  		if !(ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND) {
   835  			imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{}
   836  		}
   837  	}
   838  	if len(ctr.Methods) > 0 {
   839  		imports["github.com/nspcc-dev/neo-go/pkg/core/transaction"] = struct{}{}
   840  	}
   841  	if len(ctr.Methods) > 0 || ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND {
   842  		ctr.HasWriter = true
   843  	}
   844  	if len(ctr.SafeMethods) > 0 || ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND {
   845  		ctr.HasReader = true
   846  	}
   847  	ctr.Imports = ctr.Imports[:0]
   848  	for imp := range imports {
   849  		ctr.Imports = append(ctr.Imports, imp)
   850  	}
   851  	sort.Strings(ctr.Imports)
   852  	return ctr
   853  }
   854  
   855  func addETImports(et binding.ExtendedType, named map[string]binding.ExtendedType, imports map[string]struct{}) {
   856  	_, pkg := extendedTypeToGo(et, named)
   857  	if pkg != "" {
   858  		imports[pkg] = struct{}{}
   859  	}
   860  	// Additional packages used during decoding.
   861  	switch et.Base {
   862  	case smartcontract.StringType:
   863  		imports["unicode/utf8"] = struct{}{}
   864  		imports["errors"] = struct{}{}
   865  	case smartcontract.PublicKeyType:
   866  		imports["crypto/elliptic"] = struct{}{}
   867  	case smartcontract.MapType:
   868  		imports["fmt"] = struct{}{}
   869  	case smartcontract.ArrayType:
   870  		imports["errors"] = struct{}{}
   871  		imports["fmt"] = struct{}{}
   872  	}
   873  	if et.Value != nil {
   874  		addETImports(*et.Value, named, imports)
   875  	}
   876  	if et.Base == smartcontract.MapType {
   877  		addETImports(binding.ExtendedType{Base: et.Key}, named, imports)
   878  	}
   879  	for i := range et.Fields {
   880  		addETImports(et.Fields[i].ExtendedType, named, imports)
   881  	}
   882  }
   883  
   884  func cutPointer(s string) string {
   885  	if s[0] == '*' {
   886  		return s[1:]
   887  	}
   888  	return s
   889  }
   890  
   891  func toTypeName(s string) string {
   892  	return strings.Map(func(c rune) rune {
   893  		if c == '.' {
   894  			return -1
   895  		}
   896  		return c
   897  	}, upperFirst(s))
   898  }
   899  
   900  func addIndent(str string, ind string) string {
   901  	return strings.ReplaceAll(str, "\n", "\n"+ind)
   902  }
   903  
   904  // ToEventBindingName converts event name specified in the contract manifest to
   905  // a valid go exported event structure name.
   906  func ToEventBindingName(eventName string) string {
   907  	return toPascalCase(eventName) + "Event"
   908  }
   909  
   910  // ToParameterBindingName converts parameter name specified in the contract
   911  // manifest to a valid go structure's exported field name.
   912  func ToParameterBindingName(paramName string) string {
   913  	return toPascalCase(paramName)
   914  }
   915  
   916  // toPascalCase removes all non-unicode characters from the provided string and
   917  // converts it to pascal case using space as delimiter.
   918  func toPascalCase(s string) string {
   919  	var res string
   920  	ss := strings.Split(s, " ")
   921  	for i := range ss { // TODO: use DecodeRuneInString instead.
   922  		var word string
   923  		for _, ch := range ss[i] {
   924  			var ok bool
   925  			if len(res) == 0 && len(word) == 0 {
   926  				ok = unicode.IsLetter(ch)
   927  			} else {
   928  				ok = unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_'
   929  			}
   930  			if ok {
   931  				word += string(ch)
   932  			}
   933  		}
   934  		if len(word) > 0 {
   935  			res += upperFirst(word)
   936  		}
   937  	}
   938  	return res
   939  }
   940  
   941  func upperFirst(s string) string {
   942  	return strings.ToUpper(s[0:1]) + s[1:]
   943  }