rpcsrv: support Map parameter in invokefunction
RPC handler
We have smartcontract.ParameterPair structure that can be properly marshalled and passed to RPC server as an element of smartcontract.Map structure. However, RPC server can't unmarshal map values properly without this change. This change is compatible with C# node. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
cc38221d77
commit
132531fabe
4 changed files with 194 additions and 68 deletions
|
@ -34,6 +34,13 @@ type (
|
||||||
Type smartcontract.ParamType `json:"type"`
|
Type smartcontract.ParamType `json:"type"`
|
||||||
Value Param `json:"value"`
|
Value Param `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuncParamKV represents a pair of function argument parameters
|
||||||
|
// a slice of which is stored in FuncParam of [smartcontract.MapType] type.
|
||||||
|
FuncParamKV struct {
|
||||||
|
Key FuncParam `json:"key"`
|
||||||
|
Value FuncParam `json:"value"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -358,6 +365,21 @@ func (p *Param) GetFuncParam() (FuncParam, error) {
|
||||||
return fp, err
|
return fp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFuncParamPair returns a pair of function call parameters.
|
||||||
|
func (p *Param) GetFuncParamPair() (FuncParamKV, error) {
|
||||||
|
if p == nil {
|
||||||
|
return FuncParamKV{}, errMissingParameter
|
||||||
|
}
|
||||||
|
// This one doesn't need to be cached, it's used only once.
|
||||||
|
fpp := FuncParamKV{}
|
||||||
|
err := json.Unmarshal(p.RawMessage, &fpp)
|
||||||
|
if err != nil {
|
||||||
|
return FuncParamKV{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fpp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBytesHex returns a []byte value of the parameter if
|
// GetBytesHex returns a []byte value of the parameter if
|
||||||
// it is a hex-encoded string.
|
// it is a hex-encoded string.
|
||||||
func (p *Param) GetBytesHex() ([]byte, error) {
|
func (p *Param) GetBytesHex() ([]byte, error) {
|
||||||
|
|
|
@ -426,6 +426,48 @@ func TestParamGetFuncParam(t *testing.T) {
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamGetFuncParamPair(t *testing.T) {
|
||||||
|
fp := FuncParam{
|
||||||
|
Type: smartcontract.MapType,
|
||||||
|
Value: Param{RawMessage: []byte(`[{"key": {"type": "String", "value": "key1"}, "value": {"type": "Integer", "value": 123}}, {"key": {"type": "String", "value": "key2"}, "value": {"type": "Integer", "value": 456}}]`)},
|
||||||
|
}
|
||||||
|
p := Param{RawMessage: []byte(`{"type": "Map", "value": [{"key": {"type": "String", "value": "key1"}, "value": {"type": "Integer", "value": 123}}, {"key": {"type": "String", "value": "key2"}, "value": {"type": "Integer", "value": 456}}]}`)}
|
||||||
|
newfp, err := p.GetFuncParam()
|
||||||
|
assert.Equal(t, fp, newfp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
kvs, err := newfp.Value.GetArray()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p1, err := kvs[0].GetFuncParamPair()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fp1Key := FuncParam{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: Param{RawMessage: []byte(`"key1"`)},
|
||||||
|
}
|
||||||
|
fp1Value := FuncParam{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: Param{RawMessage: []byte(`123`)},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fp1Key, p1.Key)
|
||||||
|
assert.Equal(t, fp1Value, p1.Value)
|
||||||
|
|
||||||
|
p2, err := kvs[1].GetFuncParamPair()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fp2Key := FuncParam{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: Param{RawMessage: []byte(`"key2"`)},
|
||||||
|
}
|
||||||
|
fp2Value := FuncParam{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: Param{RawMessage: []byte(`456`)},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fp2Key, p2.Key)
|
||||||
|
assert.Equal(t, fp2Value, p2.Value)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParamGetBytesHex(t *testing.T) {
|
func TestParamGetBytesHex(t *testing.T) {
|
||||||
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||||
inb, _ := hex.DecodeString(in)
|
inb, _ := hex.DecodeString(in)
|
||||||
|
|
|
@ -13,6 +13,90 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExpandFuncParameterIntoScript pushes provided FuncParam parameter
|
||||||
|
// into the given buffer.
|
||||||
|
func ExpandFuncParameterIntoScript(script *io.BinWriter, fp FuncParam) error {
|
||||||
|
switch fp.Type {
|
||||||
|
case smartcontract.ByteArrayType:
|
||||||
|
str, err := fp.Value.GetBytesBase64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.Bytes(script, str)
|
||||||
|
case smartcontract.SignatureType:
|
||||||
|
str, err := fp.Value.GetBytesBase64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.Bytes(script, str)
|
||||||
|
case smartcontract.StringType:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.String(script, str)
|
||||||
|
case smartcontract.Hash160Type:
|
||||||
|
hash, err := fp.Value.GetUint160FromHex()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.Bytes(script, hash.BytesBE())
|
||||||
|
case smartcontract.Hash256Type:
|
||||||
|
hash, err := fp.Value.GetUint256()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.Bytes(script, hash.BytesBE())
|
||||||
|
case smartcontract.PublicKeyType:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key, err := keys.NewPublicKeyFromString(string(str))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.Bytes(script, key.Bytes())
|
||||||
|
case smartcontract.IntegerType:
|
||||||
|
bi, err := fp.Value.GetBigInt()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emit.BigInt(script, bi)
|
||||||
|
case smartcontract.BoolType:
|
||||||
|
val, err := fp.Value.GetBoolean() // not GetBooleanStrict(), because that's the way C# code works
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("not a bool")
|
||||||
|
}
|
||||||
|
emit.Bool(script, val)
|
||||||
|
case smartcontract.ArrayType:
|
||||||
|
val, err := fp.Value.GetArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ExpandArrayIntoScriptAndPack(script, val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case smartcontract.MapType:
|
||||||
|
val, err := fp.Value.GetArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ExpandMapIntoScriptAndPack(script, val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case smartcontract.AnyType:
|
||||||
|
if fp.Value.IsNull() || len(fp.Value.RawMessage) == 0 {
|
||||||
|
emit.Opcodes(script, opcode.PUSHNULL)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("parameter type %v is not supported", fp.Type)
|
||||||
|
}
|
||||||
|
return script.Err
|
||||||
|
}
|
||||||
|
|
||||||
// ExpandArrayIntoScript pushes all FuncParam parameters from the given array
|
// ExpandArrayIntoScript pushes all FuncParam parameters from the given array
|
||||||
// into the given buffer in the reverse order.
|
// into the given buffer in the reverse order.
|
||||||
func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
||||||
|
@ -21,74 +105,9 @@ func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch fp.Type {
|
err = ExpandFuncParameterIntoScript(script, fp)
|
||||||
case smartcontract.ByteArrayType:
|
if err != nil {
|
||||||
str, err := fp.Value.GetBytesBase64()
|
return fmt.Errorf("param %d: %w", j, err)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.Bytes(script, str)
|
|
||||||
case smartcontract.SignatureType:
|
|
||||||
str, err := fp.Value.GetBytesBase64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.Bytes(script, str)
|
|
||||||
case smartcontract.StringType:
|
|
||||||
str, err := fp.Value.GetString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.String(script, str)
|
|
||||||
case smartcontract.Hash160Type:
|
|
||||||
hash, err := fp.Value.GetUint160FromHex()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.Bytes(script, hash.BytesBE())
|
|
||||||
case smartcontract.Hash256Type:
|
|
||||||
hash, err := fp.Value.GetUint256()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.Bytes(script, hash.BytesBE())
|
|
||||||
case smartcontract.PublicKeyType:
|
|
||||||
str, err := fp.Value.GetString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key, err := keys.NewPublicKeyFromString(string(str))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.Bytes(script, key.Bytes())
|
|
||||||
case smartcontract.IntegerType:
|
|
||||||
bi, err := fp.Value.GetBigInt()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emit.BigInt(script, bi)
|
|
||||||
case smartcontract.BoolType:
|
|
||||||
val, err := fp.Value.GetBoolean() // not GetBooleanStrict(), because that's the way C# code works
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("not a bool")
|
|
||||||
}
|
|
||||||
emit.Bool(script, val)
|
|
||||||
case smartcontract.ArrayType:
|
|
||||||
val, err := fp.Value.GetArray()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = ExpandArrayIntoScriptAndPack(script, val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case smartcontract.AnyType:
|
|
||||||
if fp.Value.IsNull() || len(fp.Value.RawMessage) == 0 {
|
|
||||||
emit.Opcodes(script, opcode.PUSHNULL)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("parameter type %v is not supported", fp.Type)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return script.Err
|
return script.Err
|
||||||
|
@ -110,6 +129,32 @@ func ExpandArrayIntoScriptAndPack(script *io.BinWriter, slice []Param) error {
|
||||||
return script.Err
|
return script.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpandMapIntoScriptAndPack expands provided array of key-value items into script
|
||||||
|
// and packs the resulting pairs in the [stackitem.Map].
|
||||||
|
func ExpandMapIntoScriptAndPack(script *io.BinWriter, slice []Param) error {
|
||||||
|
if len(slice) == 0 {
|
||||||
|
emit.Opcodes(script, opcode.NEWMAP)
|
||||||
|
return script.Err
|
||||||
|
}
|
||||||
|
for i := len(slice) - 1; i >= 0; i-- {
|
||||||
|
pair, err := slice[i].GetFuncParamPair()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ExpandFuncParameterIntoScript(script, pair.Value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("map value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
err = ExpandFuncParameterIntoScript(script, pair.Key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("map key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit.Int(script, int64(len(slice)))
|
||||||
|
emit.Opcodes(script, opcode.PACKMAP)
|
||||||
|
return script.Err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateFunctionInvocationScript creates a script to invoke the given contract with
|
// CreateFunctionInvocationScript creates a script to invoke the given contract with
|
||||||
// the given parameters.
|
// the given parameters.
|
||||||
func CreateFunctionInvocationScript(contract util.Uint160, method string, param *Param) ([]byte, error) {
|
func CreateFunctionInvocationScript(contract util.Uint160, method string, param *Param) ([]byte, error) {
|
||||||
|
|
|
@ -124,6 +124,17 @@ func TestExpandArrayIntoScript(t *testing.T) {
|
||||||
Input: []Param{{RawMessage: []byte(`{"type": "Integer", "value": "` + bi.String() + `"}`)}},
|
Input: []Param{{RawMessage: []byte(`{"type": "Integer", "value": "` + bi.String() + `"}`)}},
|
||||||
Expected: append([]byte{byte(opcode.PUSHINT256)}, rawInt...),
|
Expected: append([]byte{byte(opcode.PUSHINT256)}, rawInt...),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Input: []Param{{RawMessage: []byte(`{"type": "Map", "value": [{"key": {"type": "String", "value": "a" }, "value": {"type": "Integer", "value": 1}}, {"key": {"type": "String", "value": "b"}, "value": {"type": "Integer", "value": 2}}]}`)}},
|
||||||
|
Expected: []byte{
|
||||||
|
byte(opcode.PUSH2), // value of element #2
|
||||||
|
byte(opcode.PUSHDATA1), 1, byte('b'), // key of element #2
|
||||||
|
byte(opcode.PUSH1), // value of element #1
|
||||||
|
byte(opcode.PUSHDATA1), 1, byte('a'), // key of element #1
|
||||||
|
byte(opcode.PUSH2), // map len
|
||||||
|
byte(opcode.PACKMAP),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, c := range testCases {
|
for _, c := range testCases {
|
||||||
script := io.NewBufBinWriter()
|
script := io.NewBufBinWriter()
|
||||||
|
@ -142,6 +153,12 @@ func TestExpandArrayIntoScript(t *testing.T) {
|
||||||
{RawMessage: []byte(`{"type": "Integer", "value": "` +
|
{RawMessage: []byte(`{"type": "Integer", "value": "` +
|
||||||
new(big.Int).Lsh(big.NewInt(1), 255).String() + `"}`)},
|
new(big.Int).Lsh(big.NewInt(1), 255).String() + `"}`)},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
{RawMessage: []byte(`{"type": "Map", "value": [{"key": {"type": "InvalidT", "value": "a" }, "value": {"type": "Integer", "value": 1}}]}`)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{RawMessage: []byte(`{"type": "Map", "value": [{"key": {"type": "String", "value": "a" }, "value": {"type": "Integer", "value": "not-an-int"}}]}`)},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, c := range errorCases {
|
for _, c := range errorCases {
|
||||||
script := io.NewBufBinWriter()
|
script := io.NewBufBinWriter()
|
||||||
|
|
Loading…
Reference in a new issue