Merge pull request #3329 from nspcc-dev/support-rpc-maps
This commit is contained in:
commit
9d7357c9fd
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