diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index b6a1bf253..80a33aa89 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -360,6 +360,9 @@ func TestGenerateRPCBindings(t *testing.T) { checkBinding(filepath.Join("testdata", "verifyrpc", "verify.manifest.json"), "0x00112233445566778899aabbccddeeff00112233", filepath.Join("testdata", "verifyrpc", "verify.go")) + checkBinding(filepath.Join("testdata", "nonepiter", "iter.manifest.json"), + "0x00112233445566778899aabbccddeeff00112233", + filepath.Join("testdata", "nonepiter", "iter.go")) } func TestGenerate_Errors(t *testing.T) { diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index daf1e6842..d156d985c 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -9,6 +9,7 @@ import ( "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" ) @@ -64,6 +65,15 @@ func (c *ContractReader) Roots() (uuid.UUID, result.Iterator, error) { return unwrap.SessionIterator(c.invoker.Call(Hash, "roots")) } +// RootsExpanded is similar to Roots (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) RootsExpanded(_numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "roots", _numOfIteratorItems)) +} + // GetPrice invokes `getPrice` method of contract. func (c *ContractReader) GetPrice(length *big.Int) (*big.Int, error) { return unwrap.BigInt(c.invoker.Call(Hash, "getPrice", length)) @@ -84,6 +94,15 @@ func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, return unwrap.SessionIterator(c.invoker.Call(Hash, "getAllRecords", name)) } +// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "getAllRecords", _numOfIteratorItems, name)) +} + // Resolve invokes `resolve` method of contract. func (c *ContractReader) Resolve(name string, typev *big.Int) (string, error) { return unwrap.UTF8String(c.invoker.Call(Hash, "resolve", name, typev)) diff --git a/cli/smartcontract/testdata/nonepiter/iter.go b/cli/smartcontract/testdata/nonepiter/iter.go new file mode 100644 index 000000000..c7323fb56 --- /dev/null +++ b/cli/smartcontract/testdata/nonepiter/iter.go @@ -0,0 +1,60 @@ +// Package nonnepxxcontractwithiterators contains RPC wrappers for Non-NEPXX contract with iterators contract. +package nonnepxxcontractwithiterators + +import ( + "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// 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} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) + CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) + TerminateSession(sessionID uuid.UUID) error + TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + invoker Invoker +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + return &ContractReader{invoker} +} + + +// Tokens invokes `tokens` method of contract. +func (c *ContractReader) Tokens() (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(Hash, "tokens")) +} + +// TokensExpanded is similar to Tokens (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) TokensExpanded(_numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "tokens", _numOfIteratorItems)) +} + +// GetAllRecords invokes `getAllRecords` method of contract. +func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(Hash, "getAllRecords", name)) +} + +// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "getAllRecords", _numOfIteratorItems, name)) +} diff --git a/cli/smartcontract/testdata/nonepiter/iter.manifest.json b/cli/smartcontract/testdata/nonepiter/iter.manifest.json new file mode 100644 index 000000000..9f33e27d3 --- /dev/null +++ b/cli/smartcontract/testdata/nonepiter/iter.manifest.json @@ -0,0 +1,33 @@ +{ + "groups" : [], + "abi" : { + "events" : [], + "methods" : [ + { + "parameters" : [], + "safe" : true, + "name" : "tokens", + "offset" : 0, + "returntype" : "InteropInterface" + }, + { + "offset" : 1, + "returntype" : "InteropInterface", + "safe" : true, + "parameters" : [ + { + "type" : "String", + "name" : "name" + } + ], + "name" : "getAllRecords" + } + ] + }, + "supportedstandards" : [], + "trusts" : [], + "extra" : {}, + "permissions" : [], + "name" : "Non-NEPXX contract with iterators", + "features" : {} +} diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 8fdc4d26a..89550646f 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -25,6 +25,17 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} {{- range $arg := .Arguments -}}, {{.Name}}{{end}}) {{- end}} } +{{- if eq .CallFlag "SessionIterator"}} + +// {{.Name}}Expanded is similar to {{.Name}} (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) {{.Name}}Expanded({{range $index, $arg := .Arguments}}{{.Name}} {{.Type}}, {{end}}_numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "{{.NameABI}}", _numOfIteratorItems{{range $arg := .Arguments}}, {{.Name}}{{end}})) +} +{{- end -}} {{- end -}} {{- define "METHOD" -}} {{- if eq .ReturnType "bool"}}func scriptFor{{.Name}}({{range $index, $arg := .Arguments -}} @@ -93,9 +104,15 @@ var Hash = {{ .Hash }} {{if .HasReader}}// Invoker is used by ContractReader to call various safe methods. type Invoker interface { {{if or .IsNep11D .IsNep11ND}} nep11.Invoker -{{else if .IsNep17}} nep17.Invoker +{{else -}} +{{ if .IsNep17}} nep17.Invoker {{else if len .SafeMethods}} Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {{end -}} +{{if .HasIterator}} CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) + TerminateSession(sessionID uuid.UUID) error + TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) +{{end -}} +{{end -}} } {{end -}} @@ -196,8 +213,9 @@ type ( IsNep11ND bool IsNep17 bool - HasReader bool - HasWriter bool + HasReader bool + HasWriter bool + HasIterator bool } ) @@ -337,9 +355,11 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st abim := cfg.Manifest.ABI.GetMethod(ctr.SafeMethods[i].NameABI, len(ctr.SafeMethods[i].Arguments)) if abim.ReturnType == smartcontract.InteropInterfaceType { imports["github.com/google/uuid"] = struct{}{} + imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{} imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{} ctr.SafeMethods[i].ReturnType = "uuid.UUID, result.Iterator" ctr.SafeMethods[i].CallFlag = "SessionIterator" + ctr.HasIterator = true } else { imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{} ctr.SafeMethods[i].ReturnType = "stackitem.Item"