Merge pull request #1242 from nspcc-dev/fix/json

Implement lossless stackitem to json conversion
This commit is contained in:
Roman Khimov 2020-08-04 12:59:47 +03:00 committed by GitHub
commit fa5ff8dd95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 562 additions and 208 deletions

View file

@ -293,21 +293,21 @@ Example:
"value" : [
{
"value" : "636f6e74726163742063616c6c",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : "7472616e73666572",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : [
{
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : "1000",
@ -351,20 +351,20 @@ Example:
"value" : [
{
"value" : "636f6e74726163742063616c6c",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"type" : "ByteArray",
"type" : "ByteString",
"value" : "7472616e73666572"
},
{
"value" : [
{
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"type" : "ByteArray",
"type" : "ByteString",
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987"
},
{
@ -384,15 +384,15 @@ Example:
"value" : [
{
"value" : "7472616e73666572",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987",
"type" : "ByteArray"
"type" : "ByteString"
},
{
"value" : "1000",

View file

@ -206,7 +206,7 @@ func (c *codegen) scTypeFromExpr(typ ast.Expr) string {
return "Struct"
case *types.Slice:
if isByte(t.Elem()) {
return "ByteArray"
return "ByteString"
}
return "Array"
default:

View file

@ -63,7 +63,7 @@ func unexportedMethod() int { return 1 }
returnTypes := map[string]string{
"MethodInt": "Integer",
"MethodConcat": "String",
"MethodString": "String", "MethodByteArray": "ByteArray",
"MethodString": "String", "MethodByteArray": "ByteString",
"MethodArray": "Array", "MethodStruct": "Struct",
"Main": "Boolean",
"unexportedMethod": "Integer",
@ -272,7 +272,7 @@ func TestDebugInfo_MarshalJSON(t *testing.T) {
{"param1", "Integer"},
{"ok", "Boolean"},
},
ReturnType: "ByteArray",
ReturnType: "ByteString",
Variables: []string{},
SeqPoints: []DebugSeqPoint{
{

View file

@ -591,7 +591,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
Trigger: trigger.System,
VMState: v.State(),
GasConsumed: v.GasConsumed(),
Stack: v.Estack().ToContractParameters(),
Stack: v.Estack().ToArray(),
Events: systemInterop.Notifications,
}
appExecResults = append(appExecResults, aer)
@ -632,7 +632,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
Trigger: trigger.Application,
VMState: v.State(),
GasConsumed: v.GasConsumed(),
Stack: v.Estack().ToContractParameters(),
Stack: v.Estack().ToArray(),
Events: systemInterop.Notifications,
}
appExecResults = append(appExecResults, aer)

View file

@ -11,8 +11,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
@ -103,7 +103,7 @@ func TestPutGetAppExecResult(t *testing.T) {
appExecResult := &state.AppExecResult{
TxHash: hash,
Events: []state.NotificationEvent{},
Stack: []smartcontract.Parameter{},
Stack: []stackitem.Item{},
}
err := dao.PutAppExecResult(appExecResult)
require.NoError(t, err)

View file

@ -131,8 +131,7 @@ func TestNativeContract_Invoke(t *testing.T) {
require.NoError(t, err)
require.Equal(t, vm.HaltState, res.VMState)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, smartcontract.IntegerType, res.Stack[0].Type)
require.EqualValues(t, 42, res.Stack[0].Value)
require.Equal(t, big.NewInt(42), res.Stack[0].Value())
res, err = chain.GetAppExecResult(tx2.Hash())
require.NoError(t, err)

View file

@ -10,10 +10,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
@ -29,20 +29,14 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
t.Run("get, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getMaxTransactionsPerBlock")
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.IntegerType,
Value: 512,
})
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(512)))
require.NoError(t, chain.persist())
})
t.Run("set", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao)
require.Equal(t, 1024, int(n))
@ -56,27 +50,18 @@ func TestMaxBlockSize(t *testing.T) {
t.Run("get", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getMaxBlockSize")
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.IntegerType,
Value: 1024 * 256,
})
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1024*256)))
require.NoError(t, chain.persist())
})
t.Run("set", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
res, err = invokeNativePolicyMethod(chain, "getMaxBlockSize")
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.IntegerType,
Value: 102400,
})
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(102400)))
require.NoError(t, chain.persist())
})
}
@ -93,20 +78,14 @@ func TestFeePerByte(t *testing.T) {
t.Run("get, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getFeePerByte")
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.IntegerType,
Value: 1000,
})
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1000)))
require.NoError(t, chain.persist())
})
t.Run("set", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
require.Equal(t, 1024, int(n))
@ -127,20 +106,14 @@ func TestBlockedAccounts(t *testing.T) {
t.Run("get, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getBlockedAccounts")
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.ArrayType,
Value: []smartcontract.Parameter{},
})
checkResult(t, res, stackitem.NewArray([]stackitem.Item{}))
require.NoError(t, chain.persist())
})
t.Run("block-unblock account", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
accounts, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
require.NoError(t, err)
@ -148,10 +121,7 @@ func TestBlockedAccounts(t *testing.T) {
require.NoError(t, chain.persist())
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
accounts, err = chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
require.NoError(t, err)
@ -163,35 +133,23 @@ func TestBlockedAccounts(t *testing.T) {
// block
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
// double-block should fail
res, err = invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: false,
})
checkResult(t, res, stackitem.NewBool(false))
require.NoError(t, chain.persist())
// unblock
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
// unblock the same account should fail as we don't have it blocked
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: false,
})
checkResult(t, res, stackitem.NewBool(false))
require.NoError(t, chain.persist())
})
@ -205,10 +163,7 @@ func TestBlockedAccounts(t *testing.T) {
for _, acc := range accounts {
res, err := invokeNativePolicyMethod(chain, "blockAccount", acc.BytesBE())
require.NoError(t, err)
checkResult(t, res, smartcontract.Parameter{
Type: smartcontract.BoolType,
Value: true,
})
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
}
@ -253,9 +208,8 @@ func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interfac
return res, nil
}
func checkResult(t *testing.T, result *state.AppExecResult, expected smartcontract.Parameter) {
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
require.Equal(t, vm.HaltState, result.VMState)
require.Equal(t, 1, len(result.Stack))
require.Equal(t, expected.Type, result.Stack[0].Type)
require.EqualValues(t, expected.Value, result.Stack[0].Value)
require.Equal(t, expected, result.Stack[0])
}

View file

@ -4,7 +4,6 @@ import (
"errors"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
@ -26,7 +25,7 @@ type AppExecResult struct {
Trigger trigger.Type
VMState vm.State
GasConsumed int64
Stack []smartcontract.Parameter
Stack []stackitem.Item
Events []NotificationEvent
}
@ -59,7 +58,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
w.WriteB(byte(aer.Trigger))
w.WriteB(byte(aer.VMState))
w.WriteU64LE(uint64(aer.GasConsumed))
w.WriteArray(aer.Stack)
stackitem.EncodeBinaryStackItem(stackitem.NewArray(aer.Stack), w)
w.WriteArray(aer.Events)
}
@ -69,6 +68,14 @@ func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
aer.Trigger = trigger.Type(r.ReadB())
aer.VMState = vm.State(r.ReadB())
aer.GasConsumed = int64(r.ReadU64LE())
r.ReadArray(&aer.Stack)
item := stackitem.DecodeBinaryStackItem(r)
if r.Err == nil {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
r.Err = errors.New("array expected")
return
}
aer.Stack = arr
}
r.ReadArray(&aer.Events)
}

View file

@ -5,7 +5,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -26,7 +25,7 @@ func TestEncodeDecodeAppExecResult(t *testing.T) {
Trigger: 1,
VMState: vm.HaltState,
GasConsumed: 10,
Stack: []smartcontract.Parameter{},
Stack: []stackitem.Item{},
Events: []NotificationEvent{},
}

View file

