Merge pull request #2792 from nspcc-dev/rpcwrapper-arrays

RPC wrapper for simple arrays
This commit is contained in:
Roman Khimov 2022-11-15 13:08:25 +07:00 committed by GitHub
commit c67ee54566
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 419 additions and 1 deletions

View file

@ -365,6 +365,44 @@ func TestGenerateRPCBindings(t *testing.T) {
filepath.Join("testdata", "nonepiter", "iter.go"))
}
func TestAssistedRPCBindings(t *testing.T) {
tmpDir := t.TempDir()
app := cli.NewApp()
app.Commands = NewCommands()
var checkBinding = func(source string) {
t.Run(source, func(t *testing.T) {
manifestF := filepath.Join(tmpDir, "manifest.json")
bindingF := filepath.Join(tmpDir, "binding.yml")
nefF := filepath.Join(tmpDir, "out.nef")
require.NoError(t, app.Run([]string{"", "contract", "compile",
"--in", source,
"--config", filepath.Join(source, "config.yml"),
"--manifest", manifestF,
"--bindings", bindingF,
"--out", nefF,
}))
outFile := filepath.Join(tmpDir, "out.go")
require.NoError(t, app.Run([]string{"", "contract", "generate-rpcwrapper",
"--config", bindingF,
"--manifest", manifestF,
"--out", outFile,
"--hash", "0x00112233445566778899aabbccddeeff00112233",
}))
data, err := os.ReadFile(outFile)
require.NoError(t, err)
data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows.
expected, err := os.ReadFile(filepath.Join(source, "rpcbindings.out"))
require.NoError(t, err)
expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows.
require.Equal(t, string(expected), string(data))
})
}
checkBinding(filepath.Join("testdata", "types"))
}
func TestGenerate_Errors(t *testing.T) {
app := cli.NewApp()
app.Commands = []cli.Command{generateWrapperCmd}

View file

@ -0,0 +1,3 @@
name: "Types"
sourceurl: https://github.com/nspcc-dev/neo-go/
safemethods: ["bool", "int", "bytes", "string", "hash160", "hash256", "publicKey", "signature", "bools", "ints", "bytess", "strings", "hash160s", "hash256s", "publicKeys", "signatures"]

View file

@ -0,0 +1,109 @@
// Package types contains RPC wrappers for Types contract.
package types
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/unwrap"
"github.com/nspcc-dev/neo-go/pkg/util"
"math/big"
)
// 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)
}
// 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}
}
// Bool invokes `bool` method of contract.
func (c *ContractReader) Bool(b bool) (bool, error) {
return unwrap.Bool(c.invoker.Call(Hash, "bool", b))
}
// Bools invokes `bools` method of contract.
func (c *ContractReader) Bools(b []bool) ([]bool, error) {
return unwrap.ArrayOfBools(c.invoker.Call(Hash, "bools", b))
}
// Bytes invokes `bytes` method of contract.
func (c *ContractReader) Bytes(b []byte) ([]byte, error) {
return unwrap.Bytes(c.invoker.Call(Hash, "bytes", b))
}
// Bytess invokes `bytess` method of contract.
func (c *ContractReader) Bytess(b [][]byte) ([][]byte, error) {
return unwrap.ArrayOfBytes(c.invoker.Call(Hash, "bytess", b))
}
// Hash160 invokes `hash160` method of contract.
func (c *ContractReader) Hash160(h util.Uint160) (util.Uint160, error) {
return unwrap.Uint160(c.invoker.Call(Hash, "hash160", h))
}
// Hash160s invokes `hash160s` method of contract.
func (c *ContractReader) Hash160s(h []util.Uint160) ([]util.Uint160, error) {
return unwrap.ArrayOfUint160(c.invoker.Call(Hash, "hash160s", h))
}
// Hash256 invokes `hash256` method of contract.
func (c *ContractReader) Hash256(h util.Uint256) (util.Uint256, error) {
return unwrap.Uint256(c.invoker.Call(Hash, "hash256", h))
}
// Hash256s invokes `hash256s` method of contract.
func (c *ContractReader) Hash256s(h []util.Uint256) ([]util.Uint256, error) {
return unwrap.ArrayOfUint256(c.invoker.Call(Hash, "hash256s", h))
}
// Int invokes `int` method of contract.
func (c *ContractReader) Int(i *big.Int) (*big.Int, error) {
return unwrap.BigInt(c.invoker.Call(Hash, "int", i))
}
// Ints invokes `ints` method of contract.
func (c *ContractReader) Ints(i []*big.Int) ([]*big.Int, error) {
return unwrap.ArrayOfBigInts(c.invoker.Call(Hash, "ints", i))
}
// PublicKey invokes `publicKey` method of contract.
func (c *ContractReader) PublicKey(k *keys.PublicKey) (*keys.PublicKey, error) {
return unwrap.PublicKey(c.invoker.Call(Hash, "publicKey", k))
}
// PublicKeys invokes `publicKeys` method of contract.
func (c *ContractReader) PublicKeys(k keys.PublicKeys) (keys.PublicKeys, error) {
return unwrap.ArrayOfPublicKeys(c.invoker.Call(Hash, "publicKeys", k))
}
// Signature invokes `signature` method of contract.
func (c *ContractReader) Signature(s []byte) ([]byte, error) {
return unwrap.Bytes(c.invoker.Call(Hash, "signature", s))
}
// Signatures invokes `signatures` method of contract.
func (c *ContractReader) Signatures(s [][]byte) ([][]byte, error) {
return unwrap.ArrayOfBytes(c.invoker.Call(Hash, "signatures", s))
}
// String invokes `string` method of contract.
func (c *ContractReader) String(s string) (string, error) {
return unwrap.UTF8String(c.invoker.Call(Hash, "string", s))
}
// Strings invokes `strings` method of contract.
func (c *ContractReader) Strings(s []string) ([]string, error) {
return unwrap.ArrayOfUTF8Strings(c.invoker.Call(Hash, "strings", s))
}

