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"`
|
||||
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