@ -6,12 +6,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
@ -192,46 +192,20 @@ func (c *Client) MultiTransferNEP5(acc *wallet.Account, token util.Uint160, gas
return c.SendRawTransaction(tx)
}
func topIntFromStack(st []smartcontract.Parameter) (int64, error) {
func topIntFromStack(st []stackitem.Item) (int64, error) {
index := len(st) - 1 // top stack element is last in the array
var decimals int64
switch typ := st[index].Type; typ {
case smartcontract.IntegerType:
var ok bool
decimals, ok = st[index].Value.(int64)
if !ok {
return 0, errors.New("invalid Integer item")
}
case smartcontract.ByteArrayType:
data, ok := st[index].Value.([]byte)
if !ok {
return 0, errors.New("invalid ByteArray item")
}
decimals = bigint.FromBytes(data).Int64()
default:
return 0, fmt.Errorf("invalid stack item type: %s", typ)
bi, err := st[index].TryInteger()
if err != nil {
return 0, err
}
return decimals, nil
return bi.Int64(), nil
}
func topStringFromStack(st []smartcontract.Parameter) (string, error) {
func topStringFromStack(st []stackitem.Item) (string, error) {
index := len(st) - 1 // top stack element is last in the array
var s string
switch typ := st[index].Type; typ {
case smartcontract.StringType:
var ok bool
s, ok = st[index].Value.(string)
if !ok {
return "", errors.New("invalid String item")
}
case smartcontract.ByteArrayType:
data, ok := st[index].Value.([]byte)
if !ok {
return "", errors.New("invalid ByteArray item")
}
s = string(data)
default:
return "", fmt.Errorf("invalid stack item type: %s", typ)
bs, err := st[index].TryBytes()
if err != nil {
return "", err
}
return s, nil
return string(bs), nil
}

View file

@ -6,6 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/pkg/errors"
)
@ -51,27 +52,26 @@ func (c *Client) GetBlockedAccounts() (native.BlockedAccounts, error) {
return topBlockedAccountsFromStack(result.Stack)
}
func topBlockedAccountsFromStack(st []smartcontract.Parameter) (native.BlockedAccounts, error) {
func topBlockedAccountsFromStack(st []stackitem.Item) (native.BlockedAccounts, error) {
index := len(st) - 1 // top stack element is last in the array
var (
ba native.BlockedAccounts
err error
)
switch typ := st[index].Type; typ {
case smartcontract.ArrayType:
data, ok := st[index].Value.([]smartcontract.Parameter)
items, ok := st[index].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type())
}
ba = make(native.BlockedAccounts, len(items))
for i, account := range items {
val, ok := account.Value().([]byte)
if !ok {
return nil, errors.New("invalid Array item")
return nil, fmt.Errorf("invalid array element: %s", account.Type())
}
ba = make(native.BlockedAccounts, len(data))
for i, account := range data {
ba[i], err = util.Uint160DecodeBytesLE(account.Value.([]byte))
if err != nil {
return nil, err
}
ba[i], err = util.Uint160DecodeBytesLE(val)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid stack item type: %s", typ)
}
return ba, nil
}

View file

@ -27,6 +27,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -638,7 +639,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
Account: util.Uint160{1, 2, 3},
}})
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gasconsumed":"31100000","stack":[{"type":"ByteArray","value":"JivsCEQy"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`,
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`,
result: func(c *Client) interface{} {
bytes, err := hex.DecodeString("262bec084432")
if err != nil {
@ -648,12 +649,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
State: "HALT",
GasConsumed: 31100000,
Script: "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf",
Stack: []smartcontract.Parameter{
{
Type: smartcontract.ByteArrayType,
Value: bytes,
},
},
Stack: []stackitem.Item{stackitem.NewByteArray(bytes)},
}
},
},
@ -670,7 +666,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
Account: util.Uint160{1, 2, 3},
}})
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gasconsumed":"16100000","stack":[{"type":"ByteArray","value":"TkVQNSBHQVM="}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`,
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gasconsumed":"16100000","stack":[{"type":"ByteString","value":"TkVQNSBHQVM="}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`,
result: func(c *Client) interface{} {
bytes, err := hex.DecodeString("4e45503520474153")
if err != nil {
@ -680,12 +676,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
State: "HALT",
GasConsumed: 16100000,
Script: "00046e616d656724058e5e1b6008847cd662728549088a9ee82191",
Stack: []smartcontract.Parameter{
{
Type: smartcontract.ByteArrayType,
Value: bytes,
},
},
Stack: []stackitem.Item{stackitem.NewByteArray(bytes)},
}
},
},

View file

@ -116,9 +116,9 @@ func TestWSClientEvents(t *testing.T) {
var ok bool
// Events from RPC server test chain.
var events = []string{
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","trigger":"Application","vmstate":"HALT","gasconsumed":"22910000","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"Y29udHJhY3QgY2FsbA=="},{"type":"ByteArray","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteArray","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteArray","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"dHJhbnNmZXI="},{"type":"ByteArray","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteArray","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}}]}]}`,
`{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"Y29udHJhY3QgY2FsbA=="},{"type":"ByteArray","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteArray","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteArray","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}}]}`,
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xf97a72b7722c109f909a8bc16c22368c5023d85828b09b127b237aace33cf099","trigger":"Application","vmstate":"HALT","gasconsumed":"6042610","stack":[],"notifications":[{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","state":{"type":"Array","value":[{"type":"ByteArray","value":"Y29udHJhY3QgY2FsbA=="},{"type":"ByteArray","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteArray","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteArray","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}]}},{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","state":{"type":"Array","value":[{"type":"ByteArray","value":"dHJhbnNmZXI="},{"type":"ByteArray","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteArray","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}}]}]}`,
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","trigger":"Application","vmstate":"HALT","gasconsumed":"22910000","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}}]}]}`,
`{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}}]}`,
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xf97a72b7722c109f909a8bc16c22368c5023d85828b09b127b237aace33cf099","trigger":"Application","vmstate":"HALT","gasconsumed":"6042610","stack":[],"notifications":[{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}]}},{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}}]}]}`,
`{"jsonrpc":"2.0","method":"block_added","params":[{"hash":"0x2d312f6379ead13cf62634c703091b750e7903728df2a3cf5bd96ce80b84a849","version":0,"previousblockhash":"0xb8237d34c156cac6be7b01578decf8ac8c99a62f0b6f774d622aad7be0fe189d","merkleroot":"0xf89169e89361692b71e671f13c088e84c5325015c413e8f89e7ba38efdb41287","time":1592472500006,"index":6,"nextconsensus":"Nbb1qkwcwNSBs9pAnrVVrnFbWnbWBk91U2","witnesses":[{"invocation":"DEDblVguNGXWbUswDvBfVJzBt76BJyJ0Ga6siquyjioGn4Dbr6zy1IvcLl3xN5akcejRy9e+Mr1qvpe/gkLgtW4QDEDRwPISZagMFjE/plXTnZ/gEU0IbBAAe23U29zVWteUmzRsPxF/MdzXvdffR9W0edkj17AmkWpn+5rqzH9aCOpLDECEvjgxZaRoAHEDNzp1REllLcGzMwrwSjudtzfgRglQL3g1BKerDx6cGHH73medRVkL9QVm4KzSxlywVtvhwBMrDEBuPKvzg5TtakFW2jr/bfmy1bn2FiLARlOySwaGdKRV93ozA5lVEIAvHbBlJtT4/5H8jHjbncXXMrP3OUHqebZz","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBMHOzuw=="}],"consensusdata":{"primary":0,"nonce":"0000000000000457"},"tx":[{"hash":"0xf97a72b7722c109f909a8bc16c22368c5023d85828b09b127b237aace33cf099","size":265,"version":0,"nonce":9,"sender":"NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN","sysfee":"0","netfee":"365210","validuntilblock":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":"CalledByEntry"}],"script":"AHsMFCBygnSvr8NvQ6Bx0yjPo+Yp2cuwDBQxboUQOQGdOd/Cw31sP+4Z/VgJhxPADAh0cmFuc2ZlcgwU2gHvYphOfQUnXEpYeyAtoLP3X+ZBYn1bUjg=","witnesses":[{"invocation":"DECwklSj3liZOJbktRtkVdUCu8U2LQlrU6Dv8NtMgd0xXbk5lXjc2p68xv6xtJXbJ4aoFMJZ9lkcNpGoeUCcaCet","verification":"DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQQqQatQ="}]}]}]}`,
`{"jsonrpc":"2.0","method":"event_missed","params":[]}`,
}

