Merge pull request #2621 from nspcc-dev/sc-params
Smartcontract Parameters and Invoker interface
This commit is contained in:
commit
d4292ed532
14 changed files with 623 additions and 328 deletions
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
@ -35,6 +36,11 @@ type Client struct {
|
||||||
opts Options
|
opts Options
|
||||||
requestF func(*neorpc.Request) (*neorpc.Response, error)
|
requestF func(*neorpc.Request) (*neorpc.Response, error)
|
||||||
|
|
||||||
|
// reader is an Invoker that has no signers and uses current state,
|
||||||
|
// it's used to implement various getters. It'll be removed eventually,
|
||||||
|
// but for now it keeps Client's API compatibility.
|
||||||
|
reader *invoker.Invoker
|
||||||
|
|
||||||
cacheLock sync.RWMutex
|
cacheLock sync.RWMutex
|
||||||
// cache stores RPC node related information the client is bound to.
|
// cache stores RPC node related information the client is bound to.
|
||||||
// cache is mostly filled in during Init(), but can also be updated
|
// cache is mostly filled in during Init(), but can also be updated
|
||||||
|
@ -128,6 +134,7 @@ func initClient(ctx context.Context, cl *Client, endpoint string, opts Options)
|
||||||
cl.getNextRequestID = (cl).getRequestID
|
cl.getNextRequestID = (cl).getRequestID
|
||||||
cl.opts = opts
|
cl.opts = opts
|
||||||
cl.requestF = cl.makeHTTPRequest
|
cl.requestF = cl.makeHTTPRequest
|
||||||
|
cl.reader = invoker.New(cl, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,12 +111,22 @@ func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) {
|
||||||
// retrieve iterator values via single `invokescript` JSON-RPC call. It returns
|
// retrieve iterator values via single `invokescript` JSON-RPC call. It returns
|
||||||
// maxIteratorResultItems items at max which is set to
|
// maxIteratorResultItems items at max which is set to
|
||||||
// config.DefaultMaxIteratorResultItems by default.
|
// config.DefaultMaxIteratorResultItems by default.
|
||||||
|
//
|
||||||
|
// Deprecated: please use more convenient and powerful invoker.Invoker interface with
|
||||||
|
// CallAndExpandIterator method. This method will be removed in future versions.
|
||||||
func (c *Client) InvokeAndPackIteratorResults(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer, maxIteratorResultItems ...int) (*result.Invoke, error) {
|
func (c *Client) InvokeAndPackIteratorResults(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer, maxIteratorResultItems ...int) (*result.Invoke, error) {
|
||||||
max := config.DefaultMaxIteratorResultItems
|
max := config.DefaultMaxIteratorResultItems
|
||||||
if len(maxIteratorResultItems) != 0 {
|
if len(maxIteratorResultItems) != 0 {
|
||||||
max = maxIteratorResultItems[0]
|
max = maxIteratorResultItems[0]
|
||||||
}
|
}
|
||||||
bytes, err := smartcontract.CreateCallAndUnwrapIteratorScript(contract, operation, params, max)
|
values, err := smartcontract.ExpandParameterToEmitable(smartcontract.Parameter{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: params,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("expanding params to emitable: %w", err)
|
||||||
|
}
|
||||||
|
bytes, err := smartcontract.CreateCallAndUnwrapIteratorScript(contract, operation, max, values.([]interface{})...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create iterator unwrapper script: %w", err)
|
return nil, fmt.Errorf("failed to create iterator unwrapper script: %w", err)
|
||||||
}
|
}
|
||||||
|
|
159
pkg/rpcclient/invoker/invoker.go
Normal file
159
pkg/rpcclient/invoker/invoker.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package invoker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RPCInvoke is a set of RPC methods needed to execute things at the current
|
||||||
|
// blockchain height.
|
||||||
|
type RPCInvoke interface {
|
||||||
|
InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||||
|
InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCInvokeHistoric is a set of RPC methods needed to execute things at some
|
||||||
|
// fixed point in blockchain's life.
|
||||||
|
type RPCInvokeHistoric interface {
|
||||||
|
InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||||
|
InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||||
|
InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||||
|
InvokeFunctionAtBlock(blockHash util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
InvokeScriptAtBlock(blockHash util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoker allows to test-execute things using RPC client. Its API simplifies
|
||||||
|
// reusing the same signers list for a series of invocations and at the
|
||||||
|
// same time uses regular Go types for call parameters. It doesn't do anything with
|
||||||
|
// the result of invocation, that's left for upper (contract) layer to deal with.
|
||||||
|
// Invoker does not produce any transactions and does not change the state of the
|
||||||
|
// chain.
|
||||||
|
type Invoker struct {
|
||||||
|
client RPCInvoke
|
||||||
|
signers []transaction.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
type historicConverter struct {
|
||||||
|
client RPCInvokeHistoric
|
||||||
|
block *util.Uint256
|
||||||
|
height *uint32
|
||||||
|
root *util.Uint256
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an Invoker to test-execute things at the current blockchain height.
|
||||||
|
func New(client RPCInvoke, signers []transaction.Signer) *Invoker {
|
||||||
|
return &Invoker{client, signers}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistoricAtBlock creates an Invoker to test-execute things at some given block.
|
||||||
|
func NewHistoricAtBlock(block util.Uint256, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker {
|
||||||
|
return New(&historicConverter{
|
||||||
|
client: client,
|
||||||
|
block: &block,
|
||||||
|
}, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistoricAtHeight creates an Invoker to test-execute things at some given height.
|
||||||
|
func NewHistoricAtHeight(height uint32, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker {
|
||||||
|
return New(&historicConverter{
|
||||||
|
client: client,
|
||||||
|
height: &height,
|
||||||
|
}, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistoricWithState creates an Invoker to test-execute things with some given state.
|
||||||
|
func NewHistoricWithState(root util.Uint256, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker {
|
||||||
|
return New(&historicConverter{
|
||||||
|
client: client,
|
||||||
|
root: &root,
|
||||||
|
}, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *historicConverter) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
if h.block != nil {
|
||||||
|
return h.client.InvokeScriptAtBlock(*h.block, script, signers)
|
||||||
|
}
|
||||||
|
if h.height != nil {
|
||||||
|
return h.client.InvokeScriptAtHeight(*h.height, script, signers)
|
||||||
|
}
|
||||||
|
if h.root != nil {
|
||||||
|
return h.client.InvokeScriptWithState(*h.root, script, signers)
|
||||||
|
}
|
||||||
|
panic("uninitialized historicConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *historicConverter) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
if h.block != nil {
|
||||||
|
return h.client.InvokeFunctionAtBlock(*h.block, contract, operation, params, signers)
|
||||||
|
}
|
||||||
|
if h.height != nil {
|
||||||
|
return h.client.InvokeFunctionAtHeight(*h.height, contract, operation, params, signers)
|
||||||
|
}
|
||||||
|
if h.root != nil {
|
||||||
|
return h.client.InvokeFunctionWithState(*h.root, contract, operation, params, signers)
|
||||||
|
}
|
||||||
|
panic("uninitialized historicConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *historicConverter) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
if h.block != nil {
|
||||||
|
return h.client.InvokeContractVerifyAtBlock(*h.block, contract, params, signers, witnesses...)
|
||||||
|
}
|
||||||
|
if h.height != nil {
|
||||||
|
return h.client.InvokeContractVerifyAtHeight(*h.height, contract, params, signers, witnesses...)
|
||||||
|
}
|
||||||
|
if h.root != nil {
|
||||||
|
return h.client.InvokeContractVerifyWithState(*h.root, contract, params, signers, witnesses...)
|
||||||
|
}
|
||||||
|
panic("uninitialized historicConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call invokes a method of the contract with the given parameters (and
|
||||||
|
// Invoker-specific list of signers) and returns the result as is.
|
||||||
|
func (v *Invoker) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||||
|
ps, err := smartcontract.NewParametersFromValues(params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v.client.InvokeFunction(contract, operation, ps, v.signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallAndExpandIterator creates a script containing a call of the specified method
|
||||||
|
// of a contract with given parameters (similar to how Call operates). But then this
|
||||||
|
// script contains additional code that expects that the result of the first call is
|
||||||
|
// an iterator. This iterator is traversed extracting values from it and adding them
|
||||||
|
// into an array until maxItems is reached or iterator has no more elements. The
|
||||||
|
// result of the whole script is an array containing up to maxResultItems elements
|
||||||
|
// from the iterator returned from the contract's method call. This script is executed
|
||||||
|
// using regular JSON-API (according to the way Iterator is set up).
|
||||||
|
func (v *Invoker) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) {
|
||||||
|
bytes, err := smartcontract.CreateCallAndUnwrapIteratorScript(contract, method, maxItems, params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("iterator unwrapper script: %w", err)
|
||||||
|
}
|
||||||
|
return v.Run(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify invokes contract's verify method in the verification context with
|
||||||
|
// Invoker-specific signers and given witnesses and parameters.
|
||||||
|
func (v *Invoker) Verify(contract util.Uint160, witnesses []transaction.Witness, params ...interface{}) (*result.Invoke, error) {
|
||||||
|
ps, err := smartcontract.NewParametersFromValues(params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v.client.InvokeContractVerify(contract, ps, v.signers, witnesses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes given bytecode with Invoker-specific list of signers.
|
||||||
|
func (v *Invoker) Run(script []byte) (*result.Invoke, error) {
|
||||||
|
return v.client.InvokeScript(script, v.signers)
|
||||||
|
}
|
115
pkg/rpcclient/invoker/invoker_test.go
Normal file
115
pkg/rpcclient/invoker/invoker_test.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package invoker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rpcInv struct {
|
||||||
|
resInv *result.Invoke
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcInv) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeFunctionAtBlock(blockHash util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeScriptAtBlock(blockHash util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
func (r *rpcInv) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
return r.resInv, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvoker(t *testing.T) {
|
||||||
|
resExp := &result.Invoke{State: "HALT"}
|
||||||
|
ri := &rpcInv{resExp, nil}
|
||||||
|
|
||||||
|
testInv := func(t *testing.T, inv *Invoker) {
|
||||||
|
res, err := inv.Call(util.Uint160{}, "method")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
res, err = inv.Verify(util.Uint160{}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
res, err = inv.Run([]byte{1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
res, err = inv.Call(util.Uint160{}, "method")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
res, err = inv.Verify(util.Uint160{}, nil, "param")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
res, err = inv.Call(util.Uint160{}, "method", 42)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
_, err = inv.Verify(util.Uint160{}, nil, make(map[int]int))
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = inv.Call(util.Uint160{}, "method", make(map[int]int))
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
res, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, 42)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, resExp, res)
|
||||||
|
|
||||||
|
_, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, make(map[int]int))
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
t.Run("standard", func(t *testing.T) {
|
||||||
|
testInv(t, New(ri, nil))
|
||||||
|
})
|
||||||
|
t.Run("historic, block", func(t *testing.T) {
|
||||||
|
testInv(t, NewHistoricAtBlock(util.Uint256{}, ri, nil))
|
||||||
|
})
|
||||||
|
t.Run("historic, height", func(t *testing.T) {
|
||||||
|
testInv(t, NewHistoricAtHeight(100500, ri, nil))
|
||||||
|
})
|
||||||
|
t.Run("historic, state", func(t *testing.T) {
|
||||||
|
testInv(t, NewHistoricWithState(util.Uint256{}, ri, nil))
|
||||||
|
})
|
||||||
|
t.Run("broken historic", func(t *testing.T) {
|
||||||
|
inv := New(&historicConverter{client: ri}, nil) // It's not possible to do this from outside.
|
||||||
|
require.Panics(t, func() { _, _ = inv.Call(util.Uint160{}, "method") })
|
||||||
|
require.Panics(t, func() { _, _ = inv.Verify(util.Uint160{}, nil, "param") })
|
||||||
|
require.Panics(t, func() { _, _ = inv.Run([]byte{1}) })
|
||||||
|
})
|
||||||
|
}
|
|
@ -5,15 +5,14 @@ package rpcclient
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"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/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"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/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,16 +54,7 @@ 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.InvokeFunction(rmHash, "getDesignatedByRole", []smartcontract.Parameter{
|
result, err := c.reader.Call(rmHash, "getDesignatedByRole", int64(role), index)
|
||||||
{
|
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: big.NewInt(int64(role)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: big.NewInt(int64(index)),
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -80,16 +70,7 @@ 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.InvokeFunction(nnsHash, "resolve", []smartcontract.Parameter{
|
result, err := c.reader.Call(nnsHash, "resolve", name, int64(typ))
|
||||||
{
|
|
||||||
Type: smartcontract.StringType,
|
|
||||||
Value: name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: big.NewInt(int64(typ)),
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -102,12 +83,7 @@ func (c *Client) NNSResolve(nnsHash util.Uint160, name string, typ nns.RecordTyp
|
||||||
|
|
||||||
// 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.InvokeFunction(nnsHash, "isAvailable", []smartcontract.Parameter{
|
result, err := c.reader.Call(nnsHash, "isAvailable", name)
|
||||||
{
|
|
||||||
Type: smartcontract.StringType,
|
|
||||||
Value: name,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -124,12 +100,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.InvokeFunction(nnsHash, "getAllRecords", []smartcontract.Parameter{
|
res, err := c.reader.Call(nnsHash, "getAllRecords", name)
|
||||||
{
|
|
||||||
Type: smartcontract.StringType,
|
|
||||||
Value: name,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
return uuid.UUID{}, result.Iterator{}, err
|
||||||
}
|
}
|
||||||
|
@ -147,12 +118,7 @@ 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.InvokeAndPackIteratorResults(nnsHash, "getAllRecords", []smartcontract.Parameter{
|
result, err := c.reader.CallAndExpandIterator(nnsHash, "getAllRecords", config.DefaultMaxIteratorResultItems, name)
|
||||||
{
|
|
||||||
Type: smartcontract.StringType,
|
|
||||||
Value: name,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,13 @@ package rpcclient
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"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/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.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
|
result, err := c.reader.Call(tokenHash, "decimals")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -24,7 +23,7 @@ func (c *Client) nepDecimals(tokenHash util.Uint160) (int64, error) {
|
||||||
|
|
||||||
// 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.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
|
result, err := c.reader.Call(tokenHash, "symbol")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -38,7 +37,7 @@ func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) {
|
||||||
|
|
||||||
// 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.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
|
result, err := c.reader.Call(tokenHash, "totalSupply")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -52,17 +51,11 @@ func (c *Client) nepTotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||||
|
|
||||||
// nepBalanceOf invokes `balanceOf` NEP* method on the specified contract.
|
// nepBalanceOf invokes `balanceOf` NEP* method on the specified contract.
|
||||||
func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID []byte) (int64, error) {
|
func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID []byte) (int64, error) {
|
||||||
params := []smartcontract.Parameter{{
|
params := []interface{}{acc}
|
||||||
Type: smartcontract.Hash160Type,
|
|
||||||
Value: acc,
|
|
||||||
}}
|
|
||||||
if tokenID != nil {
|
if tokenID != nil {
|
||||||
params = append(params, smartcontract.Parameter{
|
params = append(params, tokenID)
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: tokenID,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
result, err := c.InvokeFunction(tokenHash, "balanceOf", params, nil)
|
result, err := c.reader.Call(tokenHash, "balanceOf", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"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/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"
|
||||||
|
@ -84,12 +85,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.InvokeFunction(tokenHash, "tokensOf", []smartcontract.Parameter{
|
res, err := c.reader.Call(tokenHash, "tokensOf", owner)
|
||||||
{
|
|
||||||
Type: smartcontract.Hash160Type,
|
|
||||||
Value: owner,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
return uuid.UUID{}, result.Iterator{}, err
|
||||||
}
|
}
|
||||||
|
@ -106,12 +102,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.InvokeAndPackIteratorResults(tokenHash, "tokensOf", []smartcontract.Parameter{
|
result, err := c.reader.CallAndExpandIterator(tokenHash, "tokensOf", config.DefaultMaxIteratorResultItems, owner)
|
||||||
{
|
|
||||||
Type: smartcontract.Hash160Type,
|
|
||||||
Value: owner,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -136,12 +127,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.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
|
result, err := c.reader.Call(tokenHash, "ownerOf", tokenID)
|
||||||
{
|
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: tokenID,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.Uint160{}, err
|
return util.Uint160{}, err
|
||||||
}
|
}
|
||||||
|
@ -186,12 +172,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.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
|
res, err := c.reader.Call(tokenHash, "ownerOf", tokenID)
|
||||||
{
|
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: tokenID,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
sessID := res.Session
|
sessID := res.Session
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sessID, result.Iterator{}, err
|
return sessID, result.Iterator{}, err
|
||||||
|
@ -209,12 +190,7 @@ 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.InvokeAndPackIteratorResults(tokenHash, "ownerOf", []smartcontract.Parameter{
|
result, err := c.reader.CallAndExpandIterator(tokenHash, "ownerOf", config.DefaultMaxIteratorResultItems, tokenID)
|
||||||
{
|
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: tokenID,
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -241,10 +217,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.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{
|
result, err := c.reader.Call(tokenHash, "properties", tokenID)
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: tokenID,
|
|
||||||
}}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -262,7 +235,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.InvokeFunction(tokenHash, "tokens", []smartcontract.Parameter{}, nil)
|
res, err := c.reader.Call(tokenHash, "tokens")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uuid.UUID{}, result.Iterator{}, err
|
return uuid.UUID{}, result.Iterator{}, err
|
||||||
}
|
}
|
||||||
|
@ -279,7 +252,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.InvokeAndPackIteratorResults(tokenHash, "tokens", []smartcontract.Parameter{}, nil)
|
result, err := c.reader.CallAndExpandIterator(tokenHash, "tokens", config.DefaultMaxIteratorResultItems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ 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/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +41,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.InvokeFunction(hash, operation, []smartcontract.Parameter{}, nil)
|
result, err := c.reader.Call(hash, operation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -59,10 +58,7 @@ 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.InvokeFunction(policyHash, "isBlocked", []smartcontract.Parameter{{
|
result, err := c.reader.Call(policyHash, "isBlocked", hash)
|
||||||
Type: smartcontract.Hash160Type,
|
|
||||||
Value: hash,
|
|
||||||
}}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1047,7 +1047,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
||||||
var ef int64
|
var ef int64
|
||||||
for i, cosigner := range tx.Signers {
|
for i, cosigner := range tx.Signers {
|
||||||
if accs[i].Contract.Deployed {
|
if accs[i].Contract.Deployed {
|
||||||
res, err := c.InvokeContractVerify(cosigner.Account, smartcontract.Params{}, tx.Signers)
|
res, err := c.InvokeContractVerify(cosigner.Account, []smartcontract.Parameter{}, tx.Signers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to invoke verify: %w", err)
|
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -773,7 +773,7 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("positive, with signer", func(t *testing.T) {
|
t.Run("positive, with signer", func(t *testing.T) {
|
||||||
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
res, err := c.InvokeContractVerify(contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "HALT", res.State)
|
require.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -782,7 +782,7 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
|
|
||||||
t.Run("positive, historic, by height, with signer", func(t *testing.T) {
|
t.Run("positive, historic, by height, with signer", func(t *testing.T) {
|
||||||
h := chain.BlockHeight() - 1
|
h := chain.BlockHeight() - 1
|
||||||
res, err := c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
res, err := c.InvokeContractVerifyAtHeight(h, contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "HALT", res.State)
|
require.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -790,7 +790,7 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("positive, historic, by block, with signer", func(t *testing.T) {
|
t.Run("positive, historic, by block, with signer", func(t *testing.T) {
|
||||||
res, err := c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(int(chain.BlockHeight())-1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
res, err := c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(int(chain.BlockHeight())-1), contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "HALT", res.State)
|
require.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -801,7 +801,7 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
h := chain.BlockHeight() - 1
|
h := chain.BlockHeight() - 1
|
||||||
sr, err := chain.GetStateModule().GetStateRoot(h)
|
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res, err := c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
res, err := c.InvokeContractVerifyWithState(sr.Root, contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "HALT", res.State)
|
require.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -810,13 +810,13 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
|
|
||||||
t.Run("bad, historic, by hash: contract not found", func(t *testing.T) {
|
t.Run("bad, historic, by hash: contract not found", func(t *testing.T) {
|
||||||
var h uint32 = 1
|
var h uint32 = 1
|
||||||
_, err = c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
_, err = c.InvokeContractVerifyAtHeight(h, contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad, historic, by block: contract not found", func(t *testing.T) {
|
t.Run("bad, historic, by block: contract not found", func(t *testing.T) {
|
||||||
_, err = c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
_, err = c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(1), contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
||||||
})
|
})
|
||||||
|
@ -825,13 +825,13 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
var h uint32 = 1
|
var h uint32 = 1
|
||||||
sr, err := chain.GetStateModule().GetStateRoot(h)
|
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
_, err = c.InvokeContractVerifyWithState(sr.Root, contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("positive, with signer and witness", func(t *testing.T) {
|
t.Run("positive, with signer and witness", func(t *testing.T) {
|
||||||
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}})
|
res, err := c.InvokeContractVerify(contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "HALT", res.State)
|
require.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -839,12 +839,12 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error, invalid witness number", func(t *testing.T) {
|
t.Run("error, invalid witness number", func(t *testing.T) {
|
||||||
_, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}}, transaction.Witness{InvocationScript: []byte{byte(opcode.RET)}})
|
_, err := c.InvokeContractVerify(contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}}, transaction.Witness{InvocationScript: []byte{byte(opcode.RET)}})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("false", func(t *testing.T) {
|
t.Run("false", func(t *testing.T) {
|
||||||
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: util.Uint160{}}})
|
res, err := c.InvokeContractVerify(contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: util.Uint160{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "HALT", res.State)
|
require.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -1280,7 +1280,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("default max items constraint", func(t *testing.T) {
|
t.Run("default max items constraint", func(t *testing.T) {
|
||||||
res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil)
|
res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil) //nolint:staticcheck // SA1019: c.InvokeAndPackIteratorResults is deprecated
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vmstate.Halt.String(), res.State)
|
require.Equal(t, vmstate.Halt.String(), res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
@ -1296,7 +1296,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("custom max items constraint", func(t *testing.T) {
|
t.Run("custom max items constraint", func(t *testing.T) {
|
||||||
max := 123
|
max := 123
|
||||||
res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil, max)
|
res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil, max) //nolint:staticcheck // SA1019: c.InvokeAndPackIteratorResults is deprecated
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vmstate.Halt.String(), res.State)
|
require.Equal(t, vmstate.Halt.String(), res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
/*
|
/*
|
||||||
Package smartcontract contains functions to deal with widely used scripts.
|
Package smartcontract contains functions to deal with widely used scripts and NEP-14 Parameters.
|
||||||
Neo is all about various executed code, verifications and executions of
|
Neo is all about various executed code, verifications and executions of
|
||||||
transactions need some NeoVM code and this package simplifies creating it
|
transactions need NeoVM code and this package simplifies creating it
|
||||||
for common tasks like multisignature verification scripts or transaction
|
for common tasks like multisignature verification scripts or transaction
|
||||||
entry scripts that call previously deployed contracts.
|
entry scripts that call previously deployed contracts. Another problem related
|
||||||
|
to scripts and invocations is that RPC invocations use JSONized NEP-14
|
||||||
|
parameters, so this package provides types and methods to deal with that too.
|
||||||
*/
|
*/
|
||||||
package smartcontract
|
package smartcontract
|
||||||
|
|
|
@ -18,20 +18,11 @@ import (
|
||||||
// processed this way (and this number can't exceed VM limits), the result of the
|
// processed this way (and this number can't exceed VM limits), the result of the
|
||||||
// script is an array containing extracted value elements. This script can be useful
|
// script is an array containing extracted value elements. This script can be useful
|
||||||
// for interactions with RPC server that have iterator sessions disabled.
|
// for interactions with RPC server that have iterator sessions disabled.
|
||||||
func CreateCallAndUnwrapIteratorScript(contract util.Uint160, operation string, params []Parameter, maxIteratorResultItems int) ([]byte, error) {
|
func CreateCallAndUnwrapIteratorScript(contract util.Uint160, operation string, maxIteratorResultItems int, params ...interface{}) ([]byte, error) {
|
||||||
script := io.NewBufBinWriter()
|
script := io.NewBufBinWriter()
|
||||||
emit.Int(script.BinWriter, int64(maxIteratorResultItems))
|
emit.Int(script.BinWriter, int64(maxIteratorResultItems))
|
||||||
// Pack arguments for System.Contract.Call.
|
emit.AppCall(script.BinWriter, contract, operation, callflag.All, params...) // The System.Contract.Call itself, it will push Iterator on estack.
|
||||||
arr, err := ExpandParameterToEmitable(Parameter{
|
emit.Opcodes(script.BinWriter, opcode.NEWARRAY0) // Push new empty array to estack. This array will store iterator's elements.
|
||||||
Type: ArrayType,
|
|
||||||
Value: params,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("expanding params to emitable: %w", err)
|
|
||||||
}
|
|
||||||
emit.Array(script.BinWriter, arr.([]interface{})...)
|
|
||||||
emit.AppCallNoArgs(script.BinWriter, contract, operation, callflag.All) // The System.Contract.Call itself, it will push Iterator on estack.
|
|
||||||
emit.Opcodes(script.BinWriter, opcode.NEWARRAY0) // Push new empty array to estack. This array will store iterator's elements.
|
|
||||||
|
|
||||||
// Start the iterator traversal cycle.
|
// Start the iterator traversal cycle.
|
||||||
iteratorTraverseCycleStartOffset := script.Len()
|
iteratorTraverseCycleStartOffset := script.Len()
|
||||||
|
|
|
@ -3,19 +3,16 @@ package smartcontract
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/bits"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"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/encoding/bigint"
|
|
||||||
"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"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
@ -194,123 +191,6 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params is an array of Parameter (TODO: drop it?).
|
|
||||||
type Params []Parameter
|
|
||||||
|
|
||||||
// TryParseArray converts an array of Parameter into an array of more appropriate things.
|
|
||||||
func (p Params) TryParseArray(vals ...interface{}) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
i int
|
|
||||||
par Parameter
|
|
||||||
)
|
|
||||||
if len(p) != len(vals) {
|
|
||||||
return errors.New("receiver array doesn't fit the Params length")
|
|
||||||
}
|
|
||||||
for i, par = range p {
|
|
||||||
if err = par.TryParse(vals[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TryParse converts one Parameter into something more appropriate.
|
|
||||||
func (p Parameter) TryParse(dest interface{}) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
ok bool
|
|
||||||
data []byte
|
|
||||||
)
|
|
||||||
switch p.Type {
|
|
||||||
case ByteArrayType:
|
|
||||||
if data, ok = p.Value.([]byte); !ok {
|
|
||||||
return fmt.Errorf("failed to cast %s to []byte", p.Value)
|
|
||||||
}
|
|
||||||
switch dest := dest.(type) {
|
|
||||||
case *util.Uint160:
|
|
||||||
if *dest, err = util.Uint160DecodeBytesBE(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case *[]byte:
|
|
||||||
*dest = data
|
|
||||||
return nil
|
|
||||||
case *util.Uint256:
|
|
||||||
if *dest, err = util.Uint256DecodeBytesLE(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case **big.Int:
|
|
||||||
*dest = bigint.FromBytes(data)
|
|
||||||
return nil
|
|
||||||
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
|
|
||||||
var size int
|
|
||||||
switch dest.(type) {
|
|
||||||
case *int64, *uint64:
|
|
||||||
size = 64
|
|
||||||
case *int32, *uint32:
|
|
||||||
size = 32
|
|
||||||
case *int16, *uint16:
|
|
||||||
size = 16
|
|
||||||
case *int8, *uint8:
|
|
||||||
size = 8
|
|
||||||
case *int, *uint:
|
|
||||||
size = bits.UintSize
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := bytesToUint64(data, size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch dest := dest.(type) {
|
|
||||||
case *int64:
|
|
||||||
*dest = int64(i)
|
|
||||||
case *int32:
|
|
||||||
*dest = int32(i)
|
|
||||||
case *int16:
|
|
||||||
*dest = int16(i)
|
|
||||||
case *int8:
|
|
||||||
*dest = int8(i)
|
|
||||||
case *int:
|
|
||||||
*dest = int(i)
|
|
||||||
case *uint64:
|
|
||||||
*dest = i
|
|
||||||
case *uint32:
|
|
||||||
*dest = uint32(i)
|
|
||||||
case *uint16:
|
|
||||||
*dest = uint16(i)
|
|
||||||
case *uint8:
|
|
||||||
*dest = uint8(i)
|
|
||||||
case *uint:
|
|
||||||
*dest = uint(i)
|
|
||||||
}
|
|
||||||
case *string:
|
|
||||||
*dest = string(data)
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cannot cast param of type %s to type %s", p.Type, dest)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("cannot define param type")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bytesToUint64(b []byte, size int) (uint64, error) {
|
|
||||||
var length = size / 8
|
|
||||||
if len(b) > length {
|
|
||||||
return 0, fmt.Errorf("input doesn't fit into %d bits", size)
|
|
||||||
}
|
|
||||||
if len(b) < length {
|
|
||||||
data := make([]byte, length)
|
|
||||||
copy(data, b)
|
|
||||||
return binary.LittleEndian.Uint64(data), nil
|
|
||||||
}
|
|
||||||
return binary.LittleEndian.Uint64(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParameterFromString returns a new Parameter initialized from the given
|
// NewParameterFromString returns a new Parameter initialized from the given
|
||||||
// string in neo-go-specific format. It is intended to be used in user-facing
|
// string in neo-go-specific format. It is intended to be used in user-facing
|
||||||
// interfaces and has some heuristics in it to simplify parameter passing. The exact
|
// interfaces and has some heuristics in it to simplify parameter passing. The exact
|
||||||
|
@ -375,6 +255,111 @@ func NewParameterFromString(in string) (*Parameter, error) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewParameterFromValue infers Parameter type from the value given and adjusts
|
||||||
|
// the value if needed. It does not copy the value if it can avoid doing so. All
|
||||||
|
// regular integers, util.*, keys.PublicKey*, string and bool types are supported,
|
||||||
|
// slice of byte slices is accepted and converted as well.
|
||||||
|
func NewParameterFromValue(value interface{}) (Parameter, error) {
|
||||||
|
var result = Parameter{
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
result.Type = ByteArrayType
|
||||||
|
case string:
|
||||||
|
result.Type = StringType
|
||||||
|
case bool:
|
||||||
|
result.Type = BoolType
|
||||||
|
case *big.Int:
|
||||||
|
result.Type = IntegerType
|
||||||
|
case int8:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case byte:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case int16:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case uint16:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case int32:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case uint32:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case int:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(int64(v))
|
||||||
|
case uint:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = new(big.Int).SetUint64(uint64(v))
|
||||||
|
case int64:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = big.NewInt(v)
|
||||||
|
case uint64:
|
||||||
|
result.Type = IntegerType
|
||||||
|
result.Value = new(big.Int).SetUint64(v)
|
||||||
|
case util.Uint160:
|
||||||
|
result.Type = Hash160Type
|
||||||
|
case util.Uint256:
|
||||||
|
result.Type = Hash256Type
|
||||||
|
case keys.PublicKey:
|
||||||
|
return NewParameterFromValue(&v)
|
||||||
|
case *keys.PublicKey:
|
||||||
|
result.Type = PublicKeyType
|
||||||
|
result.Value = v.Bytes()
|
||||||
|
case [][]byte:
|
||||||
|
arr := make([]Parameter, 0, len(v))
|
||||||
|
for i := range v {
|
||||||
|
// We know the type exactly, so error is not possible.
|
||||||
|
elem, _ := NewParameterFromValue(v[i])
|
||||||
|
arr = append(arr, elem)
|
||||||
|
}
|
||||||
|
result.Type = ArrayType
|
||||||
|
result.Value = arr
|
||||||
|
case []*keys.PublicKey:
|
||||||
|
return NewParameterFromValue(keys.PublicKeys(v))
|
||||||
|
case keys.PublicKeys:
|
||||||
|
arr := make([]Parameter, 0, len(v))
|
||||||
|
for i := range v {
|
||||||
|
// We know the type exactly, so error is not possible.
|
||||||
|
elem, _ := NewParameterFromValue(v[i])
|
||||||
|
arr = append(arr, elem)
|
||||||
|
}
|
||||||
|
result.Type = ArrayType
|
||||||
|
result.Value = arr
|
||||||
|
case []interface{}:
|
||||||
|
arr, err := NewParametersFromValues(v...)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
result.Type = ArrayType
|
||||||
|
result.Value = arr
|
||||||
|
default:
|
||||||
|
return result, fmt.Errorf("unsupported parameter %T", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParametersFromValues is similar to NewParameterFromValue except that it
|
||||||
|
// works with multiple values and returns a simple slice of Parameter.
|
||||||
|
func NewParametersFromValues(values ...interface{}) ([]Parameter, error) {
|
||||||
|
res := make([]Parameter, 0, len(values))
|
||||||
|
for i := range values {
|
||||||
|
elem, err := NewParameterFromValue(values[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, elem)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExpandParameterToEmitable converts a parameter to a type which can be handled as
|
// ExpandParameterToEmitable converts a parameter to a type which can be handled as
|
||||||
// an array item by emit.Array. It correlates with the way an RPC server handles
|
// an array item by emit.Array. It correlates with the way an RPC server handles
|
||||||
// FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function.
|
// FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function.
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -341,89 +340,6 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tryParseTestCases = []struct {
|
|
||||||
input interface{}
|
|
||||||
expected interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: []byte{
|
|
||||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
|
||||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
|
||||||
},
|
|
||||||
expected: util.Uint160{
|
|
||||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
|
||||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{
|
|
||||||
0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15,
|
|
||||||
0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1,
|
|
||||||
0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69,
|
|
||||||
0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d,
|
|
||||||
},
|
|
||||||
expected: util.Uint256{
|
|
||||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
|
||||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
|
||||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
|
||||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
|
||||||
expected: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
|
|
||||||
expected: int64(50686687331),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
|
|
||||||
expected: big.NewInt(50686687331),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte("this is a test string"),
|
|
||||||
expected: "this is a test string",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParam_TryParse(t *testing.T) {
|
|
||||||
for _, tc := range tryParseTestCases {
|
|
||||||
t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) {
|
|
||||||
input := Parameter{
|
|
||||||
Type: ByteArrayType,
|
|
||||||
Value: tc.input,
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.New(reflect.TypeOf(tc.expected))
|
|
||||||
assert.NoError(t, input.TryParse(val.Interface()))
|
|
||||||
assert.Equal(t, tc.expected, val.Elem().Interface())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("[]Uint160", func(t *testing.T) {
|
|
||||||
exp1 := util.Uint160{1, 2, 3, 4, 5}
|
|
||||||
exp2 := util.Uint160{9, 8, 7, 6, 5}
|
|
||||||
|
|
||||||
params := Params{
|
|
||||||
{
|
|
||||||
Type: ByteArrayType,
|
|
||||||
Value: exp1.BytesBE(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: ByteArrayType,
|
|
||||||
Value: exp2.BytesBE(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var out1, out2 util.Uint160
|
|
||||||
|
|
||||||
assert.NoError(t, params.TryParseArray(&out1, &out2))
|
|
||||||
assert.Equal(t, exp1, out1)
|
|
||||||
assert.Equal(t, exp2, out2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParamType_String(t *testing.T) {
|
func TestParamType_String(t *testing.T) {
|
||||||
types := []ParamType{
|
types := []ParamType{
|
||||||
SignatureType,
|
SignatureType,
|
||||||
|
@ -611,3 +527,185 @@ func TestExpandParameterToEmitable(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParameterFromValue(t *testing.T) {
|
||||||
|
pk1, _ := keys.NewPrivateKey()
|
||||||
|
pk2, _ := keys.NewPrivateKey()
|
||||||
|
items := []struct {
|
||||||
|
value interface{}
|
||||||
|
expType ParamType
|
||||||
|
expVal interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
value: []byte{1, 2, 3},
|
||||||
|
expType: ByteArrayType,
|
||||||
|
expVal: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "hello world",
|
||||||
|
expType: StringType,
|
||||||
|
expVal: "hello world",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
expType: BoolType,
|
||||||
|
expVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
expType: BoolType,
|
||||||
|
expVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: big.NewInt(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: byte(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: int8(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: uint8(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: int16(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: uint16(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: int32(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: uint32(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 100,
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: uint(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: int64(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: uint64(100),
|
||||||
|
expType: IntegerType,
|
||||||
|
expVal: big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: util.Uint160{1, 2, 3},
|
||||||
|
expType: Hash160Type,
|
||||||
|
expVal: util.Uint160{1, 2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: util.Uint256{3, 2, 1},
|
||||||
|
expType: Hash256Type,
|
||||||
|
expVal: util.Uint256{3, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: pk1.PublicKey(),
|
||||||
|
expType: PublicKeyType,
|
||||||
|
expVal: pk1.PublicKey().Bytes(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: *pk2.PublicKey(),
|
||||||
|
expType: PublicKeyType,
|
||||||
|
expVal: pk2.PublicKey().Bytes(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [][]byte{{1, 2, 3}, {3, 2, 1}},
|
||||||
|
expType: ArrayType,
|
||||||
|
expVal: []Parameter{{ByteArrayType, []byte{1, 2, 3}}, {ByteArrayType, []byte{3, 2, 1}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: []*keys.PublicKey{pk1.PublicKey(), pk2.PublicKey()},
|
||||||
|
expType: ArrayType,
|
||||||
|
expVal: []Parameter{{
|
||||||
|
Type: PublicKeyType,
|
||||||
|
Value: pk1.PublicKey().Bytes(),
|
||||||
|
}, {
|
||||||
|
Type: PublicKeyType,
|
||||||
|
Value: pk2.PublicKey().Bytes(),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: keys.PublicKeys{pk1.PublicKey(), pk2.PublicKey()},
|
||||||
|
expType: ArrayType,
|
||||||
|
expVal: []Parameter{{
|
||||||
|
Type: PublicKeyType,
|
||||||
|
Value: pk1.PublicKey().Bytes(),
|
||||||
|
}, {
|
||||||
|
Type: PublicKeyType,
|
||||||
|
Value: pk2.PublicKey().Bytes(),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: []interface{}{-42, "random", []byte{1, 2, 3}},
|
||||||
|
expType: ArrayType,
|
||||||
|
expVal: []Parameter{{
|
||||||
|
Type: IntegerType,
|
||||||
|
Value: big.NewInt(-42),
|
||||||
|
}, {
|
||||||
|
Type: StringType,
|
||||||
|
Value: "random",
|
||||||
|
}, {
|
||||||
|
Type: ByteArrayType,
|
||||||
|
Value: []byte{1, 2, 3},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
t.Run(item.expType.String()+" to stack parameter", func(t *testing.T) {
|
||||||
|
res, err := NewParameterFromValue(item.value)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, item.expType, res.Type)
|
||||||
|
require.Equal(t, item.expVal, res.Value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_, err := NewParameterFromValue(make(map[string]int))
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = NewParameterFromValue([]interface{}{1, 2, make(map[string]int)})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParametersFromValues(t *testing.T) {
|
||||||
|
res, err := NewParametersFromValues(42, "some", []byte{3, 2, 1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []Parameter{{
|
||||||
|
Type: IntegerType,
|
||||||
|
Value: big.NewInt(42),
|
||||||
|
}, {
|
||||||
|
Type: StringType,
|
||||||
|
Value: "some",
|
||||||
|
}, {
|
||||||
|
Type: ByteArrayType,
|
||||||
|
Value: []byte{3, 2, 1},
|
||||||
|
}}, res)
|
||||||
|
_, err = NewParametersFromValues(42, make(map[int]int), []byte{3, 2, 1})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue