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/neorpc"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
@ -35,6 +36,11 @@ type Client struct {
|
|||
opts Options
|
||||
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
|
||||
// cache stores RPC node related information the client is bound to.
|
||||
// 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.opts = opts
|
||||
cl.requestF = cl.makeHTTPRequest
|
||||
cl.reader = invoker.New(cl, 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
|
||||
// maxIteratorResultItems items at max which is set to
|
||||
// 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) {
|
||||
max := config.DefaultMaxIteratorResultItems
|
||||
if len(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 {
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"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/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"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 {
|
||||
return nil, fmt.Errorf("failed to get native RoleManagement hash: %w", err)
|
||||
}
|
||||
result, err := c.InvokeFunction(rmHash, "getDesignatedByRole", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.IntegerType,
|
||||
Value: big.NewInt(int64(role)),
|
||||
},
|
||||
{
|
||||
Type: smartcontract.IntegerType,
|
||||
Value: big.NewInt(int64(index)),
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.Call(rmHash, "getDesignatedByRole", int64(role), index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -80,16 +70,7 @@ func (c *Client) NNSResolve(nnsHash util.Uint160, name string, typ nns.RecordTyp
|
|||
if typ == nns.CNAME {
|
||||
return "", errors.New("can't resolve CNAME record type")
|
||||
}
|
||||
result, err := c.InvokeFunction(nnsHash, "resolve", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: name,
|
||||
},
|
||||
{
|
||||
Type: smartcontract.IntegerType,
|
||||
Value: big.NewInt(int64(typ)),
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.Call(nnsHash, "resolve", name, int64(typ))
|
||||
if err != nil {
|
||||
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.
|
||||
func (c *Client) NNSIsAvailable(nnsHash util.Uint160, name string) (bool, error) {
|
||||
result, err := c.InvokeFunction(nnsHash, "isAvailable", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: name,
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.Call(nnsHash, "isAvailable", name)
|
||||
if err != nil {
|
||||
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 documentation for more details.
|
||||
func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) (uuid.UUID, result.Iterator, error) {
|
||||
res, err := c.InvokeFunction(nnsHash, "getAllRecords", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: name,
|
||||
},
|
||||
}, nil)
|
||||
res, err := c.reader.Call(nnsHash, "getAllRecords", name)
|
||||
if err != nil {
|
||||
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
|
||||
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
||||
func (c *Client) NNSUnpackedGetAllRecords(nnsHash util.Uint160, name string) ([]nns.RecordState, error) {
|
||||
result, err := c.InvokeAndPackIteratorResults(nnsHash, "getAllRecords", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: name,
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.CallAndExpandIterator(nnsHash, "getAllRecords", config.DefaultMaxIteratorResultItems, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,14 +3,13 @@ package rpcclient
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
// nepDecimals invokes `decimals` NEP* method on the specified contract.
|
||||
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 {
|
||||
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.
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
|
@ -38,7 +37,7 @@ func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) {
|
|||
|
||||
// nepTotalSupply invokes `totalSupply` NEP* method on the specified contract.
|
||||
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 {
|
||||
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.
|
||||
func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID []byte) (int64, error) {
|
||||
params := []smartcontract.Parameter{{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: acc,
|
||||
}}
|
||||
params := []interface{}{acc}
|
||||
if tokenID != nil {
|
||||
params = append(params, smartcontract.Parameter{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: tokenID,
|
||||
})
|
||||
params = append(params, tokenID)
|
||||
}
|
||||
result, err := c.InvokeFunction(tokenHash, "balanceOf", params, nil)
|
||||
result, err := c.reader.Call(tokenHash, "balanceOf", params...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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/encoding/address"
|
||||
"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
|
||||
// session. See TraverseIterator and TerminateSession documentation for more details.
|
||||
func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid.UUID, result.Iterator, error) {
|
||||
res, err := c.InvokeFunction(tokenHash, "tokensOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: owner,
|
||||
},
|
||||
}, nil)
|
||||
res, err := c.reader.Call(tokenHash, "tokensOf", owner)
|
||||
if err != nil {
|
||||
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
|
||||
// `invokescript` JSON-RPC call.
|
||||
func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint160) ([][]byte, error) {
|
||||
result, err := c.InvokeAndPackIteratorResults(tokenHash, "tokensOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: owner,
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.CallAndExpandIterator(tokenHash, "tokensOf", config.DefaultMaxIteratorResultItems, owner)
|
||||
if err != nil {
|
||||
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
|
||||
// specified token ID on the specified contract.
|
||||
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID []byte) (util.Uint160, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: tokenID,
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.Call(tokenHash, "ownerOf", tokenID)
|
||||
if err != nil {
|
||||
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
|
||||
// TraverseIterator and TerminateSession documentation for more details.
|
||||
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUID, result.Iterator, error) {
|
||||
res, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: tokenID,
|
||||
},
|
||||
}, nil)
|
||||
res, err := c.reader.Call(tokenHash, "ownerOf", tokenID)
|
||||
sessID := res.Session
|
||||
if err != nil {
|
||||
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
|
||||
// script is created and invoked via `invokescript` JSON-RPC call.
|
||||
func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) ([]util.Uint160, error) {
|
||||
result, err := c.InvokeAndPackIteratorResults(tokenHash, "ownerOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: tokenID,
|
||||
},
|
||||
}, nil)
|
||||
result, err := c.reader.CallAndExpandIterator(tokenHash, "ownerOf", config.DefaultMaxIteratorResultItems, tokenID)
|
||||
if err != nil {
|
||||
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
|
||||
// specified contract.
|
||||
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID []byte) (*stackitem.Map, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: tokenID,
|
||||
}}, nil)
|
||||
result, err := c.reader.Call(tokenHash, "properties", tokenID)
|
||||
if err != nil {
|
||||
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 documentation for more details.
|
||||
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 {
|
||||
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
|
||||
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -42,7 +41,7 @@ func (c *Client) invokeNativePolicyMethod(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 {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -59,10 +58,7 @@ func (c *Client) IsBlocked(hash util.Uint160) (bool, error) {
|
|||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get native Policy hash: %w", err)
|
||||
}
|
||||
result, err := c.InvokeFunction(policyHash, "isBlocked", []smartcontract.Parameter{{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: hash,
|
||||
}}, nil)
|
||||
result, err := c.reader.Call(policyHash, "isBlocked", hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -1047,7 +1047,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
|||
var ef int64
|
||||
for i, cosigner := range tx.Signers {
|
||||
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 {
|
||||
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||
}
|
||||
|
|
|
@ -773,7 +773,7 @@ func TestInvokeVerify(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
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.Equal(t, "HALT", res.State)
|
||||
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) {
|
||||
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.Equal(t, "HALT", res.State)
|
||||
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) {
|
||||
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.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
|
@ -801,7 +801,7 @@ func TestInvokeVerify(t *testing.T) {
|
|||
h := chain.BlockHeight() - 1
|
||||
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||
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.Equal(t, "HALT", res.State)
|
||||
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) {
|
||||
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.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) {
|
||||
_, 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.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
|
||||
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||
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.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) {
|
||||
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.Equal(t, "HALT", res.State)
|
||||
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) {
|
||||
_, 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)
|
||||
})
|
||||
|
||||
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.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
|
@ -1280,7 +1280,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
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.Equal(t, vmstate.Halt.String(), res.State)
|
||||
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) {
|
||||
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.Equal(t, vmstate.Halt.String(), res.State)
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -18,19 +18,10 @@ import (
|
|||
// 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
|
||||
// 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()
|
||||
emit.Int(script.BinWriter, int64(maxIteratorResultItems))
|
||||
// Pack arguments for System.Contract.Call.
|
||||
arr, err := ExpandParameterToEmitable(Parameter{
|
||||
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.AppCall(script.BinWriter, contract, operation, callflag.All, params...) // 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.
|
||||
|
|
|
@ -3,19 +3,16 @@ package smartcontract
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/bits"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"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/vm/stackitem"
|
||||
)
|
||||
|
@ -194,123 +191,6 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
|||
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
|
||||
// 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
|
||||
|
@ -375,6 +255,111 @@ func NewParameterFromString(in string) (*Parameter, error) {
|
|||
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
|
||||
// an array item by emit.Array. It correlates with the way an RPC server handles
|
||||
// FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function.
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"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) {
|
||||
types := []ParamType{
|
||||
SignatureType,
|
||||
|
@ -611,3 +527,185 @@ func TestExpandParameterToEmitable(t *testing.T) {
|
|||
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