mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 13:47:19 +00:00
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"`
|
||||
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 (
|
||||
|
@ -358,6 +365,21 @@ func (p *Param) GetFuncParam() (FuncParam, error) {
|
|||
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
|
||||
// it is a hex-encoded string.
|
||||
func (p *Param) GetBytesHex() ([]byte, error) {
|
||||
|
|
|
@ -426,6 +426,48 @@ func TestParamGetFuncParam(t *testing.T) {
|
|||
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) {
|
||||
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||
inb, _ := hex.DecodeString(in)
|
||||
|
|
|
@ -13,6 +13,90 @@ import (
|
|||
"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
|
||||
// into the given buffer in the reverse order.
|
||||
func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
||||
|
@ -21,74 +105,9 @@ func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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.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)
|
||||
err = ExpandFuncParameterIntoScript(script, fp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("param %d: %w", j, err)
|
||||
}
|
||||
}
|
||||
return script.Err
|
||||
|
@ -110,6 +129,32 @@ func ExpandArrayIntoScriptAndPack(script *io.BinWriter, slice []Param) error {
|
|||
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
|
||||
// the given parameters.
|
||||
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() + `"}`)}},
|
||||
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 {
|
||||
script := io.NewBufBinWriter()
|
||||
|
@ -142,6 +153,12 @@ func TestExpandArrayIntoScript(t *testing.T) {
|
|||
{RawMessage: []byte(`{"type": "Integer", "value": "` +
|
||||
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 {
|
||||
script := io.NewBufBinWriter()
|
||||
|
|
Loading…
Reference in a new issue