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 }