View file

@ -0,0 +1,69 @@
package types
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
)
func Bool(b bool) bool {
return false
}
func Int(i int) int {
return 0
}
func Bytes(b []byte) []byte {
return nil
}
func String(s string) string {
return ""
}
func Hash160(h interop.Hash160) interop.Hash160 {
return nil
}
func Hash256(h interop.Hash256) interop.Hash256 {
return nil
}
func PublicKey(k interop.PublicKey) interop.PublicKey {
return nil
}
func Signature(s interop.Signature) interop.Signature {
return nil
}
func Bools(b []bool) []bool {
return nil
}
func Ints(i []int) []int {
return nil
}
func Bytess(b [][]byte) [][]byte {
return nil
}
func Strings(s []string) []string {
return nil
}
func Hash160s(h []interop.Hash160) []interop.Hash160 {
return nil
}
func Hash256s(h []interop.Hash256) []interop.Hash256 {
return nil
}
func PublicKeys(k []interop.PublicKey) []interop.PublicKey {
return nil
}
func Signatures(s []interop.Signature) []interop.Signature {
return nil
}

View file

@ -446,6 +446,10 @@ 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.
```
$ ./bin/neo-go contract generate-rpcwrapper --manifest manifest.json --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176
```
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
@ -454,8 +458,13 @@ iterator and an appropriate unwrapper is used with UUID and iterator structure
result. This pair can then be used in Invoker `TraverseIterator` method to
retrieve actual resulting items.
Go contracts can also make use of additional type data from bindings
configuration file generated during compilation. At the moment it allows to
generate proper wrappers for simple array types, but doesn't cover structures:
```
$ ./bin/neo-go contract generate-rpcwrapper --manifest manifest.json --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176
$ ./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-rpcwrapper --manifest manifest.json --config contract.bindings.yml --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176
```
## Smart contract examples