View file

@ -21,6 +21,7 @@ type ApplicationLog struct {
//NotificationEvent response wrapper
type NotificationEvent struct {
Contract util.Uint160 `json:"contract"`
Name string `json:"eventname"`
Item smartcontract.Parameter `json:"state"`
}
@ -28,13 +29,10 @@ type NotificationEvent struct {
// result.NotificationEvent.
func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent {
seen := make(map[stackitem.Item]bool)
args := stackitem.NewArray([]stackitem.Item{
stackitem.Make(event.Name),
event.Item,
})
item := smartcontract.ParameterFromStackItem(args, seen)
item := smartcontract.ParameterFromStackItem(event.Item, seen)
return NotificationEvent{
Contract: event.ScriptHash,
Name: event.Name,
Item: item,
}
}
@ -45,13 +43,17 @@ func NewApplicationLog(appExecRes *state.AppExecResult) ApplicationLog {
for _, e := range appExecRes.Events {
events = append(events, StateEventToResultNotification(e))
}
st := make([]smartcontract.Parameter, len(appExecRes.Stack))
seen := make(map[stackitem.Item]bool)
for i := range appExecRes.Stack {
st[i] = smartcontract.ParameterFromStackItem(appExecRes.Stack[i], seen)
}
return ApplicationLog{
TxHash: appExecRes.TxHash,
Trigger: appExecRes.Trigger.String(),
VMState: appExecRes.VMState.String(),
GasConsumed: appExecRes.GasConsumed,
Stack: appExecRes.Stack,
Stack: st,
Events: events,
}
}

View file

@ -1,14 +1,76 @@
package result
import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Invoke represents code invocation result and is used by several RPC calls
// that invoke functions, scripts and generic bytecode.
type Invoke struct {
State string `json:"state"`
GasConsumed int64 `json:"gasconsumed,string"`
Script string `json:"script"`
Stack []smartcontract.Parameter `json:"stack"`
State string `json:"state"`
GasConsumed int64 `json:"gasconsumed,string"`
Script string `json:"script"`
Stack []stackitem.Item `json:"stack"`
}
type invokeAux struct {
State string `json:"state"`
GasConsumed int64 `json:"gasconsumed,string"`
Script string `json:"script"`
Stack json.RawMessage `json:"stack"`
}
// MarshalJSON implements json.Marshaler.
func (r Invoke) MarshalJSON() ([]byte, error) {
var st json.RawMessage
arr := make([]json.RawMessage, len(r.Stack))
for i := range arr {
data, err := stackitem.ToJSONWithTypes(r.Stack[i])
if err != nil {
st = []byte("error: recursive reference")
break
}
arr[i] = data
}
var err error
if st == nil {
st, err = json.Marshal(arr)
if err != nil {
return nil, err
}
}
return json.Marshal(&invokeAux{
GasConsumed: r.GasConsumed,
Script: r.Script,
State: r.State,
Stack: st,
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (r *Invoke) UnmarshalJSON(data []byte) error {
aux := new(invokeAux)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
var arr []json.RawMessage
if err := json.Unmarshal(aux.Stack, &arr); err == nil {
st := make([]stackitem.Item, len(arr))
for i := range arr {
st[i], err = stackitem.FromJSONWithTypes(arr[i])
if err != nil {
break
}
}
if err == nil {
r.Stack = st
}
}
r.GasConsumed = aux.GasConsumed
r.Script = aux.Script
r.State = aux.State
return nil
}

View file

@ -22,7 +22,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/rpc"
@ -623,14 +622,11 @@ func (s *Server) getDecimals(contractID int32, cache map[int32]decimals) (decima
}
d := decimals{Hash: h}
switch item := res.Stack[len(res.Stack)-1]; item.Type {
case smartcontract.IntegerType:
d.Value = item.Value.(int64)
case smartcontract.ByteArrayType:
d.Value = bigint.FromBytes(item.Value.([]byte)).Int64()
default:
return d, errors.New("invalid result: not an integer")
bi, err := res.Stack[len(res.Stack)-1].TryInteger()
if err != nil {
return decimals{}, err
}
d.Value = bi.Int64()
if d.Value < 0 {
return d, errors.New("incorrect result: negative result")
}
@ -896,7 +892,7 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *resu
State: vm.State().String(),
GasConsumed: vm.GasConsumed(),
Script: hex.EncodeToString(script),
Stack: vm.Estack().ToContractParameters(),
Stack: vm.Estack().ToArray(),
}
return result
}

View file

@ -531,7 +531,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, int64(3), res.Stack[0].Value)
require.Equal(t, big.NewInt(3), res.Stack[0].Value())
},
},
{
@ -543,7 +543,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, int64(2), res.Stack[0].Value)
require.Equal(t, big.NewInt(2), res.Stack[0].Value())
},
},
{
@ -555,7 +555,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, int64(1), res.Stack[0].Value)
require.Equal(t, big.NewInt(1), res.Stack[0].Value())
},
},
{
@ -567,7 +567,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
assert.Equal(t, 1, len(res.Stack))
assert.Equal(t, int64(1), res.Stack[0].Value)
assert.Equal(t, big.NewInt(1), res.Stack[0].Value())
},
},
{

View file

@ -32,11 +32,11 @@ var marshalJSONTestCases = []struct {
},
{
input: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
result: `{"type":"ByteArray","value":"` + hexToBase64("010203") + `"}`,
result: `{"type":"ByteString","value":"` + hexToBase64("010203") + `"}`,
},
{
input: Parameter{Type: ByteArrayType},
result: `{"type":"ByteArray","value":null}`,
result: `{"type":"ByteString","value":null}`,
},
{
input: Parameter{
@ -68,8 +68,8 @@ var marshalJSONTestCases = []struct {
}},
},
},
result: `{"type":"Array","value":[{"type":"ByteArray","value":"` + hexToBase64("0102") + `"},{"type":"Array","value":[` +
`{"type":"ByteArray","value":"` + hexToBase64("030201") + `"},{"type":"ByteArray","value":"` + hexToBase64("070809") + `"}]}]}`,
result: `{"type":"Array","value":[{"type":"ByteString","value":"` + hexToBase64("0102") + `"},{"type":"Array","value":[` +
`{"type":"ByteString","value":"` + hexToBase64("030201") + `"},{"type":"ByteString","value":"` + hexToBase64("070809") + `"}]}]}`,
},
{
input: Parameter{
@ -185,7 +185,7 @@ var unmarshalJSONTestCases = []struct {
result: Parameter{Type: IntegerType, Value: int64(12345)},
},
{
input: `{"type":"ByteArray","value":"` + hexToBase64("010203") + `"}`,
input: `{"type":"ByteString","value":"` + hexToBase64("010203") + `"}`,
result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
},
{
@ -288,9 +288,9 @@ var unmarshalJSONTestCases = []struct {
}
var unmarshalJSONErrorCases = []string{
`{"type": "ByteArray","value":`, // incorrect JSON
`{"type": "ByteArray","value":1}`, // incorrect Value
`{"type": "ByteArray","value":"12^"}`, // incorrect ByteArray value
`{"type": "ByteString","value":`, // incorrect JSON
`{"type": "ByteString","value":1}`, // incorrect Value
`{"type": "ByteString","value":"12^"}`, // incorrect ByteArray value
`{"type": "String","value":`, // incorrect JSON
`{"type": "String","value":1}`, // incorrect Value
`{"type": "Integer","value": "nn"}`, // incorrect Integer value

View file

@ -6,7 +6,6 @@ import (
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -406,18 +405,24 @@ func (s *Stack) PopSigElements() ([][]byte, error) {
return elems, nil
}
// ToContractParameters converts Stack to slice of smartcontract.Parameter.
func (s *Stack) ToContractParameters() []smartcontract.Parameter {
items := make([]smartcontract.Parameter, 0, s.Len())
// ToArray converts stack to an array of stackitems with top item being the last.
func (s *Stack) ToArray() []stackitem.Item {
items := make([]stackitem.Item, 0, s.len)
s.IterBack(func(e *Element) {
// Each item is independent.
seen := make(map[stackitem.Item]bool)
items = append(items, smartcontract.ParameterFromStackItem(e.value, seen))
items = append(items, e.Item())
})
return items
}
// MarshalJSON implements JSON marshalling interface.
func (s *Stack) MarshalJSON() ([]byte, error) {
return json.Marshal(s.ToContractParameters())
items := s.ToArray()
arr := make([]json.RawMessage, len(items))
for i := range items {
data, err := stackitem.ToJSONWithTypes(items[i])
if err == nil {
arr[i] = data
}
}
return json.Marshal(arr)
}

View file

@ -237,6 +237,22 @@ func TestPushVal(t *testing.T) {
assert.IsType(t, elem.value, &stackitem.Array{})
}
func TestStack_ToArray(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
s := NewStack("test")
items := s.ToArray()
require.Equal(t, 0, len(items))
})
t.Run("NonEmpty", func(t *testing.T) {
s := NewStack("test")
expected := []stackitem.Item{stackitem.Make(1), stackitem.Make(true)}
for i := range expected {
s.PushVal(expected[i])
}
require.Equal(t, expected, s.ToArray())
})
}
func TestSwapElemValues(t *testing.T) {
s := NewStack("test")

View file

@ -215,7 +215,7 @@ func (i *Struct) Bool() bool { return true }
// TryBytes implements Item interface.
func (i *Struct) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Struct to ByteArray")
return nil, errors.New("can't convert Struct to ByteString")
}
// TryInteger implements Item interface.
@ -301,7 +301,7 @@ func (i Null) Bool() bool { return false }
// TryBytes implements Item interface.
func (i Null) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Null to ByteArray")
return nil, errors.New("can't convert Null to ByteString")
}
// TryInteger implements Item interface.
@ -497,7 +497,7 @@ func (i *ByteArray) MarshalJSON() ([]byte, error) {
}
func (i *ByteArray) String() string {
return "ByteArray"
return "ByteString"
}
// Bool implements Item interface.
@ -606,7 +606,7 @@ func (i *Array) Bool() bool { return true }
// TryBytes implements Item interface.
func (i *Array) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Array to ByteArray")
return nil, errors.New("can't convert Array to ByteString")
}
// TryInteger implements Item interface.
@ -696,7 +696,7 @@ func (i *Map) Bool() bool { return true }
// TryBytes implements Item interface.
func (i *Map) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Map to ByteArray")
return nil, errors.New("can't convert Map to ByteString")
}
// TryInteger implements Item interface.
@ -812,7 +812,7 @@ func (i *Interop) Bool() bool { return true }
// TryBytes implements Item interface.
func (i *Interop) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Interop to ByteArray")
return nil, errors.New("can't convert Interop to ByteString")
}
// TryInteger implements Item interface.
@ -893,7 +893,7 @@ func (p *Pointer) Bool() bool {
// TryBytes implements Item interface.
func (p *Pointer) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Pointer to ByteArray")
return nil, errors.New("can't convert Pointer to ByteString")
}
// TryInteger implements Item interface.

