Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC wrapper for simple arrays #2792

Merged
merged 2 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cli/smartcontract/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
3 changes: 3 additions & 0 deletions cli/smartcontract/testdata/types/config.yml
Original file line number Diff line number Diff line change
@@ -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"]
109 changes: 109 additions & 0 deletions cli/smartcontract/testdata/types/rpcbindings.out
Original file line number Diff line number Diff line change
@@ -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))
}
69 changes: 69 additions & 0 deletions cli/smartcontract/testdata/types/types.go
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 10 additions & 1 deletion docs/compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
79 changes: 79 additions & 0 deletions pkg/rpcclient/unwrap/unwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
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) {
Expand Down
Loading