View file

@ -196,6 +196,42 @@ func Array(r *result.Invoke, err error) ([]stackitem.Item, error) {
return arr, nil
}
// ArrayOfBools checks the result for correct state (HALT) and then extracts a
// slice of boolean values from the returned stack item.
func ArrayOfBools(r *result.Invoke, err error) ([]bool, error) {
a, err := Array(r, err)
if err != nil {
return nil, err
}
res := make([]bool, len(a))
for i := range a {
b, err := a[i].TryBool()
if err != nil {
return nil, fmt.Errorf("element %d is not a boolean: %w", i, err)
}
res[i] = b
}
return res, nil
}
// ArrayOfBigInts checks the result for correct state (HALT) and then extracts a
// slice of (big) integer values from the returned stack item.
func ArrayOfBigInts(r *result.Invoke, err error) ([]*big.Int, error) {
a, err := Array(r, err)
if err != nil {
return nil, err
}
res := make([]*big.Int, len(a))
for i := range a {
v, err := a[i].TryInteger()
if err != nil {
return nil, fmt.Errorf("element %d is not an integer: %w", i, err)
}
res[i] = v
}
return res, nil
}
// ArrayOfBytes checks the result for correct state (HALT) and then extracts a
// slice of byte slices from the returned stack item.
func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) {
@ -214,6 +250,27 @@ func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) {
return res, nil
}
// ArrayOfUTB8Strings checks the result for correct state (HALT) and then extracts a
// slice of UTF-8 strings from the returned stack item.
func ArrayOfUTF8Strings(r *result.Invoke, err error) ([]string, error) {
a, err := Array(r, err)
if err != nil {
return nil, err
}
res := make([]string, len(a))
for i := range a {
b, err := a[i].TryBytes()
if err != nil {
return nil, fmt.Errorf("element %d is not a byte string: %w", i, err)
}
if !utf8.Valid(b) {
return nil, fmt.Errorf("element %d is not a UTF-8 string", i)
}
res[i] = string(b)
}
return res, nil
}
// ArrayOfUint160 checks the result for correct state (HALT) and then extracts a
// slice of util.Uint160 from the returned stack item.
func ArrayOfUint160(r *result.Invoke, err error) ([]util.Uint160, error) {
@ -236,6 +293,28 @@ func ArrayOfUint160(r *result.Invoke, err error) ([]util.Uint160, error) {
return res, nil
}
// ArrayOfUint256 checks the result for correct state (HALT) and then extracts a
// slice of util.Uint256 from the returned stack item.
func ArrayOfUint256(r *result.Invoke, err error) ([]util.Uint256, error) {
a, err := Array(r, err)
if err != nil {
return nil, err
}
res := make([]util.Uint256, len(a))
for i := range a {
b, err := a[i].TryBytes()
if err != nil {
return nil, fmt.Errorf("element %d is not a byte string: %w", i, err)
}
u, err := util.Uint256DecodeBytesBE(b)
if err != nil {
return nil, fmt.Errorf("element %d is not a uint256: %w", i, err)
}
res[i] = u
}
return res, nil
}
// ArrayOfPublicKeys checks the result for correct state (HALT) and then
// extracts a slice of public keys from the returned stack item.
func ArrayOfPublicKeys(r *result.Invoke, err error) (keys.PublicKeys, error) {

View file

@ -53,9 +53,24 @@ func TestStdErrors(t *testing.T) {
func(r *result.Invoke, err error) (interface{}, error) {
return Array(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfBools(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfBigInts(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfBytes(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfUTF8Strings(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfUint160(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfUint256(r, err)
},
func(r *result.Invoke, err error) (interface{}, error) {
return ArrayOfPublicKeys(r, err)
},
@ -233,6 +248,32 @@ func TestArray(t *testing.T) {
require.Equal(t, stackitem.Make(42), a[0])
}
func TestArrayOfBools(t *testing.T) {
_, err := ArrayOfBools(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
_, err = ArrayOfBools(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make("reallybigstringthatcantbeanumberandthuscantbeconvertedtobool")})}}, nil)
require.Error(t, err)
a, err := ArrayOfBools(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(true)})}}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(a))
require.Equal(t, true, a[0])
}
func TestArrayOfBigInts(t *testing.T) {
_, err := ArrayOfBigInts(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
_, err = ArrayOfBigInts(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil)
require.Error(t, err)
a, err := ArrayOfBigInts(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(42)})}}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(a))
require.Equal(t, big.NewInt(42), a[0])
}
func TestArrayOfBytes(t *testing.T) {
_, err := ArrayOfBytes(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
@ -246,6 +287,22 @@ func TestArrayOfBytes(t *testing.T) {
require.Equal(t, []byte("some"), a[0])
}
func TestArrayOfUTF8Strings(t *testing.T) {
_, err := ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
_, err = ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil)
require.Error(t, err)
_, err = ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]byte{0, 0xff})})}}, nil)
require.Error(t, err)
a, err := ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make("some")})}}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(a))
require.Equal(t, "some", a[0])
}
func TestArrayOfUint160(t *testing.T) {
_, err := ArrayOfUint160(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
@ -263,6 +320,23 @@ func TestArrayOfUint160(t *testing.T) {
require.Equal(t, u160, uints[0])
}
func TestArrayOfUint256(t *testing.T) {
_, err := ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
_, err = ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil)
require.Error(t, err)
_, err = ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]byte("some"))})}}, nil)
require.Error(t, err)
u256 := util.Uint256{1, 2, 3}
uints, err := ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(u256.BytesBE())})}}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(uints))
require.Equal(t, u256, uints[0])
}
func TestArrayOfPublicKeys(t *testing.T) {
_, err := ArrayOfPublicKeys(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)

View file

@ -296,6 +296,29 @@ func dropStdMethods(meths []manifest.Method, std *standard.Standard) []manifest.
}
func scTypeToGo(name string, typ smartcontract.ParamType, overrides map[string]binding.Override) (string, string) {
over, ok := overrides[name]
if ok {
switch over.TypeName {
case "[]bool":
return "[]bool", ""
case "[]int", "[]uint", "[]int8", "[]uint8", "[]int16",
"[]uint16", "[]int32", "[]uint32", "[]int64", "[]uint64":
return "[]*big.Int", "math/big"
case "[][]byte":
return "[][]byte", ""
case "[]string":
return "[]string", ""
case "[]interop.Hash160":
return "[]util.Uint160", "github.com/nspcc-dev/neo-go/pkg/util"
case "[]interop.Hash256":
return "[]util.Uint256", "github.com/nspcc-dev/neo-go/pkg/util"
case "[]interop.PublicKey":
return "keys.PublicKeys", "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
case "[]interop.Signature":
return "[][]byte", ""
}
}
switch typ {
case smartcontract.AnyType:
return "interface{}", ""
@ -383,6 +406,20 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st
ctr.SafeMethods[i].CallFlag = "Array"
case "*stackitem.Map":
ctr.SafeMethods[i].CallFlag = "Map"
case "[]bool":
ctr.SafeMethods[i].CallFlag = "ArrayOfBools"
case "[]*big.Int":
ctr.SafeMethods[i].CallFlag = "ArrayOfBigInts"
case "[][]byte":
ctr.SafeMethods[i].CallFlag = "ArrayOfBytes"
case "[]string":
ctr.SafeMethods[i].CallFlag = "ArrayOfUTF8Strings"
case "[]util.Uint160":
ctr.SafeMethods[i].CallFlag = "ArrayOfUint160"
case "[]util.Uint256":
ctr.SafeMethods[i].CallFlag = "ArrayOfUint256"
case "keys.PublicKeys":
ctr.SafeMethods[i].CallFlag = "ArrayOfPublicKeys"
}
}