Merge pull request #2621 from nspcc-dev/sc-params

Smartcontract Parameters and Invoker interface
This commit is contained in:
Roman Khimov 2022-08-02 14:18:34 +03:00 committed by GitHub
commit d4292ed532
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 623 additions and 328 deletions

View file

@ -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
}

View file

@ -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)
}

View 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)
}

View 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}) })
})
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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))

View file

@ -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

View file

@ -18,20 +18,11 @@ 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.Opcodes(script.BinWriter, opcode.NEWARRAY0) // Push new empty array to estack. This array will store iterator's elements.
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.
iteratorTraverseCycleStartOffset := script.Len()

View file

@ -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.

View file

@ -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)
}