Merge pull request #2766 from nspcc-dev/rpc-wrapper-autogen
Rpc wrapper autogeneration
This commit is contained in:
commit
b590d4ca04
18 changed files with 1330 additions and 100 deletions
|
@ -7,39 +7,58 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/rpcbinding"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var generatorFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "Configuration file to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
Usage: "Read contract manifest (*.manifest.json) file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "Output of the compiled contract",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "hash",
|
||||
Usage: "Smart-contract hash",
|
||||
},
|
||||
}
|
||||
|
||||
var generateWrapperCmd = cli.Command{
|
||||
Name: "generate-wrapper",
|
||||
Usage: "generate wrapper to use in other contracts",
|
||||
UsageText: "neo-go contract generate-wrapper --manifest <file.json> --out <file.go> --hash <hash>",
|
||||
Description: ``,
|
||||
Action: contractGenerateWrapper,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "Configuration file to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
Usage: "Read contract manifest (*.manifest.json) file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "Output of the compiled contract",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "hash",
|
||||
Usage: "Smart-contract hash",
|
||||
},
|
||||
},
|
||||
Flags: generatorFlags,
|
||||
}
|
||||
|
||||
var generateRPCWrapperCmd = cli.Command{
|
||||
Name: "generate-rpcwrapper",
|
||||
Usage: "generate RPC wrapper to use for data reads",
|
||||
UsageText: "neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> --hash <hash>",
|
||||
Action: contractGenerateRPCWrapper,
|
||||
Flags: generatorFlags,
|
||||
}
|
||||
|
||||
// contractGenerateWrapper deploys contract.
|
||||
func contractGenerateWrapper(ctx *cli.Context) error {
|
||||
return contractGenerateSomething(ctx, binding.Generate)
|
||||
}
|
||||
|
||||
func contractGenerateRPCWrapper(ctx *cli.Context) error {
|
||||
return contractGenerateSomething(ctx, rpcbinding.Generate)
|
||||
}
|
||||
|
||||
// contractGenerateSomething reads generator parameters and calls the given callback.
|
||||
func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -74,7 +93,7 @@ func contractGenerateWrapper(ctx *cli.Context) error {
|
|||
|
||||
cfg.Output = f
|
||||
|
||||
err = binding.Generate(cfg)
|
||||
err = cb(cfg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("error during generation: %w", err), 1)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -44,6 +45,15 @@ func TestGenerate(t *testing.T) {
|
|||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
manifest.Method{
|
||||
Name: "zum",
|
||||
Parameters: []manifest.Parameter{
|
||||
manifest.NewParameter("type", smartcontract.IntegerType),
|
||||
manifest.NewParameter("typev", smartcontract.IntegerType),
|
||||
manifest.NewParameter("func", smartcontract.IntegerType),
|
||||
},
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
},
|
||||
manifest.Method{
|
||||
Name: "justExecute",
|
||||
Parameters: []manifest.Parameter{
|
||||
|
@ -171,6 +181,11 @@ func Sum3() int {
|
|||
return neogointernal.CallWithToken(Hash, "sum3", int(contract.ReadOnly)).(int)
|
||||
}
|
||||
|
||||
// Zum invokes ` + "`zum`" + ` method of contract.
|
||||
func Zum(typev int, typev_ int, funcv int) int {
|
||||
return neogointernal.CallWithToken(Hash, "zum", int(contract.All), typev, typev_, funcv).(int)
|
||||
}
|
||||
|
||||
// JustExecute invokes ` + "`justExecute`" + ` method of contract.
|
||||
func JustExecute(arr []interface{}) {
|
||||
neogointernal.CallWithTokenNoRet(Hash, "justExecute", int(contract.All), arr)
|
||||
|
@ -224,6 +239,7 @@ func TestGenerateValidPackageName(t *testing.T) {
|
|||
Name: "get",
|
||||
Parameters: []manifest.Parameter{},
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -239,7 +255,7 @@ func TestGenerateValidPackageName(t *testing.T) {
|
|||
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{generateWrapperCmd}
|
||||
app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd}
|
||||
require.NoError(t, app.Run([]string{"", "generate-wrapper",
|
||||
"--manifest", manifestFile,
|
||||
"--out", outFile,
|
||||
|
@ -261,9 +277,85 @@ const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\x
|
|||
|
||||
// Get invokes `+"`get`"+` method of contract.
|
||||
func Get() int {
|
||||
return neogointernal.CallWithToken(Hash, "get", int(contract.All)).(int)
|
||||
return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int)
|
||||
}
|
||||
`, string(data))
|
||||
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper",
|
||||
"--manifest", manifestFile,
|
||||
"--out", outFile,
|
||||
"--hash", "0x" + h.StringLE(),
|
||||
}))
|
||||
|
||||
data, err = os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `// Package myspacecontract contains RPC wrappers for My space contract contract.
|
||||
package myspacecontract
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0x4, 0x8, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x0, 0x1, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad, 0xbe, 0xef, 0x3, 0x4}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, 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}
|
||||
}
|
||||
|
||||
|
||||
// Get invokes `+"`get`"+` method of contract.
|
||||
func (c *ContractReader) Get() (*big.Int, error) {
|
||||
return unwrap.BigInt(c.invoker.Call(Hash, "get"))
|
||||
}
|
||||
`, string(data))
|
||||
}
|
||||
|
||||
func TestGenerateRPCBindings(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd}
|
||||
|
||||
outFile := filepath.Join(tmpDir, "out.go")
|
||||
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper",
|
||||
"--manifest", filepath.Join("testdata", "nex", "nex.manifest.json"),
|
||||
"--out", outFile,
|
||||
"--hash", "0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8",
|
||||
}))
|
||||
|
||||
data, err := os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows.
|
||||
expected, err := os.ReadFile(filepath.Join("testdata", "nex", "nex.go"))
|
||||
require.NoError(t, err)
|
||||
expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows.
|
||||
require.Equal(t, string(expected), string(data))
|
||||
|
||||
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper",
|
||||
"--manifest", filepath.Join("testdata", "nameservice", "nns.manifest.json"),
|
||||
"--out", outFile,
|
||||
"--hash", "0x50ac1c37690cc2cfc594472833cf57505d5f46de",
|
||||
}))
|
||||
|
||||
data, err = os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows.
|
||||
expected, err = os.ReadFile(filepath.Join("testdata", "nameservice", "nns.go"))
|
||||
require.NoError(t, err)
|
||||
expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows.
|
||||
require.Equal(t, string(expected), string(data))
|
||||
}
|
||||
|
||||
func TestGenerate_Errors(t *testing.T) {
|
||||
|
|
|
@ -182,6 +182,7 @@ func NewCommands() []cli.Command {
|
|||
Flags: deployFlags,
|
||||
},
|
||||
generateWrapperCmd,
|
||||
generateRPCWrapperCmd,
|
||||
{
|
||||
Name: "invokefunction",
|
||||
Usage: "invoke deployed contract on the blockchain",
|
||||
|
|
62
cli/smartcontract/testdata/nameservice/nns.go
vendored
Normal file
62
cli/smartcontract/testdata/nameservice/nns.go
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Package nameservice contains RPC wrappers for NameService contract.
|
||||
package nameservice
|
||||
|
||||
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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0xde, 0x46, 0x5f, 0x5d, 0x50, 0x57, 0xcf, 0x33, 0x28, 0x47, 0x94, 0xc5, 0xcf, 0xc2, 0xc, 0x69, 0x37, 0x1c, 0xac, 0x50}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep11.Invoker
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
nep11.NonDivisibleReader
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using Hash and the given Invoker.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
return &ContractReader{*nep11.NewNonDivisibleReader(invoker, Hash), invoker}
|
||||
}
|
||||
|
||||
|
||||
// Roots invokes `roots` method of contract.
|
||||
func (c *ContractReader) Roots() (stackitem.Item, error) {
|
||||
return unwrap.Item(c.invoker.Call(Hash, "roots"))
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// IsAvailable invokes `isAvailable` method of contract.
|
||||
func (c *ContractReader) IsAvailable(name string) (bool, error) {
|
||||
return unwrap.Bool(c.invoker.Call(Hash, "isAvailable", name))
|
||||
}
|
||||
|
||||
// GetRecord invokes `getRecord` method of contract.
|
||||
func (c *ContractReader) GetRecord(name string, typev *big.Int) (string, error) {
|
||||
return unwrap.UTF8String(c.invoker.Call(Hash, "getRecord", name, typev))
|
||||
}
|
||||
|
||||
// GetAllRecords invokes `getAllRecords` method of contract.
|
||||
func (c *ContractReader) GetAllRecords(name string) (stackitem.Item, error) {
|
||||
return unwrap.Item(c.invoker.Call(Hash, "getAllRecords", 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))
|
||||
}
|
441
cli/smartcontract/testdata/nameservice/nns.manifest.json
vendored
Normal file
441
cli/smartcontract/testdata/nameservice/nns.manifest.json
vendored
Normal file
|
@ -0,0 +1,441 @@
|
|||
{
|
||||
"abi" : {
|
||||
"events" : [
|
||||
{
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "from",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"name" : "to",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"name" : "amount",
|
||||
"type" : "Integer"
|
||||
},
|
||||
{
|
||||
"type" : "ByteArray",
|
||||
"name" : "tokenId"
|
||||
}
|
||||
],
|
||||
"name" : "Transfer"
|
||||
},
|
||||
{
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "String",
|
||||
"name" : "name"
|
||||
},
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "oldAdmin"
|
||||
},
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "newAdmin"
|
||||
}
|
||||
],
|
||||
"name" : "SetAdmin"
|
||||
},
|
||||
{
|
||||
"name" : "Renew",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "oldExpiration"
|
||||
},
|
||||
{
|
||||
"name" : "newExpiration",
|
||||
"type" : "Integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"methods" : [
|
||||
{
|
||||
"safe" : true,
|
||||
"parameters" : [],
|
||||
"name" : "symbol",
|
||||
"returntype" : "String",
|
||||
"offset" : 0
|
||||
},
|
||||
{
|
||||
"parameters" : [],
|
||||
"name" : "decimals",
|
||||
"returntype" : "Integer",
|
||||
"safe" : true,
|
||||
"offset" : 6
|
||||
},
|
||||
{
|
||||
"offset" : 8,
|
||||
"returntype" : "Integer",
|
||||
"name" : "totalSupply",
|
||||
"parameters" : [],
|
||||
"safe" : true
|
||||
},
|
||||
{
|
||||
"offset" : 53,
|
||||
"safe" : true,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "ByteArray",
|
||||
"name" : "tokenId"
|
||||
}
|
||||
],
|
||||
"name" : "ownerOf",
|
||||
"returntype" : "Hash160"
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"name" : "properties",
|
||||
"returntype" : "Map",
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "ByteArray",
|
||||
"name" : "tokenId"
|
||||
}
|
||||
],
|
||||
"offset" : 184
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"returntype" : "Integer",
|
||||
"name" : "balanceOf",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "owner",
|
||||
"type" : "Hash160"
|
||||
}
|
||||
],
|
||||
"offset" : 341
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"returntype" : "InteropInterface",
|
||||
"name" : "tokens",
|
||||
"parameters" : [],
|
||||
"offset" : 453
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"name" : "tokensOf",
|
||||
"returntype" : "InteropInterface",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "owner",
|
||||
"type" : "Hash160"
|
||||
}
|
||||
],
|
||||
"offset" : 494
|
||||
},
|
||||
{
|
||||
"offset" : 600,
|
||||
"safe" : false,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "to"
|
||||
},
|
||||
{
|
||||
"name" : "tokenId",
|
||||
"type" : "ByteArray"
|
||||
},
|
||||
{
|
||||
"name" : "data",
|
||||
"type" : "Any"
|
||||
}
|
||||
],
|
||||
"name" : "transfer",
|
||||
"returntype" : "Boolean"
|
||||
},
|
||||
{
|
||||
"name" : "update",
|
||||
"returntype" : "Void",
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "ByteArray",
|
||||
"name" : "nef"
|
||||
},
|
||||
{
|
||||
"name" : "manifest",
|
||||
"type" : "String"
|
||||
}
|
||||
],
|
||||
"safe" : false,
|
||||
"offset" : 1121
|
||||
},
|
||||
{
|
||||
"offset" : 1291,
|
||||
"returntype" : "Void",
|
||||
"name" : "addRoot",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "root",
|
||||
"type" : "String"
|
||||
}
|
||||
],
|
||||
"safe" : false
|
||||
},
|
||||
{
|
||||
"offset" : 1725,
|
||||
"safe" : true,
|
||||
"parameters" : [],
|
||||
"returntype" : "InteropInterface",
|
||||
"name" : "roots"
|
||||
},
|
||||
{
|
||||
"offset" : 1757,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Array",
|
||||
"name" : "priceList"
|
||||
}
|
||||
],
|
||||
"name" : "setPrice",
|
||||
"returntype" : "Void",
|
||||
"safe" : false
|
||||
},
|
||||
{
|
||||
"offset" : 1952,
|
||||
"safe" : true,
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "length",
|
||||
"type" : "Integer"
|
||||
}
|
||||
],
|
||||
"name" : "getPrice",
|
||||
"returntype" : "Integer"
|
||||
},
|
||||
{
|
||||
"offset" : 2017,
|
||||
"safe" : true,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "String",
|
||||
"name" : "name"
|
||||
}
|
||||
],
|
||||
"name" : "isAvailable",
|
||||
"returntype" : "Boolean"
|
||||
},
|
||||
{
|
||||
"offset" : 2405,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "String",
|
||||
"name" : "name"
|
||||
},
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "owner"
|
||||
}
|
||||
],
|
||||
"name" : "register",
|
||||
"returntype" : "Boolean",
|
||||
"safe" : false
|
||||
},
|
||||
{
|
||||
"name" : "renew",
|
||||
"returntype" : "Integer",
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "String",
|
||||
"name" : "name"
|
||||
}
|
||||
],
|
||||
"safe" : false,
|
||||
"offset" : 3113
|
||||
},
|
||||
{
|
||||
"offset" : 3123,
|
||||
"safe" : false,
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
},
|
||||
{
|
||||
"name" : "years",
|
||||
"type" : "Integer"
|
||||
}
|
||||
],
|
||||
"name" : "renew",
|
||||
"returntype" : "Integer"
|
||||
},
|
||||
{
|
||||
"offset" : 3697,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "String",
|
||||
"name" : "name"
|
||||
},
|
||||
{
|
||||
"name" : "admin",
|
||||
"type" : "Hash160"
|
||||
}
|
||||
],
|
||||
"name" : "setAdmin",
|
||||
"returntype" : "Void",
|
||||
"safe" : false
|
||||
},
|
||||
{
|
||||
"safe" : false,
|
||||
"returntype" : "Void",
|
||||
"name" : "setRecord",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "type"
|
||||
},
|
||||
{
|
||||
"name" : "data",
|
||||
"type" : "String"
|
||||
}
|
||||
],
|
||||
"offset" : 3921
|
||||
},
|
||||
{
|
||||
"name" : "getRecord",
|
||||
"returntype" : "String",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "type"
|
||||
}
|
||||
],
|
||||
"safe" : true,
|
||||
"offset" : 5877
|
||||
},
|
||||
{
|
||||
"returntype" : "InteropInterface",
|
||||
"name" : "getAllRecords",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
}
|
||||
],
|
||||
"safe" : true,
|
||||
"offset" : 6201
|
||||
},
|
||||
{
|
||||
"safe" : false,
|
||||
"returntype" : "Void",
|
||||
"name" : "deleteRecord",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "type"
|
||||
}
|
||||
],
|
||||
"offset" : 6281
|
||||
},
|
||||
{
|
||||
"offset" : 6565,
|
||||
"name" : "resolve",
|
||||
"returntype" : "String",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "name",
|
||||
"type" : "String"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "type"
|
||||
}
|
||||
],
|
||||
"safe" : true
|
||||
},
|
||||
{
|
||||
"safe" : false,
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "data",
|
||||
"type" : "Any"
|
||||
},
|
||||
{
|
||||
"name" : "update",
|
||||
"type" : "Boolean"
|
||||
}
|
||||
],
|
||||
"name" : "_deploy",
|
||||
"returntype" : "Void",
|
||||
"offset" : 7152
|
||||
},
|
||||
{
|
||||
"offset" : 7514,
|
||||
"parameters" : [],
|
||||
"returntype" : "Void",
|
||||
"name" : "_initialize",
|
||||
"safe" : false
|
||||
}
|
||||
]
|
||||
},
|
||||
"supportedstandards" : [
|
||||
"NEP-11"
|
||||
],
|
||||
"permissions" : [
|
||||
{
|
||||
"contract" : "0x726cb6e0cd8628a1350a611384688911ab75f51b",
|
||||
"methods" : [
|
||||
"ripemd160"
|
||||
]
|
||||
},
|
||||
{
|
||||
"contract" : "0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0",
|
||||
"methods" : [
|
||||
"atoi",
|
||||
"deserialize",
|
||||
"serialize",
|
||||
"stringSplit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
|
||||
"methods" : [
|
||||
"getCommittee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd",
|
||||
"methods" : [
|
||||
"getContract",
|
||||
"update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"methods" : [
|
||||
"onNEP11Payment"
|
||||
],
|
||||
"contract" : "*"
|
||||
}
|
||||
],
|
||||
"features" : {},
|
||||
"name" : "NameService",
|
||||
"trusts" : [],
|
||||
"extra" : {
|
||||
"Author" : "The Neo Project",
|
||||
"Description" : "Neo Name Service",
|
||||
"Email" : "dev@neo.org"
|
||||
},
|
||||
"groups" : []
|
||||
}
|
52
cli/smartcontract/testdata/nex/nex.go
vendored
Normal file
52
cli/smartcontract/testdata/nex/nex.go
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Package nextoken contains RPC wrappers for NEX Token contract.
|
||||
package nextoken
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0xa8, 0x1a, 0xa1, 0xf0, 0x4b, 0xf, 0xdc, 0x4a, 0xa2, 0xce, 0xd5, 0xbf, 0xc6, 0x22, 0xcf, 0xe8, 0x9, 0x7f, 0xa6, 0xa2}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep17.Invoker
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
nep17.TokenReader
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using Hash and the given Invoker.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
return &ContractReader{*nep17.NewReader(invoker, Hash), invoker}
|
||||
}
|
||||
|
||||
|
||||
// Cap invokes `cap` method of contract.
|
||||
func (c *ContractReader) Cap() (*big.Int, error) {
|
||||
return unwrap.BigInt(c.invoker.Call(Hash, "cap"))
|
||||
}
|
||||
|
||||
// GetMinter invokes `getMinter` method of contract.
|
||||
func (c *ContractReader) GetMinter() (*keys.PublicKey, error) {
|
||||
return unwrap.PublicKey(c.invoker.Call(Hash, "getMinter"))
|
||||
}
|
||||
|
||||
// GetOwner invokes `getOwner` method of contract.
|
||||
func (c *ContractReader) GetOwner() (util.Uint160, error) {
|
||||
return unwrap.Uint160(c.invoker.Call(Hash, "getOwner"))
|
||||
}
|
||||
|
||||
// TotalMinted invokes `totalMinted` method of contract.
|
||||
func (c *ContractReader) TotalMinted() (*big.Int, error) {
|
||||
return unwrap.BigInt(c.invoker.Call(Hash, "totalMinted"))
|
||||
}
|
275
cli/smartcontract/testdata/nex/nex.manifest.json
vendored
Normal file
275
cli/smartcontract/testdata/nex/nex.manifest.json
vendored
Normal file
|
@ -0,0 +1,275 @@
|
|||
{
|
||||
"name" : "NEX Token",
|
||||
"abi" : {
|
||||
"events" : [
|
||||
{
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "from"
|
||||
},
|
||||
{
|
||||
"name" : "to",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"name" : "amount",
|
||||
"type" : "Integer"
|
||||
}
|
||||
],
|
||||
"name" : "Transfer"
|
||||
},
|
||||
{
|
||||
"name" : "OnMint",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "from",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "to"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "amount"
|
||||
},
|
||||
{
|
||||
"name" : "swapId",
|
||||
"type" : "Integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"methods" : [
|
||||
{
|
||||
"parameters" : [],
|
||||
"offset" : 0,
|
||||
"name" : "_initialize",
|
||||
"safe" : false,
|
||||
"returntype" : "Void"
|
||||
},
|
||||
{
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Any",
|
||||
"name" : "data"
|
||||
},
|
||||
{
|
||||
"name" : "isUpdate",
|
||||
"type" : "Boolean"
|
||||
}
|
||||
],
|
||||
"offset" : 3,
|
||||
"name" : "_deploy",
|
||||
"safe" : false,
|
||||
"returntype" : "Void"
|
||||
},
|
||||
{
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "holder"
|
||||
}
|
||||
],
|
||||
"offset" : 484,
|
||||
"returntype" : "Integer",
|
||||
"safe" : true,
|
||||
"name" : "balanceOf"
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"returntype" : "Integer",
|
||||
"name" : "cap",
|
||||
"offset" : 1881,
|
||||
"parameters" : []
|
||||
},
|
||||
{
|
||||
"name" : "changeMinter",
|
||||
"safe" : false,
|
||||
"returntype" : "Void",
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "newMinter",
|
||||
"type" : "PublicKey"
|
||||
}
|
||||
],
|
||||
"offset" : 1678
|
||||
},
|
||||
{
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Hash160",
|
||||
"name" : "newOwner"
|
||||
}
|
||||
],
|
||||
"offset" : 1659,
|
||||
"name" : "changeOwner",
|
||||
"safe" : false,
|
||||
"returntype" : "Void"
|
||||
},
|
||||
{
|
||||
"offset" : 466,
|
||||
"parameters" : [],
|
||||
"safe" : true,
|
||||
"name" : "decimals",
|
||||
"returntype" : "Integer"
|
||||
},
|
||||
{
|
||||
"returntype" : "Void",
|
||||
"safe" : false,
|
||||
"name" : "destroy",
|
||||
"parameters" : [],
|
||||
"offset" : 1194
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"returntype" : "PublicKey",
|
||||
"name" : "getMinter",
|
||||
"offset" : 1671,
|
||||
"parameters" : []
|
||||
},
|
||||
{
|
||||
"parameters" : [],
|
||||
"offset" : 1652,
|
||||
"name" : "getOwner",
|
||||
"safe" : true,
|
||||
"returntype" : "Hash160"
|
||||
},
|
||||
{
|
||||
"name" : "maxSupply",
|
||||
"safe" : false,
|
||||
"returntype" : "Integer",
|
||||
"parameters" : [],
|
||||
"offset" : 468
|
||||
},
|
||||
{
|
||||
"offset" : 1222,
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "from",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"name" : "to",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "amount"
|
||||
},
|
||||
{
|
||||
"name" : "swapId",
|
||||
"type" : "Integer"
|
||||
},
|
||||
{
|
||||
"name" : "signature",
|
||||
"type" : "Signature"
|
||||
},
|
||||
{
|
||||
"name" : "data",
|
||||
"type" : "Any"
|
||||
}
|
||||
],
|
||||
"safe" : false,
|
||||
"name" : "mint",
|
||||
"returntype" : "Void"
|
||||
},
|
||||
{
|
||||
"safe" : true,
|
||||
"name" : "symbol",
|
||||
"returntype" : "String",
|
||||
"parameters" : [],
|
||||
"offset" : 460
|
||||
},
|
||||
{
|
||||
"offset" : 1854,
|
||||
"parameters" : [],
|
||||
"name" : "totalMinted",
|
||||
"safe" : true,
|
||||
"returntype" : "Integer"
|
||||
},
|
||||
{
|
||||
"offset" : 478,
|
||||
"parameters" : [],
|
||||
"name" : "totalSupply",
|
||||
"safe" : true,
|
||||
"returntype" : "Integer"
|
||||
},
|
||||
{
|
||||
"offset" : 543,
|
||||
"parameters" : [
|
||||
{
|
||||
"name" : "from",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"name" : "to",
|
||||
"type" : "Hash160"
|
||||
},
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "amount"
|
||||
},
|
||||
{
|
||||
"name" : "data",
|
||||
"type" : "Any"
|
||||
}
|
||||
],
|
||||
"name" : "transfer",
|
||||
"safe" : false,
|
||||
"returntype" : "Boolean"
|
||||
},
|
||||
{
|
||||
"offset" : 1205,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "ByteArray",
|
||||
"name" : "nef"
|
||||
},
|
||||
{
|
||||
"name" : "manifest",
|
||||
"type" : "ByteArray"
|
||||
}
|
||||
],
|
||||
"safe" : false,
|
||||
"returntype" : "Void",
|
||||
"name" : "update"
|
||||
},
|
||||
{
|
||||
"offset" : 1717,
|
||||
"parameters" : [
|
||||
{
|
||||
"type" : "Integer",
|
||||
"name" : "newCap"
|
||||
}
|
||||
],
|
||||
"returntype" : "Void",
|
||||
"safe" : false,
|
||||
"name" : "updateCap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"supportedstandards" : [
|
||||
"NEP-17"
|
||||
],
|
||||
"extra" : null,
|
||||
"trusts" : [],
|
||||
"features" : {},
|
||||
"groups" : [],
|
||||
"permissions" : [
|
||||
{
|
||||
"methods" : [
|
||||
"onNEP17Payment"
|
||||
],
|
||||
"contract" : "*"
|
||||
},
|
||||
{
|
||||
"methods" : [
|
||||
"update",
|
||||
"destroy"
|
||||
],
|
||||
"contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -410,6 +410,43 @@ given RPC server and wallet and paying 0.00001 extra GAS for this transaction):
|
|||
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json -g 0.00001 f84d6a337fbc3d3a201d41da99e86b479e7a2554 balanceOf AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
|
||||
```
|
||||
|
||||
### Generating contract bindings
|
||||
To be able to use deployed contract from another contract one needs to have
|
||||
its interface definition (exported methods and hash). While it is possible to
|
||||
use generic contract.Call interop interface, it's not very convenient and
|
||||
efficient. NeoGo can autogenerate contract bindings in Go language for any
|
||||
deployed contract based on its manifest, it creates a Go source file with all
|
||||
of the contract's methods that then can be imported and used as a regular Go
|
||||
package.
|
||||
|
||||
```
|
||||
$ ./bin/neo-go contract generate-wrapper --manifest manifest.json --out wrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176
|
||||
```
|
||||
|
||||
Notice that some structured types can be omitted this way (when a function
|
||||
returns some structure it's just an "Array" type in the manifest with no
|
||||
internal details), but if the contract you're using is written in Go
|
||||
originally you can create a specific configuration file during compilation
|
||||
that will add this data for wrapper generator to use:
|
||||
|
||||
```
|
||||
$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.nef --manifest manifest.json --bindings contract.bindings.yml
|
||||
$ ./bin/neo-go contract generate-wrapper --manifest manifest.json --config contract.bindings.yml --out wrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176
|
||||
```
|
||||
|
||||
### Generating RPC contract bindings
|
||||
To simplify interacting with the contract via RPC you can generate
|
||||
contract-specific RPC bindings with the "generate-rpcwrapper" command. At the
|
||||
moment it only works for safe (read-only) methods. 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
|
||||
```
|
||||
|
||||
## Smart contract examples
|
||||
|
||||
Some examples are provided in the [examples directory](../examples). For more
|
||||
|
|
|
@ -146,6 +146,16 @@ func Uint256(r *result.Invoke, err error) (util.Uint256, error) {
|
|||
return util.Uint256DecodeBytesBE(b)
|
||||
}
|
||||
|
||||
// PublicKey expects correct execution (HALT state) with a single stack item
|
||||
// returned. A public key is extracted from this item and returned.
|
||||
func PublicKey(r *result.Invoke, err error) (*keys.PublicKey, error) {
|
||||
b, err := Bytes(r, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keys.NewPublicKeyFromBytes(b, elliptic.P256())
|
||||
}
|
||||
|
||||
// SessionIterator expects correct execution (HALT state) with a single stack
|
||||
// item returned. If this item is an iterator it's returned to the caller along
|
||||
// with the session ID. Notice that this function also returns successfully
|
||||
|
|
|
@ -43,6 +43,9 @@ func TestStdErrors(t *testing.T) {
|
|||
func(r *result.Invoke, err error) (interface{}, error) {
|
||||
return Uint256(r, err)
|
||||
},
|
||||
func(r *result.Invoke, err error) (interface{}, error) {
|
||||
return PublicKey(r, err)
|
||||
},
|
||||
func(r *result.Invoke, err error) (interface{}, error) {
|
||||
_, _, err = SessionIterator(r, err)
|
||||
return nil, err
|
||||
|
@ -189,6 +192,18 @@ func TestUint256(t *testing.T) {
|
|||
require.Equal(t, util.Uint256{1, 2, 3}, u)
|
||||
}
|
||||
|
||||
func TestPublicKey(t *testing.T) {
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = PublicKey(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(util.Uint160{1, 2, 3}.BytesBE())}}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
pk, err := PublicKey(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(k.PublicKey().Bytes())}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, k.PublicKey(), pk)
|
||||
}
|
||||
|
||||
func TestSessionIterator(t *testing.T) {
|
||||
_, _, err := SessionIterator(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||
require.Error(t, err)
|
||||
|
|
|
@ -3,6 +3,7 @@ package binding
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -55,24 +56,24 @@ type (
|
|||
Output io.Writer `yaml:"-"`
|
||||
}
|
||||
|
||||
contractTmpl struct {
|
||||
ContractTmpl struct {
|
||||
PackageName string
|
||||
ContractName string
|
||||
Imports []string
|
||||
Hash string
|
||||
Methods []methodTmpl
|
||||
Methods []MethodTmpl
|
||||
}
|
||||
|
||||
methodTmpl struct {
|
||||
MethodTmpl struct {
|
||||
Name string
|
||||
NameABI string
|
||||
CallFlag string
|
||||
Comment string
|
||||
Arguments []paramTmpl
|
||||
Arguments []ParamTmpl
|
||||
ReturnType string
|
||||
}
|
||||
|
||||
paramTmpl struct {
|
||||
ParamTmpl struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
@ -88,15 +89,15 @@ func NewConfig() Config {
|
|||
|
||||
// Generate writes Go file containing smartcontract bindings to the `cfg.Output`.
|
||||
func Generate(cfg Config) error {
|
||||
ctr, err := templateFromManifest(cfg)
|
||||
ctr, err := TemplateFromManifest(cfg, scTypeToGo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctr.Imports = append(ctr.Imports, "github.com/nspcc-dev/neo-go/pkg/interop/contract")
|
||||
ctr.Imports = append(ctr.Imports, "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal")
|
||||
sort.Strings(ctr.Imports)
|
||||
|
||||
tmp, err := template.New("generate").Funcs(template.FuncMap{
|
||||
"lowerFirst": lowerFirst,
|
||||
"scTypeToGo": scTypeToGo,
|
||||
}).Parse(srcTmpl)
|
||||
tmp, err := template.New("generate").Parse(srcTmpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -104,46 +105,52 @@ func Generate(cfg Config) error {
|
|||
return tmp.Execute(cfg.Output, ctr)
|
||||
}
|
||||
|
||||
func scTypeToGo(typ smartcontract.ParamType) string {
|
||||
func scTypeToGo(name string, typ smartcontract.ParamType, overrides map[string]Override) (string, string) {
|
||||
if over, ok := overrides[name]; ok {
|
||||
return over.TypeName, over.Package
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case smartcontract.AnyType:
|
||||
return "interface{}"
|
||||
return "interface{}", ""
|
||||
case smartcontract.BoolType:
|
||||
return "bool"
|
||||
return "bool", ""
|
||||
case smartcontract.IntegerType:
|
||||
return "int"
|
||||
return "int", ""
|
||||
case smartcontract.ByteArrayType:
|
||||
return "[]byte"
|
||||
return "[]byte", ""
|
||||
case smartcontract.StringType:
|
||||
return "string"
|
||||
return "string", ""
|
||||
case smartcontract.Hash160Type:
|
||||
return "interop.Hash160"
|
||||
return "interop.Hash160", "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
case smartcontract.Hash256Type:
|
||||
return "interop.Hash256"
|
||||
return "interop.Hash256", "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
case smartcontract.PublicKeyType:
|
||||
return "interop.PublicKey"
|
||||
return "interop.PublicKey", "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
case smartcontract.SignatureType:
|
||||
return "interop.Signature"
|
||||
return "interop.Signature", "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
case smartcontract.ArrayType:
|
||||
return "[]interface{}"
|
||||
return "[]interface{}", ""
|
||||
case smartcontract.MapType:
|
||||
return "map[string]interface{}"
|
||||
return "map[string]interface{}", ""
|
||||
case smartcontract.InteropInterfaceType:
|
||||
return "interface{}"
|
||||
return "interface{}", ""
|
||||
case smartcontract.VoidType:
|
||||
return ""
|
||||
return "", ""
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func templateFromManifest(cfg Config) (contractTmpl, error) {
|
||||
// TemplateFromManifest create a contract template using the given configuration
|
||||
// and type conversion function.
|
||||
func TemplateFromManifest(cfg Config, scTypeConverter func(string, smartcontract.ParamType, map[string]Override) (string, string)) (ContractTmpl, error) {
|
||||
hStr := ""
|
||||
for _, b := range cfg.Hash.BytesBE() {
|
||||
hStr += fmt.Sprintf("\\x%02x", b)
|
||||
}
|
||||
|
||||
ctr := contractTmpl{
|
||||
ctr := ContractTmpl{
|
||||
PackageName: cfg.Package,
|
||||
ContractName: cfg.Manifest.Name,
|
||||
Hash: hStr,
|
||||
|
@ -169,9 +176,6 @@ func templateFromManifest(cfg Config) (contractTmpl, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/interop/contract"] = struct{}{}
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"] = struct{}{}
|
||||
|
||||
// Consider `perform(a)` and `perform(a, b)` methods.
|
||||
// First, try to export the second method with `Perform2` name.
|
||||
// If `perform2` is already in the manifest, use `perform_2` with as many underscores
|
||||
|
@ -187,7 +191,7 @@ func templateFromManifest(cfg Config) (contractTmpl, error) {
|
|||
}
|
||||
seen[name] = true
|
||||
|
||||
mtd := methodTmpl{
|
||||
mtd := MethodTmpl{
|
||||
Name: upperFirst(name),
|
||||
NameABI: m.Name,
|
||||
CallFlag: callflag.All.String(),
|
||||
|
@ -198,49 +202,42 @@ func templateFromManifest(cfg Config) (contractTmpl, error) {
|
|||
} else if m.Safe {
|
||||
mtd.CallFlag = callflag.ReadOnly.String()
|
||||
}
|
||||
|
||||
var varnames = make(map[string]bool)
|
||||
for i := range m.Parameters {
|
||||
name := m.Parameters[i].Name
|
||||
if name == "" {
|
||||
return ctr, fmt.Errorf("manifest ABI method %q/%d: parameter #%d is unnamed", m.Name, len(m.Parameters), i)
|
||||
}
|
||||
|
||||
var typeStr string
|
||||
if over, ok := cfg.Overrides[m.Name+"."+name]; ok {
|
||||
typeStr = over.TypeName
|
||||
if over.Package != "" {
|
||||
imports[over.Package] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
typeStr = scTypeToGo(m.Parameters[i].Type)
|
||||
typeStr, pkg := scTypeConverter(m.Name+"."+name, m.Parameters[i].Type, cfg.Overrides)
|
||||
if pkg != "" {
|
||||
imports[pkg] = struct{}{}
|
||||
}
|
||||
|
||||
mtd.Arguments = append(mtd.Arguments, paramTmpl{
|
||||
if token.IsKeyword(name) {
|
||||
name = name + "v"
|
||||
}
|
||||
for varnames[name] {
|
||||
name = name + "_"
|
||||
}
|
||||
varnames[name] = true
|
||||
mtd.Arguments = append(mtd.Arguments, ParamTmpl{
|
||||
Name: name,
|
||||
Type: typeStr,
|
||||
})
|
||||
}
|
||||
|
||||
if over, ok := cfg.Overrides[m.Name]; ok {
|
||||
mtd.ReturnType = over.TypeName
|
||||
if over.Package != "" {
|
||||
imports[over.Package] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
mtd.ReturnType = scTypeToGo(m.ReturnType)
|
||||
switch m.ReturnType {
|
||||
case smartcontract.Hash160Type, smartcontract.Hash256Type, smartcontract.InteropInterfaceType,
|
||||
smartcontract.SignatureType, smartcontract.PublicKeyType:
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/interop"] = struct{}{}
|
||||
}
|
||||
typeStr, pkg := scTypeConverter(m.Name, m.ReturnType, cfg.Overrides)
|
||||
if pkg != "" {
|
||||
imports[pkg] = struct{}{}
|
||||
}
|
||||
|
||||
mtd.ReturnType = typeStr
|
||||
ctr.Methods = append(ctr.Methods, mtd)
|
||||
}
|
||||
|
||||
for imp := range imports {
|
||||
ctr.Imports = append(ctr.Imports, imp)
|
||||
}
|
||||
sort.Strings(ctr.Imports)
|
||||
|
||||
return ctr, nil
|
||||
}
|
||||
|
@ -248,7 +245,3 @@ func templateFromManifest(cfg Config) (contractTmpl, error) {
|
|||
func upperFirst(s string) string {
|
||||
return strings.ToUpper(s[0:1]) + s[1:]
|
||||
}
|
||||
|
||||
func lowerFirst(s string) string {
|
||||
return strings.ToLower(s[0:1]) + s[1:]
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ var (
|
|||
)
|
||||
|
||||
var checks = map[string][]*Standard{
|
||||
manifest.NEP11StandardName: {nep11NonDivisible, nep11Divisible},
|
||||
manifest.NEP17StandardName: {nep17},
|
||||
manifest.NEP11Payable: {nep11payable},
|
||||
manifest.NEP17Payable: {nep17payable},
|
||||
manifest.NEP11StandardName: {Nep11NonDivisible, Nep11Divisible},
|
||||
manifest.NEP17StandardName: {Nep17},
|
||||
manifest.NEP11Payable: {Nep11Payable},
|
||||
manifest.NEP17Payable: {Nep17Payable},
|
||||
}
|
||||
|
||||
// Check checks if the manifest complies with all provided standards.
|
||||
|
@ -55,12 +55,12 @@ func check(m *manifest.Manifest, checkNames bool, standards ...string) error {
|
|||
}
|
||||
|
||||
// Comply if m has all methods and event from st manifest and they have the same signature.
|
||||
// Parameter names are ignored.
|
||||
// Parameter names are checked to exactly match the ones in the given standard.
|
||||
func Comply(m *manifest.Manifest, st *Standard) error {
|
||||
return comply(m, true, st)
|
||||
}
|
||||
|
||||
// ComplyABI is similar to comply but doesn't check parameter names.
|
||||
// ComplyABI is similar to Comply but doesn't check parameter names.
|
||||
func ComplyABI(m *manifest.Manifest, st *Standard) error {
|
||||
return comply(m, false, st)
|
||||
}
|
||||
|
|
|
@ -135,9 +135,9 @@ func TestCheck(t *testing.T) {
|
|||
m := manifest.NewManifest("Test")
|
||||
require.Error(t, Check(m, manifest.NEP17StandardName))
|
||||
|
||||
m.ABI.Methods = append(m.ABI.Methods, decimalTokenBase.ABI.Methods...)
|
||||
m.ABI.Methods = append(m.ABI.Methods, nep17.ABI.Methods...)
|
||||
m.ABI.Events = append(m.ABI.Events, nep17.ABI.Events...)
|
||||
m.ABI.Methods = append(m.ABI.Methods, DecimalTokenBase.ABI.Methods...)
|
||||
m.ABI.Methods = append(m.ABI.Methods, Nep17.ABI.Methods...)
|
||||
m.ABI.Events = append(m.ABI.Events, Nep17.ABI.Events...)
|
||||
require.NoError(t, Check(m, manifest.NEP17StandardName))
|
||||
require.NoError(t, CheckABI(m, manifest.NEP17StandardName))
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var nep11Base = &Standard{
|
||||
Base: decimalTokenBase,
|
||||
// Nep11Base is a Standard containing common NEP-11 methods.
|
||||
var Nep11Base = &Standard{
|
||||
Base: DecimalTokenBase,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
|
@ -66,8 +67,9 @@ var nep11Base = &Standard{
|
|||
},
|
||||
}
|
||||
|
||||
var nep11NonDivisible = &Standard{
|
||||
Base: nep11Base,
|
||||
// Nep11NonDivisible is a NEP-11 non-divisible Standard.
|
||||
var Nep11NonDivisible = &Standard{
|
||||
Base: Nep11Base,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
|
@ -84,8 +86,9 @@ var nep11NonDivisible = &Standard{
|
|||
},
|
||||
}
|
||||
|
||||
var nep11Divisible = &Standard{
|
||||
Base: nep11Base,
|
||||
// Nep11Divisible is a NEP-11 divisible Standard.
|
||||
var Nep11Divisible = &Standard{
|
||||
Base: Nep11Base,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
|
|
|
@ -5,8 +5,9 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var nep17 = &Standard{
|
||||
Base: decimalTokenBase,
|
||||
// Nep17 is a NEP-17 Standard.
|
||||
var Nep17 = &Standard{
|
||||
Base: DecimalTokenBase,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
|
|
|
@ -5,7 +5,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var nep11payable = &Standard{
|
||||
// Nep11Payable contains NEP-11's onNEP11Payment method definition.
|
||||
var Nep11Payable = &Standard{
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{{
|
||||
|
@ -22,7 +23,8 @@ var nep11payable = &Standard{
|
|||
},
|
||||
}
|
||||
|
||||
var nep17payable = &Standard{
|
||||
// Nep17Payable contains NEP-17's onNEP17Payment method definition.
|
||||
var Nep17Payable = &Standard{
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{{
|
||||
|
|
|
@ -5,7 +5,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var decimalTokenBase = &Standard{
|
||||
// DecimalTokenBase contains methods common to NEP-11 and NEP-17 token standards.
|
||||
var DecimalTokenBase = &Standard{
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
|
|
226
pkg/smartcontract/rpcbinding/binding.go
Normal file
226
pkg/smartcontract/rpcbinding/binding.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
package rpcbinding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"text/template"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
|
||||
)
|
||||
|
||||
const srcTmpl = `
|
||||
{{- define "METHOD" -}}
|
||||
// {{.Name}} {{.Comment}}
|
||||
func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}}
|
||||
{{- if ne $index 0}}, {{end}}
|
||||
{{- .Name}} {{.Type}}
|
||||
{{- end}}) {{if .ReturnType }}({{ .ReturnType }}, error) {
|
||||
return unwrap.{{.CallFlag}}(c.invoker.Call(Hash, "{{ .NameABI }}"{{/* CallFlag field is used for function name */}}
|
||||
{{- range $arg := .Arguments -}}, {{.Name}}{{end}}))
|
||||
{{- else -}} (*result.Invoke, error) {
|
||||
c.invoker.Call(Hash, "{{ .NameABI }}"
|
||||
{{- range $arg := .Arguments -}}, {{.Name}}{{end}})
|
||||
{{- end}}
|
||||
}
|
||||
{{- end -}}
|
||||
// Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract.
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
{{range $m := .Imports}} "{{ $m }}"
|
||||
{{end}})
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = {{ .Hash }}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
{{if or .IsNep11D .IsNep11ND}}nep11.Invoker
|
||||
{{end -}}
|
||||
{{if .IsNep17}}nep17.Invoker
|
||||
{{end -}}
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
{{if .IsNep11D}}nep11.DivisibleReader
|
||||
{{end -}}
|
||||
{{if .IsNep11ND}}nep11.NonDivisibleReader
|
||||
{{end -}}
|
||||
{{if .IsNep17}}nep17.TokenReader
|
||||
{{end -}}
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using Hash and the given Invoker.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
return &ContractReader{
|
||||
{{- if .IsNep11D}}*nep11.NewDivisibleReader(invoker, Hash), {{end}}
|
||||
{{- if .IsNep11ND}}*nep11.NewNonDivisibleReader(invoker, Hash), {{end}}
|
||||
{{- if .IsNep17}}*nep17.NewReader(invoker, Hash), {{end -}}
|
||||
invoker}
|
||||
}
|
||||
|
||||
{{range $m := .Methods}}
|
||||
{{template "METHOD" $m }}
|
||||
{{end}}`
|
||||
|
||||
var srcTemplate = template.Must(template.New("generate").Parse(srcTmpl))
|
||||
|
||||
type (
|
||||
ContractTmpl struct {
|
||||
binding.ContractTmpl
|
||||
IsNep11D bool
|
||||
IsNep11ND bool
|
||||
IsNep17 bool
|
||||
}
|
||||
)
|
||||
|
||||
// NewConfig initializes and returns a new config instance.
|
||||
func NewConfig() binding.Config {
|
||||
return binding.NewConfig()
|
||||
}
|
||||
|
||||
// Generate writes Go file containing smartcontract bindings to the `cfg.Output`.
|
||||
func Generate(cfg binding.Config) error {
|
||||
bctr, err := binding.TemplateFromManifest(cfg, scTypeToGo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctr := scTemplateToRPC(cfg, bctr)
|
||||
|
||||
return srcTemplate.Execute(cfg.Output, ctr)
|
||||
}
|
||||
|
||||
func dropManifestMethods(meths []binding.MethodTmpl, manifested []manifest.Method) []binding.MethodTmpl {
|
||||
for _, m := range manifested {
|
||||
for i := 0; i < len(meths); i++ {
|
||||
if meths[i].NameABI == m.Name && len(meths[i].Arguments) == len(m.Parameters) {
|
||||
meths = append(meths[:i], meths[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
return meths
|
||||
}
|
||||
|
||||
func dropStdMethods(meths []binding.MethodTmpl, std *standard.Standard) []binding.MethodTmpl {
|
||||
meths = dropManifestMethods(meths, std.Manifest.ABI.Methods)
|
||||
if std.Optional != nil {
|
||||
meths = dropManifestMethods(meths, std.Optional)
|
||||
}
|
||||
if std.Base != nil {
|
||||
return dropStdMethods(meths, std.Base)
|
||||
}
|
||||
return meths
|
||||
}
|
||||
|
||||
func scTypeToGo(name string, typ smartcontract.ParamType, overrides map[string]binding.Override) (string, string) {
|
||||
switch typ {
|
||||
case smartcontract.AnyType:
|
||||
return "interface{}", ""
|
||||
case smartcontract.BoolType:
|
||||
return "bool", ""
|
||||
case smartcontract.IntegerType:
|
||||
return "*big.Int", "math/big"
|
||||
case smartcontract.ByteArrayType:
|
||||
return "[]byte", ""
|
||||
case smartcontract.StringType:
|
||||
return "string", ""
|
||||
case smartcontract.Hash160Type:
|
||||
return "util.Uint160", "github.com/nspcc-dev/neo-go/pkg/util"
|
||||
case smartcontract.Hash256Type:
|
||||
return "util.Uint256", "github.com/nspcc-dev/neo-go/pkg/util"
|
||||
case smartcontract.PublicKeyType:
|
||||
return "*keys.PublicKey", "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
case smartcontract.SignatureType:
|
||||
return "[]byte", ""
|
||||
case smartcontract.ArrayType:
|
||||
return "[]interface{}", ""
|
||||
case smartcontract.MapType:
|
||||
return "*stackitem.Map", "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
case smartcontract.InteropInterfaceType:
|
||||
return "interface{}", ""
|
||||
case smartcontract.VoidType:
|
||||
return "", ""
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func scTemplateToRPC(cfg binding.Config, bctr binding.ContractTmpl) ContractTmpl {
|
||||
var imports = make(map[string]struct{})
|
||||
var ctr = ContractTmpl{ContractTmpl: bctr}
|
||||
for i := range ctr.Imports {
|
||||
imports[ctr.Imports[i]] = struct{}{}
|
||||
}
|
||||
ctr.Hash = fmt.Sprintf("%#v", cfg.Hash)
|
||||
for _, std := range cfg.Manifest.SupportedStandards {
|
||||
if std == manifest.NEP11StandardName {
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"] = struct{}{}
|
||||
if standard.ComplyABI(cfg.Manifest, standard.Nep11Divisible) == nil {
|
||||
ctr.Methods = dropStdMethods(ctr.Methods, standard.Nep11Divisible)
|
||||
ctr.IsNep11D = true
|
||||
} else if standard.ComplyABI(cfg.Manifest, standard.Nep11NonDivisible) == nil {
|
||||
ctr.Methods = dropStdMethods(ctr.Methods, standard.Nep11NonDivisible)
|
||||
ctr.IsNep11ND = true
|
||||
}
|
||||
break // Can't be NEP-17 at the same time.
|
||||
}
|
||||
if std == manifest.NEP17StandardName && standard.ComplyABI(cfg.Manifest, standard.Nep17) == nil {
|
||||
ctr.Methods = dropStdMethods(ctr.Methods, standard.Nep17)
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"] = struct{}{}
|
||||
ctr.IsNep17 = true
|
||||
break // Can't be NEP-11 at the same time.
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(ctr.Methods); i++ {
|
||||
abim := cfg.Manifest.ABI.GetMethod(ctr.Methods[i].NameABI, len(ctr.Methods[i].Arguments))
|
||||
if !abim.Safe {
|
||||
ctr.Methods = append(ctr.Methods[:i], ctr.Methods[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
// We're misusing CallFlag field for function name here.
|
||||
for i := range ctr.Methods {
|
||||
switch ctr.Methods[i].ReturnType {
|
||||
case "interface{}":
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{}
|
||||
ctr.Methods[i].ReturnType = "stackitem.Item"
|
||||
ctr.Methods[i].CallFlag = "Item"
|
||||
case "bool":
|
||||
ctr.Methods[i].CallFlag = "Bool"
|
||||
case "*big.Int":
|
||||
ctr.Methods[i].CallFlag = "BigInt"
|
||||
case "string":
|
||||
ctr.Methods[i].CallFlag = "UTF8String"
|
||||
case "util.Uint160":
|
||||
ctr.Methods[i].CallFlag = "Uint160"
|
||||
case "util.Uint256":
|
||||
ctr.Methods[i].CallFlag = "Uint256"
|
||||
case "*keys.PublicKey":
|
||||
ctr.Methods[i].CallFlag = "PublicKey"
|
||||
case "[]byte":
|
||||
ctr.Methods[i].CallFlag = "Bytes"
|
||||
case "[]interface{}":
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{}
|
||||
ctr.Methods[i].ReturnType = "[]stackitem.Item"
|
||||
ctr.Methods[i].CallFlag = "Array"
|
||||
case "*stackitem.Map":
|
||||
ctr.Methods[i].CallFlag = "Map"
|
||||
}
|
||||
}
|
||||
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"] = struct{}{}
|
||||
imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{}
|
||||
ctr.Imports = ctr.Imports[:0]
|
||||
for imp := range imports {
|
||||
ctr.Imports = append(ctr.Imports, imp)
|
||||
}
|
||||
sort.Strings(ctr.Imports)
|
||||
return ctr
|
||||
}
|
Loading…
Reference in a new issue