View file

@ -112,7 +112,7 @@ var stringerTestCases = []struct {
},
{
input: NewByteArray([]byte{}),
result: "ByteArray",
result: "ByteString",
},
{
input: NewArray([]Item{}),

View file

@ -208,3 +208,171 @@ func (d *decoder) decodeMap() (*Map, error) {
m.Add(NewByteArray([]byte(k)), val)
}
}
// ToJSONWithTypes serializes any stackitem to JSON in a lossless way.
func ToJSONWithTypes(item Item) ([]byte, error) {
result, err := toJSONWithTypes(item, make(map[Item]bool))
if err != nil {
return nil, err
}
return json.Marshal(result)
}
func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) {
typ := item.Type()
result := map[string]interface{}{
"type": typ.String(),
}
var value interface{}
switch it := item.(type) {
case *Array, *Struct:
if seen[item] {
return "", errors.New("recursive structures can't be serialized to json")
}
seen[item] = true
arr := []interface{}{}
for _, elem := range it.Value().([]Item) {
s, err := toJSONWithTypes(elem, seen)
if err != nil {
return "", err
}
arr = append(arr, s)
}
value = arr
case *Bool:
value = it.value
case *Buffer, *ByteArray:
value = base64.StdEncoding.EncodeToString(it.Value().([]byte))
case *BigInteger:
value = it.value.String()
case *Map:
if seen[item] {
return "", errors.New("recursive structures can't be serialized to json")
}
seen[item] = true
arr := []interface{}{}
for i := range it.value {
// map keys are primitive types and can always be converted to json
key, _ := toJSONWithTypes(it.value[i].Key, seen)
val, err := toJSONWithTypes(it.value[i].Value, seen)
if err != nil {
return "", err
}
arr = append(arr, map[string]interface{}{
"key": key,
"value": val,
})
}
value = arr
case *Pointer:
value = it.pos
}
if value != nil {
result["value"] = value
}
return result, nil
}
type (
rawItem struct {
Type string `json:"type"`
Value json.RawMessage `json:"value,omitempty"`
}
rawMapElement struct {
Key json.RawMessage `json:"key"`
Value json.RawMessage `json:"value"`
}
)
// FromJSONWithTypes deserializes an item from typed-json representation.
func FromJSONWithTypes(data []byte) (Item, error) {
raw := new(rawItem)
if err := json.Unmarshal(data, raw); err != nil {
return nil, err
}
typ, err := FromString(raw.Type)
if err != nil {
return nil, errors.New("invalid type")
}
switch typ {
case AnyT:
return Null{}, nil
case PointerT:
var pos int
if err := json.Unmarshal(raw.Value, &pos); err != nil {
return nil, err
}
return NewPointer(pos, nil), nil
case BooleanT:
var b bool
if err := json.Unmarshal(raw.Value, &b); err != nil {
return nil, err
}
return NewBool(b), nil
case IntegerT:
var s string
if err := json.Unmarshal(raw.Value, &s); err != nil {
return nil, err
}
val, ok := new(big.Int).SetString(s, 10)
if !ok {
return nil, errors.New("invalid integer")
}
return NewBigInteger(val), nil
case ByteArrayT, BufferT:
var s string
if err := json.Unmarshal(raw.Value, &s); err != nil {
return nil, err
}
val, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
if typ == ByteArrayT {
return NewByteArray(val), nil
}
return NewBuffer(val), nil
case ArrayT, StructT:
var arr []json.RawMessage
if err := json.Unmarshal(raw.Value, &arr); err != nil {
return nil, err
}
items := make([]Item, len(arr))
for i := range arr {
it, err := FromJSONWithTypes(arr[i])
if err != nil {
return nil, err
}
items[i] = it
}
if typ == ArrayT {
return NewArray(items), nil
}
return NewStruct(items), nil
case MapT:
var arr []rawMapElement
if err := json.Unmarshal(raw.Value, &arr); err != nil {
return nil, err
}
m := NewMap()
for i := range arr {
key, err := FromJSONWithTypes(arr[i].Key)
if err != nil {
return nil, err
} else if !IsValidMapKey(key) {
return nil, fmt.Errorf("invalid map key of type %s", key.Type())
}
value, err := FromJSONWithTypes(arr[i].Value)
if err != nil {
return nil, err
}
m.Add(key, value)
}
return m, nil
case InteropT:
return NewInterop(nil), nil
default:
return nil, errors.New("unexpected type")
}
}

