From aeb61fb61dfd2c22555ebb4d493dba303d302e77 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 7 Nov 2022 22:13:47 +0300 Subject: [PATCH] rpcbinding: generate ASSERT for bool-returning methods It's a common pattern. --- cli/smartcontract/testdata/nameservice/nns.go | 23 ++++++++++-- docs/compiler.md | 19 +++++++--- pkg/smartcontract/rpcbinding/binding.go | 36 +++++++++++++++---- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index 511acaedf..436d7fbcb 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -6,6 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "math/big" @@ -153,18 +154,30 @@ func (c *Contract) SetPriceUnsigned(priceList []interface{}) (*transaction.Trans return c.actor.MakeUnsignedCall(Hash, "setPrice", nil, priceList) } +func scriptForRegister(name string, owner util.Uint160) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(Hash, "register", name, owner) +} + // Register creates a transaction invoking `register` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. func (c *Contract) Register(name string, owner util.Uint160) (util.Uint256, uint32, error) { - return c.actor.SendCall(Hash, "register", name, owner) + script, err := scriptForRegister(name, owner) + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script) } // RegisterTransaction creates a transaction invoking `register` method of the contract. // This transaction is signed, but not sent to the network, instead it's // returned to the caller. func (c *Contract) RegisterTransaction(name string, owner util.Uint160) (*transaction.Transaction, error) { - return c.actor.MakeCall(Hash, "register", name, owner) + script, err := scriptForRegister(name, owner) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) } // RegisterUnsigned creates a transaction invoking `register` method of the contract. @@ -172,7 +185,11 @@ func (c *Contract) RegisterTransaction(name string, owner util.Uint160) (*transa // Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Nonce), fee values (NetworkFee, SystemFee) can be increased as well. func (c *Contract) RegisterUnsigned(name string, owner util.Uint160) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(Hash, "register", nil, name, owner) + script, err := scriptForRegister(name, owner) + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil) } // Renew creates a transaction invoking `renew` method of the contract. diff --git a/docs/compiler.md b/docs/compiler.md index 7b909fc6c..ab1358b7e 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -436,11 +436,20 @@ $ ./bin/neo-go contract generate-wrapper --manifest manifest.json --config contr ### Generating RPC contract bindings To simplify interacting with the contract via RPC you can generate -contract-specific RPC bindings with the "generate-rpcwrapper" command. If your -contract is NEP-11 or NEP-17 that's autodetected and an appropriate package is -included as well. Notice that the type data available in the manifest is -limited, so in some cases the interface generated may use generic stackitem -types. Iterators are not supported yet. +contract-specific RPC bindings with the "generate-rpcwrapper" command. It +generates ContractReader structure for safe methods that accept appropriate +data for input and return things returned by the contract. State-changing +methods are contained in Contract structure with each contract method +represented by three wrapper methods that create/send transaction with a +script performing appropriate action. This script invokes contract method and +does not do anything else unless the method's returned value is of a boolean +type, in this case an ASSERT is added to script making it fail when the method +returns false. + +If your contract is NEP-11 or NEP-17 that's autodetected and an appropriate +package is included as well. Notice that the type data available in the +manifest is limited, so in some cases the interface generated may use generic +stackitem types. Iterators are not supported yet. ``` $ ./bin/neo-go contract generate-rpcwrapper --manifest manifest.json --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176 diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 578e0e048..a5e7c5cab 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -27,15 +27,26 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} } {{- end -}} {{- define "METHOD" -}} -// {{.Name}} {{.Comment}} +{{- if eq .ReturnType "bool"}}func scriptFor{{.Name}}({{range $index, $arg := .Arguments -}} + {{- if ne $index 0}}, {{end}} + {{- .Name}} {{.Type}} + {{- end}}) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(Hash, "{{ .NameABI }}"{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) +} + +{{end}}// {{.Name}} {{.Comment}} // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. func (c *Contract) {{.Name}}({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) (util.Uint256, uint32, error) { - return c.actor.SendCall(Hash, "{{ .NameABI }}" - {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) + {{if ne .ReturnType "bool"}}return c.actor.SendCall(Hash, "{{ .NameABI }}" + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}}) + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script){{end}} } // {{.Name}}Transaction {{.Comment}} @@ -45,8 +56,12 @@ func (c *Contract) {{.Name}}Transaction({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) (*transaction.Transaction, error) { - return c.actor.MakeCall(Hash, "{{ .NameABI }}" - {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) + {{if ne .ReturnType "bool"}}return c.actor.MakeCall(Hash, "{{ .NameABI }}" + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}}) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script){{end}} } // {{.Name}}Unsigned {{.Comment}} @@ -57,8 +72,12 @@ func (c *Contract) {{.Name}}Unsigned({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(Hash, "{{ .NameABI }}", nil - {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) + {{if ne .ReturnType "bool"}}return c.actor.MakeUnsignedCall(Hash, "{{ .NameABI }}", nil + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}}) + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil){{end}} } {{- end -}} // Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract. @@ -278,6 +297,9 @@ func scTemplateToRPC(cfg binding.Config, bctr binding.ContractTmpl) ContractTmpl i-- } else { ctr.Methods[i].Comment = fmt.Sprintf("creates a transaction invoking `%s` method of the contract.", ctr.Methods[i].NameABI) + if ctr.Methods[i].ReturnType == "bool" { + imports["github.com/nspcc-dev/neo-go/pkg/smartcontract"] = struct{}{} + } } } // We're misusing CallFlag field for function name here.