Merge pull request #2638 from nspcc-dev/rpc-unwrap
rpcclient: move result processing code into unwrap package
This commit is contained in:
commit
bf094dffbb
8 changed files with 539 additions and 388 deletions
|
@ -1,105 +1,15 @@
|
||||||
package rpcclient
|
package rpcclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"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/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getInvocationError returns an error in case of bad VM state or an empty stack.
|
|
||||||
func getInvocationError(result *result.Invoke) error {
|
|
||||||
if result.State != "HALT" {
|
|
||||||
return fmt.Errorf("invocation failed: %s", result.FaultException)
|
|
||||||
}
|
|
||||||
if len(result.Stack) == 0 {
|
|
||||||
return errors.New("result stack is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// topBoolFromStack returns the top boolean value from the stack.
|
|
||||||
func topBoolFromStack(st []stackitem.Item) (bool, error) {
|
|
||||||
index := len(st) - 1 // top stack element is last in the array
|
|
||||||
result, ok := st[index].Value().(bool)
|
|
||||||
if !ok {
|
|
||||||
return false, fmt.Errorf("invalid stack item type: %s", st[index].Type())
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// topIntFromStack returns the top integer value from the stack.
|
|
||||||
func topIntFromStack(st []stackitem.Item) (int64, error) {
|
|
||||||
index := len(st) - 1 // top stack element is last in the array
|
|
||||||
bi, err := st[index].TryInteger()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return bi.Int64(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// topPublicKeysFromStack returns the top array of public keys from the stack.
|
|
||||||
func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) {
|
|
||||||
index := len(st) - 1 // top stack element is last in the array
|
|
||||||
var (
|
|
||||||
pks keys.PublicKeys
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
items, ok := st[index].Value().([]stackitem.Item)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type())
|
|
||||||
}
|
|
||||||
pks = make(keys.PublicKeys, len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
val, ok := item.Value().([]byte)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
|
|
||||||
}
|
|
||||||
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// top string from stack returns the top string from the stack.
|
|
||||||
func topStringFromStack(st []stackitem.Item) (string, error) {
|
|
||||||
index := len(st) - 1 // top stack element is last in the array
|
|
||||||
bs, err := st[index].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(bs), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// topUint160FromStack returns the top util.Uint160 from the stack.
|
|
||||||
func topUint160FromStack(st []stackitem.Item) (util.Uint160, error) {
|
|
||||||
index := len(st) - 1 // top stack element is last in the array
|
|
||||||
bs, err := st[index].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return util.Uint160{}, err
|
|
||||||
}
|
|
||||||
return util.Uint160DecodeBytesBE(bs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// topMapFromStack returns the top stackitem.Map from the stack.
|
|
||||||
func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) {
|
|
||||||
index := len(st) - 1 // top stack element is last in the array
|
|
||||||
if t := st[index].Type(); t != stackitem.MapT {
|
|
||||||
return nil, fmt.Errorf("invalid return stackitem type: %s", t.String())
|
|
||||||
}
|
|
||||||
return st[index].(*stackitem.Map), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvokeAndPackIteratorResults creates a script containing System.Contract.Call
|
// InvokeAndPackIteratorResults creates a script containing System.Contract.Call
|
||||||
// of the specified contract with the specified arguments. It assumes that the
|
// of the specified contract with the specified arguments. It assumes that the
|
||||||
// specified operation will return iterator. The script traverses the resulting
|
// specified operation will return iterator. The script traverses the resulting
|
||||||
|
@ -132,87 +42,3 @@ func (c *Client) InvokeAndPackIteratorResults(contract util.Uint160, operation s
|
||||||
}
|
}
|
||||||
return c.InvokeScript(bytes, signers)
|
return c.InvokeScript(bytes, signers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// topIterableFromStack returns the list of elements of `resultItemType` type from the top element
|
|
||||||
// of the provided stack. The top element is expected to be an Array, otherwise an error is returned.
|
|
||||||
func topIterableFromStack(st []stackitem.Item, resultItemType interface{}) ([]interface{}, error) {
|
|
||||||
index := len(st) - 1 // top stack element is the last in the array
|
|
||||||
if t := st[index].Type(); t != stackitem.ArrayT {
|
|
||||||
return nil, fmt.Errorf("invalid return stackitem type: %s (Array expected)", t.String())
|
|
||||||
}
|
|
||||||
items, ok := st[index].Value().([]stackitem.Item)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to deserialize iterable from Array stackitem: invalid value type (Array expected)")
|
|
||||||
}
|
|
||||||
result := make([]interface{}, len(items))
|
|
||||||
for i := range items {
|
|
||||||
switch resultItemType.(type) {
|
|
||||||
case []byte:
|
|
||||||
bytes, err := items[i].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to deserialize []byte from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
result[i] = bytes
|
|
||||||
case string:
|
|
||||||
bytes, err := items[i].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to deserialize string from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
result[i] = string(bytes)
|
|
||||||
case util.Uint160:
|
|
||||||
bytes, err := items[i].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to deserialize uint160 from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
result[i], err = util.Uint160DecodeBytesBE(bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode uint160 from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
case nns.RecordState:
|
|
||||||
rs, ok := items[i].Value().([]stackitem.Item)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: not a struct", i)
|
|
||||||
}
|
|
||||||
if len(rs) != 3 {
|
|
||||||
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: wrong number of elements", i)
|
|
||||||
}
|
|
||||||
name, err := rs[0].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
typ, err := rs[1].TryInteger()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
data, err := rs[2].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
|
|
||||||
}
|
|
||||||
u64Typ := typ.Uint64()
|
|
||||||
if !typ.IsUint64() || u64Typ > 255 {
|
|
||||||
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: bad type", i)
|
|
||||||
}
|
|
||||||
result[i] = nns.RecordState{
|
|
||||||
Name: string(name),
|
|
||||||
Type: nns.RecordType(u64Typ),
|
|
||||||
Data: string(data),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported iterable type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// topIteratorFromStack returns the top Iterator from the stack.
|
|
||||||
func topIteratorFromStack(st []stackitem.Item) (result.Iterator, error) {
|
|
||||||
index := len(st) - 1 // top stack element is the last in the array
|
|
||||||
if t := st[index].Type(); t != stackitem.InteropT {
|
|
||||||
return result.Iterator{}, fmt.Errorf("expected InteropInterface on stack, got %s", t)
|
|
||||||
}
|
|
||||||
iter, ok := st[index].Value().(result.Iterator)
|
|
||||||
if !ok {
|
|
||||||
return result.Iterator{}, fmt.Errorf("failed to deserialize iterable from interop stackitem: invalid value type (Iterator expected)")
|
|
||||||
}
|
|
||||||
return iter, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package rpcclient
|
||||||
// Various non-policy things from native contracts.
|
// Various non-policy things from native contracts.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
@ -13,7 +14,9 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"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/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetOraclePrice invokes `getPrice` method on a native Oracle contract.
|
// GetOraclePrice invokes `getPrice` method on a native Oracle contract.
|
||||||
|
@ -54,15 +57,22 @@ func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.Pu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get native RoleManagement hash: %w", err)
|
return nil, fmt.Errorf("failed to get native RoleManagement hash: %w", err)
|
||||||
}
|
}
|
||||||
result, err := c.reader.Call(rmHash, "getDesignatedByRole", int64(role), index)
|
arr, err := unwrap.Array(c.reader.Call(rmHash, "getDesignatedByRole", int64(role), index))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = getInvocationError(result)
|
pks := make(keys.PublicKeys, len(arr))
|
||||||
|
for i, item := range arr {
|
||||||
|
val, err := item.TryBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("`getDesignatedByRole`: %w", err)
|
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
|
||||||
}
|
}
|
||||||
return topPublicKeysFromStack(result.Stack)
|
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NNSResolve invokes `resolve` method on a NameService contract with the specified hash.
|
// NNSResolve invokes `resolve` method on a NameService contract with the specified hash.
|
||||||
|
@ -70,28 +80,12 @@ func (c *Client) NNSResolve(nnsHash util.Uint160, name string, typ nns.RecordTyp
|
||||||
if typ == nns.CNAME {
|
if typ == nns.CNAME {
|
||||||
return "", errors.New("can't resolve CNAME record type")
|
return "", errors.New("can't resolve CNAME record type")
|
||||||
}
|
}
|
||||||
result, err := c.reader.Call(nnsHash, "resolve", name, int64(typ))
|
return unwrap.UTF8String(c.reader.Call(nnsHash, "resolve", name, int64(typ)))
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("`resolve`: %w", err)
|
|
||||||
}
|
|
||||||
return topStringFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NNSIsAvailable invokes `isAvailable` method on a NeoNameService contract with the specified hash.
|
// NNSIsAvailable invokes `isAvailable` method on a NeoNameService contract with the specified hash.
|
||||||
func (c *Client) NNSIsAvailable(nnsHash util.Uint160, name string) (bool, error) {
|
func (c *Client) NNSIsAvailable(nnsHash util.Uint160, name string) (bool, error) {
|
||||||
result, err := c.reader.Call(nnsHash, "isAvailable", name)
|
return unwrap.Bool(c.reader.Call(nnsHash, "isAvailable", name))
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("`isAvailable`: %w", err)
|
|
||||||
}
|
|
||||||
return topBoolFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NNSGetAllRecords returns iterator over records for a given name from NNS service.
|
// NNSGetAllRecords returns iterator over records for a given name from NNS service.
|
||||||
|
@ -100,17 +94,7 @@ func (c *Client) NNSIsAvailable(nnsHash util.Uint160, name string) (bool, error)
|
||||||
// TerminateSession to terminate opened iterator session. See TraverseIterator and
|
// TerminateSession to terminate opened iterator session. See TraverseIterator and
|
||||||
// TerminateSession documentation for more details.
|
// TerminateSession documentation for more details.
|
||||||
func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) (uuid.UUID, result.Iterator, error) {
|
func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) (uuid.UUID, result.Iterator, error) {
|
||||||
res, err := c.reader.Call(nnsHash, "getAllRecords", name)
|
return unwrap.SessionIterator(c.reader.Call(nnsHash, "getAllRecords", name))
|
||||||
if err != nil {
|
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(res)
|
|
||||||
if err != nil {
|
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := topIteratorFromStack(res.Stack)
|
|
||||||
return res.Session, iter, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NNSUnpackedGetAllRecords returns a set of records for a given name from NNS service
|
// NNSUnpackedGetAllRecords returns a set of records for a given name from NNS service
|
||||||
|
@ -118,24 +102,42 @@ func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) (uuid.UUID,
|
||||||
// that no iterator session is used to retrieve values from iterator. Instead, unpacking
|
// that no iterator session is used to retrieve values from iterator. Instead, unpacking
|
||||||
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
||||||
func (c *Client) NNSUnpackedGetAllRecords(nnsHash util.Uint160, name string) ([]nns.RecordState, error) {
|
func (c *Client) NNSUnpackedGetAllRecords(nnsHash util.Uint160, name string) ([]nns.RecordState, error) {
|
||||||
result, err := c.reader.CallAndExpandIterator(nnsHash, "getAllRecords", config.DefaultMaxIteratorResultItems, name)
|
arr, err := unwrap.Array(c.reader.CallAndExpandIterator(nnsHash, "getAllRecords", config.DefaultMaxIteratorResultItems, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = getInvocationError(result)
|
res := make([]nns.RecordState, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
rs, ok := arr[i].Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: not a struct", i)
|
||||||
|
}
|
||||||
|
if len(rs) != 3 {
|
||||||
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: wrong number of elements", i)
|
||||||
|
}
|
||||||
|
name, err := rs[0].TryBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
typ, err := rs[1].TryInteger()
|
||||||
arr, err := topIterableFromStack(result.Stack, nns.RecordState{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
rss := make([]nns.RecordState, len(arr))
|
data, err := rs[2].TryBytes()
|
||||||
for i := range rss {
|
if err != nil {
|
||||||
rss[i] = arr[i].(nns.RecordState)
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
return rss, nil
|
u64Typ := typ.Uint64()
|
||||||
|
if !typ.IsUint64() || u64Typ > 255 {
|
||||||
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: bad type", i)
|
||||||
|
}
|
||||||
|
res[i] = nns.RecordState{
|
||||||
|
Name: string(name),
|
||||||
|
Type: nns.RecordType(u64Typ),
|
||||||
|
Data: string(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNotaryServiceFeePerKey returns a reward per notary request key for the designated
|
// GetNotaryServiceFeePerKey returns a reward per notary request key for the designated
|
||||||
|
|
|
@ -3,50 +3,24 @@ package rpcclient
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nepDecimals invokes `decimals` NEP* method on the specified contract.
|
// nepDecimals invokes `decimals` NEP* method on the specified contract.
|
||||||
func (c *Client) nepDecimals(tokenHash util.Uint160) (int64, error) {
|
func (c *Client) nepDecimals(tokenHash util.Uint160) (int64, error) {
|
||||||
result, err := c.reader.Call(tokenHash, "decimals")
|
return unwrap.Int64(c.reader.Call(tokenHash, "decimals"))
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nepSymbol invokes `symbol` NEP* method on the specified contract.
|
// nepSymbol invokes `symbol` NEP* method on the specified contract.
|
||||||
func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) {
|
func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) {
|
||||||
result, err := c.reader.Call(tokenHash, "symbol")
|
return unwrap.PrintableASCIIString(c.reader.Call(tokenHash, "symbol"))
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return topStringFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nepTotalSupply invokes `totalSupply` NEP* method on the specified contract.
|
// nepTotalSupply invokes `totalSupply` NEP* method on the specified contract.
|
||||||
func (c *Client) nepTotalSupply(tokenHash util.Uint160) (int64, error) {
|
func (c *Client) nepTotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||||
result, err := c.reader.Call(tokenHash, "totalSupply")
|
return unwrap.Int64(c.reader.Call(tokenHash, "totalSupply"))
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nepBalanceOf invokes `balanceOf` NEP* method on the specified contract.
|
// nepBalanceOf invokes `balanceOf` NEP* method on the specified contract.
|
||||||
|
@ -55,16 +29,7 @@ func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID []byte) (int6
|
||||||
if tokenID != nil {
|
if tokenID != nil {
|
||||||
params = append(params, tokenID)
|
params = append(params, tokenID)
|
||||||
}
|
}
|
||||||
result, err := c.reader.Call(tokenHash, "balanceOf", params...)
|
return unwrap.Int64(c.reader.Call(tokenHash, "balanceOf", params...))
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nepTokenInfo returns full NEP* token info.
|
// nepTokenInfo returns full NEP* token info.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"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/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -85,16 +86,7 @@ func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint1
|
||||||
// traverse iterator values or TerminateSession to terminate opened iterator
|
// traverse iterator values or TerminateSession to terminate opened iterator
|
||||||
// session. See TraverseIterator and TerminateSession documentation for more details.
|
// session. See TraverseIterator and TerminateSession documentation for more details.
|
||||||
func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid.UUID, result.Iterator, error) {
|
func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid.UUID, result.Iterator, error) {
|
||||||
res, err := c.reader.Call(tokenHash, "tokensOf", owner)
|
return unwrap.SessionIterator(c.reader.Call(tokenHash, "tokensOf", owner))
|
||||||
if err != nil {
|
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(res)
|
|
||||||
if err != nil {
|
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
iter, err := topIteratorFromStack(res.Stack)
|
|
||||||
return res.Session, iter, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEP11UnpackedTokensOf returns an array of token IDs for the specified owner of the specified NFT token
|
// NEP11UnpackedTokensOf returns an array of token IDs for the specified owner of the specified NFT token
|
||||||
|
@ -102,24 +94,7 @@ func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid
|
||||||
// is used to retrieve values from iterator. Instead, unpacking VM script is created and invoked via
|
// is used to retrieve values from iterator. Instead, unpacking VM script is created and invoked via
|
||||||
// `invokescript` JSON-RPC call.
|
// `invokescript` JSON-RPC call.
|
||||||
func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint160) ([][]byte, error) {
|
func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint160) ([][]byte, error) {
|
||||||
result, err := c.reader.CallAndExpandIterator(tokenHash, "tokensOf", config.DefaultMaxIteratorResultItems, owner)
|
return unwrap.ArrayOfBytes(c.reader.CallAndExpandIterator(tokenHash, "tokensOf", config.DefaultMaxIteratorResultItems, owner))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
arr, err := topIterableFromStack(result.Stack, []byte{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
|
||||||
}
|
|
||||||
ids := make([][]byte, len(arr))
|
|
||||||
for i := range ids {
|
|
||||||
ids[i] = arr[i].([]byte)
|
|
||||||
}
|
|
||||||
return ids, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-divisible NFT methods section start.
|
// Non-divisible NFT methods section start.
|
||||||
|
@ -127,16 +102,7 @@ func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint16
|
||||||
// NEP11NDOwnerOf invokes `ownerOf` non-divisible NEP-11 method with the
|
// NEP11NDOwnerOf invokes `ownerOf` non-divisible NEP-11 method with the
|
||||||
// specified token ID on the specified contract.
|
// specified token ID on the specified contract.
|
||||||
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID []byte) (util.Uint160, error) {
|
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID []byte) (util.Uint160, error) {
|
||||||
result, err := c.reader.Call(tokenHash, "ownerOf", tokenID)
|
return unwrap.Uint160(c.reader.Call(tokenHash, "ownerOf", tokenID))
|
||||||
if err != nil {
|
|
||||||
return util.Uint160{}, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return util.Uint160{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return topUint160FromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-divisible NFT methods section end.
|
// Non-divisible NFT methods section end.
|
||||||
|
@ -172,17 +138,7 @@ func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID []byte)
|
||||||
// method to traverse iterator values or TerminateSession to terminate opened iterator session. See
|
// method to traverse iterator values or TerminateSession to terminate opened iterator session. See
|
||||||
// TraverseIterator and TerminateSession documentation for more details.
|
// TraverseIterator and TerminateSession documentation for more details.
|
||||||
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUID, result.Iterator, error) {
|
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUID, result.Iterator, error) {
|
||||||
res, err := c.reader.Call(tokenHash, "ownerOf", tokenID)
|
return unwrap.SessionIterator(c.reader.Call(tokenHash, "ownerOf", tokenID))
|
||||||
sessID := res.Session
|
|
||||||
if err != nil {
|
|
||||||
return sessID, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(res)
|
|
||||||
if err != nil {
|
|
||||||
return sessID, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
arr, err := topIteratorFromStack(res.Stack)
|
|
||||||
return sessID, arr, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEP11DUnpackedOwnerOf returns list of the specified NEP-11 divisible token owners
|
// NEP11DUnpackedOwnerOf returns list of the specified NEP-11 divisible token owners
|
||||||
|
@ -190,22 +146,16 @@ func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUI
|
||||||
// iterator session is used to retrieve values from iterator. Instead, unpacking VM
|
// iterator session is used to retrieve values from iterator. Instead, unpacking VM
|
||||||
// script is created and invoked via `invokescript` JSON-RPC call.
|
// script is created and invoked via `invokescript` JSON-RPC call.
|
||||||
func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) ([]util.Uint160, error) {
|
func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) ([]util.Uint160, error) {
|
||||||
result, err := c.reader.CallAndExpandIterator(tokenHash, "ownerOf", config.DefaultMaxIteratorResultItems, tokenID)
|
arr, err := unwrap.ArrayOfBytes(c.reader.CallAndExpandIterator(tokenHash, "ownerOf", config.DefaultMaxIteratorResultItems, tokenID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
arr, err := topIterableFromStack(result.Stack, util.Uint160{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
|
||||||
}
|
|
||||||
owners := make([]util.Uint160, len(arr))
|
owners := make([]util.Uint160, len(arr))
|
||||||
for i := range owners {
|
for i := range arr {
|
||||||
owners[i] = arr[i].(util.Uint160)
|
owners[i], err = util.Uint160DecodeBytesBE(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("not a Uint160 at %d: %w", i, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return owners, nil
|
return owners, nil
|
||||||
}
|
}
|
||||||
|
@ -217,16 +167,7 @@ func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) (
|
||||||
// NEP11Properties invokes `properties` optional NEP-11 method on the
|
// NEP11Properties invokes `properties` optional NEP-11 method on the
|
||||||
// specified contract.
|
// specified contract.
|
||||||
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID []byte) (*stackitem.Map, error) {
|
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID []byte) (*stackitem.Map, error) {
|
||||||
result, err := c.reader.Call(tokenHash, "properties", tokenID)
|
return unwrap.Map(c.reader.Call(tokenHash, "properties", tokenID))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return topMapFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEP11Tokens returns iterator over the tokens minted by the contract. First return
|
// NEP11Tokens returns iterator over the tokens minted by the contract. First return
|
||||||
|
@ -235,16 +176,7 @@ func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID []byte) (*stack
|
||||||
// TerminateSession to terminate opened iterator session. See TraverseIterator and
|
// TerminateSession to terminate opened iterator session. See TraverseIterator and
|
||||||
// TerminateSession documentation for more details.
|
// TerminateSession documentation for more details.
|
||||||
func (c *Client) NEP11Tokens(tokenHash util.Uint160) (uuid.UUID, result.Iterator, error) {
|
func (c *Client) NEP11Tokens(tokenHash util.Uint160) (uuid.UUID, result.Iterator, error) {
|
||||||
res, err := c.reader.Call(tokenHash, "tokens")
|
return unwrap.SessionIterator(c.reader.Call(tokenHash, "tokens"))
|
||||||
if err != nil {
|
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(res)
|
|
||||||
if err != nil {
|
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
|
||||||
}
|
|
||||||
iter, err := topIteratorFromStack(res.Stack)
|
|
||||||
return res.Session, iter, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEP11UnpackedTokens returns list of the tokens minted by the contract
|
// NEP11UnpackedTokens returns list of the tokens minted by the contract
|
||||||
|
@ -252,24 +184,7 @@ func (c *Client) NEP11Tokens(tokenHash util.Uint160) (uuid.UUID, result.Iterator
|
||||||
// iterator session is used to retrieve values from iterator. Instead, unpacking
|
// iterator session is used to retrieve values from iterator. Instead, unpacking
|
||||||
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
||||||
func (c *Client) NEP11UnpackedTokens(tokenHash util.Uint160) ([][]byte, error) {
|
func (c *Client) NEP11UnpackedTokens(tokenHash util.Uint160) ([][]byte, error) {
|
||||||
result, err := c.reader.CallAndExpandIterator(tokenHash, "tokens", config.DefaultMaxIteratorResultItems)
|
return unwrap.ArrayOfBytes(c.reader.CallAndExpandIterator(tokenHash, "tokens", config.DefaultMaxIteratorResultItems))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
arr, err := topIterableFromStack(result.Stack, []byte{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
|
||||||
}
|
|
||||||
tokens := make([][]byte, len(arr))
|
|
||||||
for i := range tokens {
|
|
||||||
tokens[i] = arr[i].([]byte)
|
|
||||||
}
|
|
||||||
return tokens, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional NFT methods section end.
|
// Optional NFT methods section end.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,15 +42,7 @@ func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) invokeNativeGetMethod(hash util.Uint160, operation string) (int64, error) {
|
func (c *Client) invokeNativeGetMethod(hash util.Uint160, operation string) (int64, error) {
|
||||||
result, err := c.reader.Call(hash, operation)
|
return unwrap.Int64(c.reader.Call(hash, operation))
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to invoke %s method of native contract %s: %w", operation, hash.StringLE(), err)
|
|
||||||
}
|
|
||||||
return topIntFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBlocked invokes `isBlocked` method on native Policy contract.
|
// IsBlocked invokes `isBlocked` method on native Policy contract.
|
||||||
|
@ -58,13 +51,5 @@ func (c *Client) IsBlocked(hash util.Uint160) (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get native Policy hash: %w", err)
|
return false, fmt.Errorf("failed to get native Policy hash: %w", err)
|
||||||
}
|
}
|
||||||
result, err := c.reader.Call(policyHash, "isBlocked", hash)
|
return unwrap.Bool(c.reader.Call(policyHash, "isBlocked", hash))
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
err = getInvocationError(result)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to check if account is blocked: %w", err)
|
|
||||||
}
|
|
||||||
return topBoolFromStack(result.Stack)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -1064,17 +1065,11 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to invoke verify: %w", err)
|
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||||
}
|
}
|
||||||
if res.State != "HALT" {
|
r, err := unwrap.Bool(res, err)
|
||||||
return fmt.Errorf("invalid VM state %s due to an error: %s", res.State, res.FaultException)
|
|
||||||
}
|
|
||||||
if l := len(res.Stack); l != 1 {
|
|
||||||
return fmt.Errorf("result stack length should be equal to 1, got %d", l)
|
|
||||||
}
|
|
||||||
r, err := topIntFromStack(res.Stack)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("signer #%d: failed to get `verify` result from stack: %w", i, err)
|
return fmt.Errorf("signer #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
if r == 0 {
|
if !r {
|
||||||
return fmt.Errorf("signer #%d: `verify` returned `false`", i)
|
return fmt.Errorf("signer #%d: `verify` returned `false`", i)
|
||||||
}
|
}
|
||||||
tx.NetworkFee += res.GasConsumed
|
tx.NetworkFee += res.GasConsumed
|
||||||
|
|
228
pkg/rpcclient/unwrap/unwrap.go
Normal file
228
pkg/rpcclient/unwrap/unwrap.go
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
Package unwrap provides a set of proxy methods to process invocation results.
|
||||||
|
|
||||||
|
Functions implemented there are intended to be used as wrappers for other
|
||||||
|
functions that return (*result.Invoke, error) pair (of which there are many).
|
||||||
|
These functions will check for error, check for VM state, check the number
|
||||||
|
of results, cast them to appropriate type (if everything is OK) and then
|
||||||
|
return a result or error. They're mostly useful for other higher-level
|
||||||
|
contract-specific packages.
|
||||||
|
*/
|
||||||
|
package unwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BigInt expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. A big.Int is extracted from this item and returned.
|
||||||
|
func BigInt(r *result.Invoke, err error) (*big.Int, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return itm.TryInteger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. A bool is extracted from this item and returned.
|
||||||
|
func Bool(r *result.Invoke, err error) (bool, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return itm.TryBool()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. An int64 is extracted from this item and returned.
|
||||||
|
func Int64(r *result.Invoke, err error) (int64, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
i, err := itm.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !i.IsInt64() {
|
||||||
|
return 0, errors.New("int64 overflow")
|
||||||
|
}
|
||||||
|
return i.Int64(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LimitedInt64 is similar to Int64 except it allows to set minimum and maximum
|
||||||
|
// limits to be checked, so if it doesn't return an error the value is more than
|
||||||
|
// min and less than max.
|
||||||
|
func LimitedInt64(r *result.Invoke, err error, min int64, max int64) (int64, error) {
|
||||||
|
i, err := Int64(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if i < min {
|
||||||
|
return 0, errors.New("too small value")
|
||||||
|
}
|
||||||
|
if i > max {
|
||||||
|
return 0, errors.New("too big value")
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. A slice of bytes is extracted from this item and returned.
|
||||||
|
func Bytes(r *result.Invoke, err error) ([]byte, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return itm.TryBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTF8String expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. A string is extracted from this item and checked for UTF-8
|
||||||
|
// correctness, valid strings are then returned.
|
||||||
|
func UTF8String(r *result.Invoke, err error) (string, error) {
|
||||||
|
b, err := Bytes(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintableASCIIString expects correct execution (HALT state) with a single
|
||||||
|
// stack item returned. A string is extracted from this item and checked to
|
||||||
|
// only contain ASCII characters in printable range, valid strings are then
|
||||||
|
// returned.
|
||||||
|
func PrintableASCIIString(r *result.Invoke, err error) (string, error) {
|
||||||
|
s, err := UTF8String(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
if c < 32 || c >= 127 {
|
||||||
|
return "", errors.New("not a printable ASCII string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint160 expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. An util.Uint160 is extracted from this item and returned.
|
||||||
|
func Uint160(r *result.Invoke, err error) (util.Uint160, error) {
|
||||||
|
b, err := Bytes(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
return util.Uint160DecodeBytesBE(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint256 expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. An util.Uint256 is extracted from this item and returned.
|
||||||
|
func Uint256(r *result.Invoke, err error) (util.Uint256, error) {
|
||||||
|
b, err := Bytes(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, err
|
||||||
|
}
|
||||||
|
return util.Uint256DecodeBytesBE(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, result.Iterator{}, err
|
||||||
|
}
|
||||||
|
if t := itm.Type(); t != stackitem.InteropT {
|
||||||
|
return uuid.UUID{}, result.Iterator{}, fmt.Errorf("expected InteropInterface, got %s", t)
|
||||||
|
}
|
||||||
|
iter, ok := itm.Value().(result.Iterator)
|
||||||
|
if !ok {
|
||||||
|
return uuid.UUID{}, result.Iterator{}, errors.New("the item is InteropInterface, but not an Iterator")
|
||||||
|
}
|
||||||
|
return r.Session, iter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array expects correct execution (HALT state) with a single array stack item
|
||||||
|
// returned. This item is returned to the caller. Notice that this function can
|
||||||
|
// be used for structures as well since they're also represented as slices of
|
||||||
|
// stack items (the number of them and their types are structure-specific).
|
||||||
|
func Array(r *result.Invoke, err error) ([]stackitem.Item, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
arr, ok := itm.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
return arr, 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) {
|
||||||
|
a, err := Array(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := make([][]byte, 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)
|
||||||
|
}
|
||||||
|
res[i] = b
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map expects correct execution (HALT state) with a single stack item
|
||||||
|
// returned. A stackitem.Map is extracted from this item and returned.
|
||||||
|
func Map(r *result.Invoke, err error) (*stackitem.Map, error) {
|
||||||
|
itm, err := getSingleItem(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t := itm.Type(); t != stackitem.MapT {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", t.String())
|
||||||
|
}
|
||||||
|
return itm.(*stackitem.Map), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkResOK(r *result.Invoke, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.State != vmstate.Halt.String() {
|
||||||
|
return fmt.Errorf("invocation failed: %s", r.FaultException)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSingleItem(r *result.Invoke, err error) (stackitem.Item, error) {
|
||||||
|
err = checkResOK(r, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(r.Stack) == 0 {
|
||||||
|
return nil, errors.New("result stack is empty")
|
||||||
|
}
|
||||||
|
if len(r.Stack) > 1 {
|
||||||
|
return nil, fmt.Errorf("too many (%d) result items", len(r.Stack))
|
||||||
|
}
|
||||||
|
return r.Stack[0], nil
|
||||||
|
}
|
235
pkg/rpcclient/unwrap/unwrap_test.go
Normal file
235
pkg/rpcclient/unwrap/unwrap_test.go
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
package unwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStdErrors(t *testing.T) {
|
||||||
|
funcs := []func(r *result.Invoke, err error) (interface{}, error){
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return BigInt(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Bool(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Int64(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return LimitedInt64(r, err, 0, 1)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Bytes(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return UTF8String(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return PrintableASCIIString(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Uint160(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Uint256(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
_, _, err = SessionIterator(r, err)
|
||||||
|
return nil, err
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Array(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return ArrayOfBytes(r, err)
|
||||||
|
},
|
||||||
|
func(r *result.Invoke, err error) (interface{}, error) {
|
||||||
|
return Map(r, err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Run("error on input", func(t *testing.T) {
|
||||||
|
for _, f := range funcs {
|
||||||
|
_, err := f(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, errors.New("some"))
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FAULT state", func(t *testing.T) {
|
||||||
|
for _, f := range funcs {
|
||||||
|
_, err := f(&result.Invoke{State: "FAULT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("nothing returned", func(t *testing.T) {
|
||||||
|
for _, f := range funcs {
|
||||||
|
_, err := f(&result.Invoke{State: "HALT"}, errors.New("some"))
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("multiple return values", func(t *testing.T) {
|
||||||
|
for _, f := range funcs {
|
||||||
|
_, err := f(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42), stackitem.Make(42)}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigInt(t *testing.T) {
|
||||||
|
_, err := BigInt(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{})}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
i, err := BigInt(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, big.NewInt(42), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBool(t *testing.T) {
|
||||||
|
_, err := Bool(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("0x03c564ed28ba3d50beb1a52dcb751b929e1d747281566bd510363470be186bc0")}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
b, err := Bool(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(true)}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64(t *testing.T) {
|
||||||
|
_, err := Int64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("0x03c564ed28ba3d50beb1a52dcb751b929e1d747281566bd510363470be186bc0")}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = Int64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(uint64(math.MaxUint64))}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
i, err := Int64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(42), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLimitedInt64(t *testing.T) {
|
||||||
|
_, err := LimitedInt64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("0x03c564ed28ba3d50beb1a52dcb751b929e1d747281566bd510363470be186bc0")}}, nil, math.MinInt64, math.MaxInt64)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = LimitedInt64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(uint64(math.MaxUint64))}}, nil, math.MinInt64, math.MaxInt64)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = LimitedInt64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil, 128, 256)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = LimitedInt64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil, 0, 40)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
i, err := LimitedInt64(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil, 0, 128)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(42), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBytes(t *testing.T) {
|
||||||
|
_, err := Bytes(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{})}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
b, err := Bytes(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]byte{1, 2, 3})}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []byte{1, 2, 3}, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUTF8String(t *testing.T) {
|
||||||
|
_, err := UTF8String(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{})}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = UTF8String(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("\xff")}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
s, err := UTF8String(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("value")}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "value", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintableASCIIString(t *testing.T) {
|
||||||
|
_, err := PrintableASCIIString(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{})}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = PrintableASCIIString(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("\xff")}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = PrintableASCIIString(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("\n\r")}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
s, err := PrintableASCIIString(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make("value")}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "value", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint160(t *testing.T) {
|
||||||
|
_, err := Uint160(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(util.Uint256{1, 2, 3}.BytesBE())}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
u, err := Uint160(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(util.Uint160{1, 2, 3}.BytesBE())}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, util.Uint160{1, 2, 3}, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint256(t *testing.T) {
|
||||||
|
_, err := Uint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(util.Uint160{1, 2, 3}.BytesBE())}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
u, err := Uint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(util.Uint256{1, 2, 3}.BytesBE())}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, util.Uint256{1, 2, 3}, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSessionIterator(t *testing.T) {
|
||||||
|
_, _, err := SessionIterator(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, _, err = SessionIterator(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.NewInterop(42)}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
iid := uuid.New()
|
||||||
|
sid := uuid.New()
|
||||||
|
iter := result.Iterator{ID: &iid}
|
||||||
|
rs, ri, err := SessionIterator(&result.Invoke{Session: sid, State: "HALT", Stack: []stackitem.Item{stackitem.NewInterop(iter)}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, sid, rs)
|
||||||
|
require.Equal(t, iter, ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArray(t *testing.T) {
|
||||||
|
_, err := Array(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
a, err := Array(&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, stackitem.Make(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)
|
||||||
|
|
||||||
|
_, err = ArrayOfBytes(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
a, err := ArrayOfBytes(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]byte("some"))})}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(a))
|
||||||
|
require.Equal(t, []byte("some"), a[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
_, err := Map(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
m, err := Map(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.NewMapWithValue([]stackitem.MapElement{{Key: stackitem.Make(42), Value: stackitem.Make("string")}})}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, m.Len())
|
||||||
|
require.Equal(t, 0, m.Index(stackitem.Make(42)))
|
||||||
|
}
|
Loading…
Reference in a new issue