View file

@ -5,6 +5,7 @@ import (
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -103,3 +104,137 @@ func TestFromToJSON(t *testing.T) {
})
})
}
// This test is taken from the C# code
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/VM/UT_Helper.cs#L30
func TestToJSONWithTypeCompat(t *testing.T) {
items := []Item{
Make(5), Make("hello world"),
Make([]byte{1, 2, 3}), Make(true),
}
// Note: we use `Equal` and not `JSONEq` because there are no spaces and maps so the order is well-defined.
s, err := ToJSONWithTypes(items[0])
assert.NoError(t, err)
assert.Equal(t, `{"type":"Integer","value":"5"}`, string(s))
s, err = ToJSONWithTypes(items[1])
assert.NoError(t, err)
assert.Equal(t, `{"type":"ByteString","value":"aGVsbG8gd29ybGQ="}`, string(s))
s, err = ToJSONWithTypes(items[2])
assert.NoError(t, err)
assert.Equal(t, `{"type":"ByteString","value":"AQID"}`, string(s))
s, err = ToJSONWithTypes(items[3])
assert.NoError(t, err)
assert.Equal(t, `{"type":"Boolean","value":true}`, string(s))
s, err = ToJSONWithTypes(NewArray(items))
assert.NoError(t, err)
assert.Equal(t, `{"type":"Array","value":[{"type":"Integer","value":"5"},{"type":"ByteString","value":"aGVsbG8gd29ybGQ="},{"type":"ByteString","value":"AQID"},{"type":"Boolean","value":true}]}`, string(s))
item := NewMap()
item.Add(Make(1), NewPointer(0, []byte{0}))
s, err = ToJSONWithTypes(item)
assert.NoError(t, err)
assert.Equal(t, `{"type":"Map","value":[{"key":{"type":"Integer","value":"1"},"value":{"type":"Pointer","value":0}}]}`, string(s))
}
func TestToJSONWithTypes(t *testing.T) {
testCases := []struct {
name string
item Item
result string
}{
{"Null", Null{}, `{"type":"Any"}`},
{"Integer", NewBigInteger(big.NewInt(42)), `{"type":"Integer","value":"42"}`},
{"ByteString", NewByteArray([]byte{1, 2, 3}), `{"type":"ByteString","value":"AQID"}`},
{"Buffer", NewBuffer([]byte{1, 2, 3}), `{"type":"Buffer","value":"AQID"}`},
{"BoolTrue", NewBool(true), `{"type":"Boolean","value":true}`},
{"BoolFalse", NewBool(false), `{"type":"Boolean","value":false}`},
{"Struct", NewStruct([]Item{Make(11)}),
`{"type":"Struct","value":[{"type":"Integer","value":"11"}]}`},
{"Map", NewMapWithValue([]MapElement{{Key: NewBigInteger(big.NewInt(42)), Value: NewBool(false)}}),
`{"type":"Map","value":[{"key":{"type":"Integer","value":"42"},` +
`"value":{"type":"Boolean","value":false}}]}`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
s, err := ToJSONWithTypes(tc.item)
require.NoError(t, err)
require.Equal(t, tc.result, string(s))
item, err := FromJSONWithTypes(s)
require.NoError(t, err)
require.Equal(t, tc.item, item)
})
}
t.Run("Invalid", func(t *testing.T) {
t.Run("RecursiveArray", func(t *testing.T) {
arr := NewArray(nil)
arr.value = []Item{Make(5), arr, Make(true)}
_, err := ToJSONWithTypes(arr)
require.Error(t, err)
})
t.Run("RecursiveMap", func(t *testing.T) {
m := NewMap()
m.Add(Make(3), Make(true))
m.Add(Make(5), m)
_, err := ToJSONWithTypes(m)
require.Error(t, err)
})
})
}
func TestFromJSONWithTypes(t *testing.T) {
testCases := []struct {
name string
json string
item Item
}{
{"Pointer", `{"type":"Pointer","value":3}`, NewPointer(3, nil)},
{"Interop", `{"type":"Interop"}`, NewInterop(nil)},
{"Null", `{"type":"Any"}`, Null{}},
{"Array", `{"type":"Array","value":[{"type":"Any"}]}`, NewArray([]Item{Null{}})},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
item, err := FromJSONWithTypes([]byte(tc.json))
require.NoError(t, err)
require.Equal(t, tc.item, item)
})
}
t.Run("Invalid", func(t *testing.T) {
errCases := []struct {
name string
json string
}{
{"InvalidType", `{"type":int,"value":"4"`},
{"UnexpectedType", `{"type":"int","value":"4"}`},
{"IntegerValue1", `{"type":"Integer","value": 4}`},
{"IntegerValue2", `{"type":"Integer","value": "a"}`},
{"BoolValue", `{"type":"Boolean","value": "str"}`},
{"PointerValue", `{"type":"Pointer","value": "str"}`},
{"BufferValue1", `{"type":"Buffer","value":"not a base 64"}`},
{"BufferValue2", `{"type":"Buffer","value":123}`},
{"ArrayValue", `{"type":"Array","value":3}`},
{"ArrayElement", `{"type":"Array","value":[3]}`},
{"MapValue", `{"type":"Map","value":3}`},
{"MapElement", `{"type":"Map","value":[{"key":"value"}]}`},
{"MapElementKeyNotPrimitive", `{"type":"Map","value":[{"key":{"type":"Any"}}]}`},
{"MapElementValue", `{"type":"Map","value":[` +
`{"key":{"type":"Integer","value":"3"},"value":3}]}`},
}
for _, tc := range errCases {
t.Run(tc.name, func(t *testing.T) {
_, err := FromJSONWithTypes([]byte(tc.json))
require.Error(t, err)
})
}
})
}

