rpc: pass single Param to CreateFunctionInvocationScript

N3 contracts require method and array of arguments to function correctly, but
CreateFunctionInvocationScript can produce scripts that won't work correctly
if one is to pass anything but a single array parameter to it. This doesn't
make much sense, so we can always expect there to be an array of parameters in
the third positional invokefunction argument (the same way C# node does).
This commit is contained in:
Roman Khimov 2021-11-20 19:25:42 +03:00
parent d01f9da8f3
commit 5b470f14cc
3 changed files with 46 additions and 49 deletions

View file

@ -3,7 +3,6 @@ package request
import (
"errors"
"fmt"
"strconv"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
@ -103,28 +102,19 @@ func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
// CreateFunctionInvocationScript creates a script to invoke given contract with
// given parameters.
func CreateFunctionInvocationScript(contract util.Uint160, method string, params Params) ([]byte, error) {
func CreateFunctionInvocationScript(contract util.Uint160, method string, param *Param) ([]byte, error) {
script := io.NewBufBinWriter()
for i := len(params) - 1; i >= 0; i-- {
if slice, err := params[i].GetArray(); err == nil {
err = ExpandArrayIntoScript(script.BinWriter, slice)
if err != nil {
return nil, err
}
emit.Int(script.BinWriter, int64(len(slice)))
emit.Opcodes(script.BinWriter, opcode.PACK)
} else if s, err := params[i].GetStringStrict(); err == nil {
emit.String(script.BinWriter, s)
} else if n, err := params[i].GetIntStrict(); err == nil {
emit.String(script.BinWriter, strconv.Itoa(n))
} else if b, err := params[i].GetBooleanStrict(); err == nil {
emit.Bool(script.BinWriter, b)
} else {
return nil, fmt.Errorf("failed to convert parmeter %s to script parameter", params[i])
}
}
if len(params) == 0 {
if param == nil {
emit.Opcodes(script.BinWriter, opcode.NEWARRAY0)
} else if slice, err := param.GetArray(); err == nil {
err = ExpandArrayIntoScript(script.BinWriter, slice)
if err != nil {
return nil, err
}
emit.Int(script.BinWriter, int64(len(slice)))
emit.Opcodes(script.BinWriter, opcode.PACK)
} else {
return nil, fmt.Errorf("failed to convert %s to script parameter", param)
}
emit.AppCallNoArgs(script.BinWriter, contract, method, callflag.All)

View file

@ -26,9 +26,6 @@ func TestInvocationScriptCreationGood(t *testing.T) {
}, {
ps: Params{{RawMessage: []byte(`42`)}},
script: "c21f0c0234320c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, {
ps: Params{{RawMessage: []byte(`"m"`)}, {RawMessage: []byte(`true`)}},
script: "11db201f0c016d0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, {
ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[]`)}},
script: "10c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
@ -72,7 +69,11 @@ func TestInvocationScriptCreationGood(t *testing.T) {
for i, ps := range paramScripts {
method, err := ps.ps[0].GetString()
require.NoError(t, err, fmt.Sprintf("testcase #%d", i))
script, err := CreateFunctionInvocationScript(contract, method, ps.ps[1:])
var p *Param
if len(ps.ps) > 1 {
p = &ps.ps[1]
}
script, err := CreateFunctionInvocationScript(contract, method, p)
assert.Nil(t, err)
assert.Equal(t, ps.script, hex.EncodeToString(script), fmt.Sprintf("testcase #%d", i))
}
@ -81,18 +82,19 @@ func TestInvocationScriptCreationGood(t *testing.T) {
func TestInvocationScriptCreationBad(t *testing.T) {
contract := util.Uint160{}
var testParams = []Params{
{{RawMessage: []byte(`[{"type": "ByteArray", "value": "qwerty"}]`)}},
{{RawMessage: []byte(`[{"type": "Signature", "value": "qwerty"}]`)}},
{{RawMessage: []byte(`[{"type": "Hash160", "value": "qwerty"}]`)}},
{{RawMessage: []byte(`[{"type": "Hash256", "value": "qwerty"}]`)}},
{{RawMessage: []byte(`[{"type": "PublicKey", "value": 42}]`)}},
{{RawMessage: []byte(`[{"type": "PublicKey", "value": "qwerty"}]`)}},
{{RawMessage: []byte(`[{"type": "Integer", "value": "123q"}]`)}},
{{RawMessage: []byte(`[{"type": "Unknown"}]`)}},
var testParams = []Param{
{RawMessage: []byte(`true`)},
{RawMessage: []byte(`[{"type": "ByteArray", "value": "qwerty"}]`)},
{RawMessage: []byte(`[{"type": "Signature", "value": "qwerty"}]`)},
{RawMessage: []byte(`[{"type": "Hash160", "value": "qwerty"}]`)},
{RawMessage: []byte(`[{"type": "Hash256", "value": "qwerty"}]`)},
{RawMessage: []byte(`[{"type": "PublicKey", "value": 42}]`)},
{RawMessage: []byte(`[{"type": "PublicKey", "value": "qwerty"}]`)},
{RawMessage: []byte(`[{"type": "Integer", "value": "123q"}]`)},
{RawMessage: []byte(`[{"type": "Unknown"}]`)},
}
for i, ps := range testParams {
_, err := CreateFunctionInvocationScript(contract, "", ps)
_, err := CreateFunctionInvocationScript(contract, "", &ps)
assert.NotNil(t, err, fmt.Sprintf("testcase #%d", i))
}
}

View file

@ -1554,28 +1554,33 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
// invokeFunction implements the `invokeFunction` RPC call.
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
if len(reqParams) < 2 {
return nil, response.ErrInvalidParams
}
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
if responseErr != nil {
return nil, responseErr
}
tx := &transaction.Transaction{}
checkWitnessHashesIndex := len(reqParams)
if checkWitnessHashesIndex > 3 {
signers, _, err := reqParams[3].GetSignersWithWitnesses()
if err != nil {
return nil, response.ErrInvalidParams
}
tx.Signers = signers
checkWitnessHashesIndex--
}
if len(tx.Signers) == 0 {
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
}
method, err := reqParams[1].GetString()
if err != nil {
return nil, response.ErrInvalidParams
}
script, err := request.CreateFunctionInvocationScript(scriptHash, method, reqParams[2:checkWitnessHashesIndex])
var params *request.Param
if len(reqParams) > 2 {
params = &reqParams[2]
}
tx := &transaction.Transaction{}
if len(reqParams) > 3 {
signers, _, err := reqParams[3].GetSignersWithWitnesses()
if err != nil {
return nil, response.ErrInvalidParams
}
tx.Signers = signers
}
if len(tx.Signers) == 0 {
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
}
script, err := request.CreateFunctionInvocationScript(scriptHash, method, params)
if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err)
}