View file

@ -1,5 +1,7 @@
package stackitem
import "errors"
// Type represents type of the stack item.
type Type byte
@ -29,7 +31,7 @@ func (t Type) String() string {
case IntegerT:
return "Integer"
case ByteArrayT:
return "ByteArray"
return "ByteString"
case BufferT:
return "Buffer"
case ArrayT:
@ -54,3 +56,31 @@ func (t Type) IsValid() bool {
return false
}
}
// FromString returns stackitem type from string.
func FromString(s string) (Type, error) {
switch s {
case "Any":
return AnyT, nil
case "Pointer":
return PointerT, nil
case "Boolean":
return BooleanT, nil
case "Integer":
return IntegerT, nil
case "ByteString":
return ByteArrayT, nil
case "Buffer":
return BufferT, nil
case "Array":
return ArrayT, nil
case "Struct":
return StructT, nil
case "Map":
return MapT, nil
case "Interop":
return InteropT, nil
default:
return 0xFF, errors.New("invalid type")
}
}

View file

@ -0,0 +1,16 @@
package stackitem
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestFromString(t *testing.T) {
typs := []Type{AnyT, PointerT, BooleanT, IntegerT, ByteArrayT, BufferT, ArrayT, StructT, MapT, InteropT}
for _, typ := range typs {
actual, err := FromString(typ.String())
require.NoError(t, err)
require.Equal(t, typ, actual)
}
}

View file

@ -321,7 +321,7 @@ func (v *VM) Stack(n string) string {
if n == "estack" {
s = v.estack
}
b, _ := json.MarshalIndent(s.ToContractParameters(), "", " ")
b, _ := json.MarshalIndent(s, "", " ")
return string(b)
}