Merge pull request #1242 from nspcc-dev/fix/json
Implement lossless stackitem to json conversion
This commit is contained in:
commit
fa5ff8dd95
27 changed files with 562 additions and 208 deletions
|
@ -293,21 +293,21 @@ Example:
|
||||||
"value" : [
|
"value" : [
|
||||||
{
|
{
|
||||||
"value" : "636f6e74726163742063616c6c",
|
"value" : "636f6e74726163742063616c6c",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : "7472616e73666572",
|
"value" : "7472616e73666572",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : [
|
"value" : [
|
||||||
{
|
{
|
||||||
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
|
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987",
|
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : "1000",
|
"value" : "1000",
|
||||||
|
@ -351,20 +351,20 @@ Example:
|
||||||
"value" : [
|
"value" : [
|
||||||
{
|
{
|
||||||
"value" : "636f6e74726163742063616c6c",
|
"value" : "636f6e74726163742063616c6c",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type" : "ByteArray",
|
"type" : "ByteString",
|
||||||
"value" : "7472616e73666572"
|
"value" : "7472616e73666572"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : [
|
"value" : [
|
||||||
{
|
{
|
||||||
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
|
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type" : "ByteArray",
|
"type" : "ByteString",
|
||||||
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987"
|
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -384,15 +384,15 @@ Example:
|
||||||
"value" : [
|
"value" : [
|
||||||
{
|
{
|
||||||
"value" : "7472616e73666572",
|
"value" : "7472616e73666572",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
|
"value" : "769162241eedf97c2481652adf1ba0f5bf57431b",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987",
|
"value" : "316e851039019d39dfc2c37d6c3fee19fd580987",
|
||||||
"type" : "ByteArray"
|
"type" : "ByteString"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value" : "1000",
|
"value" : "1000",
|
||||||
|
|
|
@ -206,7 +206,7 @@ func (c *codegen) scTypeFromExpr(typ ast.Expr) string {
|
||||||
return "Struct"
|
return "Struct"
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
if isByte(t.Elem()) {
|
if isByte(t.Elem()) {
|
||||||
return "ByteArray"
|
return "ByteString"
|
||||||
}
|
}
|
||||||
return "Array"
|
return "Array"
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -63,7 +63,7 @@ func unexportedMethod() int { return 1 }
|
||||||
returnTypes := map[string]string{
|
returnTypes := map[string]string{
|
||||||
"MethodInt": "Integer",
|
"MethodInt": "Integer",
|
||||||
"MethodConcat": "String",
|
"MethodConcat": "String",
|
||||||
"MethodString": "String", "MethodByteArray": "ByteArray",
|
"MethodString": "String", "MethodByteArray": "ByteString",
|
||||||
"MethodArray": "Array", "MethodStruct": "Struct",
|
"MethodArray": "Array", "MethodStruct": "Struct",
|
||||||
"Main": "Boolean",
|
"Main": "Boolean",
|
||||||
"unexportedMethod": "Integer",
|
"unexportedMethod": "Integer",
|
||||||
|
@ -272,7 +272,7 @@ func TestDebugInfo_MarshalJSON(t *testing.T) {
|
||||||
{"param1", "Integer"},
|
{"param1", "Integer"},
|
||||||
{"ok", "Boolean"},
|
{"ok", "Boolean"},
|
||||||
},
|
},
|
||||||
ReturnType: "ByteArray",
|
ReturnType: "ByteString",
|
||||||
Variables: []string{},
|
Variables: []string{},
|
||||||
SeqPoints: []DebugSeqPoint{
|
SeqPoints: []DebugSeqPoint{
|
||||||
{
|
{
|
||||||
|
|
|
@ -591,7 +591,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
Trigger: trigger.System,
|
Trigger: trigger.System,
|
||||||
VMState: v.State(),
|
VMState: v.State(),
|
||||||
GasConsumed: v.GasConsumed(),
|
GasConsumed: v.GasConsumed(),
|
||||||
Stack: v.Estack().ToContractParameters(),
|
Stack: v.Estack().ToArray(),
|
||||||
Events: systemInterop.Notifications,
|
Events: systemInterop.Notifications,
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
|
@ -632,7 +632,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: v.State(),
|
VMState: v.State(),
|
||||||
GasConsumed: v.GasConsumed(),
|
GasConsumed: v.GasConsumed(),
|
||||||
Stack: v.Estack().ToContractParameters(),
|
Stack: v.Estack().ToArray(),
|
||||||
Events: systemInterop.Notifications,
|
Events: systemInterop.Notifications,
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ func TestPutGetAppExecResult(t *testing.T) {
|
||||||
appExecResult := &state.AppExecResult{
|
appExecResult := &state.AppExecResult{
|
||||||
TxHash: hash,
|
TxHash: hash,
|
||||||
Events: []state.NotificationEvent{},
|
Events: []state.NotificationEvent{},
|
||||||
Stack: []smartcontract.Parameter{},
|
Stack: []stackitem.Item{},
|
||||||
}
|
}
|
||||||
err := dao.PutAppExecResult(appExecResult)
|
err := dao.PutAppExecResult(appExecResult)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -131,8 +131,7 @@ func TestNativeContract_Invoke(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
require.Equal(t, vm.HaltState, res.VMState)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res.Stack))
|
||||||
require.Equal(t, smartcontract.IntegerType, res.Stack[0].Type)
|
require.Equal(t, big.NewInt(42), res.Stack[0].Value())
|
||||||
require.EqualValues(t, 42, res.Stack[0].Value)
|
|
||||||
|
|
||||||
res, err = chain.GetAppExecResult(tx2.Hash())
|
res, err = chain.GetAppExecResult(tx2.Hash())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/encoding/bigint"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"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/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,20 +29,14 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
|
||||||
t.Run("get, contract method", func(t *testing.T) {
|
t.Run("get, contract method", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "getMaxTransactionsPerBlock")
|
res, err := invokeNativePolicyMethod(chain, "getMaxTransactionsPerBlock")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(512)))
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: 512,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("set", func(t *testing.T) {
|
t.Run("set", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
|
res, err := invokeNativePolicyMethod(chain, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao)
|
n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao)
|
||||||
require.Equal(t, 1024, int(n))
|
require.Equal(t, 1024, int(n))
|
||||||
|
@ -56,27 +50,18 @@ func TestMaxBlockSize(t *testing.T) {
|
||||||
t.Run("get", func(t *testing.T) {
|
t.Run("get", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "getMaxBlockSize")
|
res, err := invokeNativePolicyMethod(chain, "getMaxBlockSize")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1024*256)))
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: 1024 * 256,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("set", func(t *testing.T) {
|
t.Run("set", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
|
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
res, err = invokeNativePolicyMethod(chain, "getMaxBlockSize")
|
res, err = invokeNativePolicyMethod(chain, "getMaxBlockSize")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(102400)))
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: 102400,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -93,20 +78,14 @@ func TestFeePerByte(t *testing.T) {
|
||||||
t.Run("get, contract method", func(t *testing.T) {
|
t.Run("get, contract method", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "getFeePerByte")
|
res, err := invokeNativePolicyMethod(chain, "getFeePerByte")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1000)))
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: 1000,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("set", func(t *testing.T) {
|
t.Run("set", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
|
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
|
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
|
||||||
require.Equal(t, 1024, int(n))
|
require.Equal(t, 1024, int(n))
|
||||||
|
@ -127,20 +106,14 @@ func TestBlockedAccounts(t *testing.T) {
|
||||||
t.Run("get, contract method", func(t *testing.T) {
|
t.Run("get, contract method", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "getBlockedAccounts")
|
res, err := invokeNativePolicyMethod(chain, "getBlockedAccounts")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewArray([]stackitem.Item{}))
|
||||||
Type: smartcontract.ArrayType,
|
|
||||||
Value: []smartcontract.Parameter{},
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("block-unblock account", func(t *testing.T) {
|
t.Run("block-unblock account", func(t *testing.T) {
|
||||||
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
accounts, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
|
accounts, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -148,10 +121,7 @@ func TestBlockedAccounts(t *testing.T) {
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
accounts, err = chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
|
accounts, err = chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -163,35 +133,23 @@ func TestBlockedAccounts(t *testing.T) {
|
||||||
// block
|
// block
|
||||||
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
// double-block should fail
|
// double-block should fail
|
||||||
res, err = invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
res, err = invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(false))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: false,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
// unblock
|
// unblock
|
||||||
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
// unblock the same account should fail as we don't have it blocked
|
// unblock the same account should fail as we don't have it blocked
|
||||||
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(false))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: false,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -205,10 +163,7 @@ func TestBlockedAccounts(t *testing.T) {
|
||||||
for _, acc := range accounts {
|
for _, acc := range accounts {
|
||||||
res, err := invokeNativePolicyMethod(chain, "blockAccount", acc.BytesBE())
|
res, err := invokeNativePolicyMethod(chain, "blockAccount", acc.BytesBE())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkResult(t, res, smartcontract.Parameter{
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
Type: smartcontract.BoolType,
|
|
||||||
Value: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,9 +208,8 @@ func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interfac
|
||||||
return res, nil
|
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, vm.HaltState, result.VMState)
|
||||||
require.Equal(t, 1, len(result.Stack))
|
require.Equal(t, 1, len(result.Stack))
|
||||||
require.Equal(t, expected.Type, result.Stack[0].Type)
|
require.Equal(t, expected, result.Stack[0])
|
||||||
require.EqualValues(t, expected.Value, result.Stack[0].Value)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
@ -26,7 +25,7 @@ type AppExecResult struct {
|
||||||
Trigger trigger.Type
|
Trigger trigger.Type
|
||||||
VMState vm.State
|
VMState vm.State
|
||||||
GasConsumed int64
|
GasConsumed int64
|
||||||
Stack []smartcontract.Parameter
|
Stack []stackitem.Item
|
||||||
Events []NotificationEvent
|
Events []NotificationEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteB(byte(aer.Trigger))
|
w.WriteB(byte(aer.Trigger))
|
||||||
w.WriteB(byte(aer.VMState))
|
w.WriteB(byte(aer.VMState))
|
||||||
w.WriteU64LE(uint64(aer.GasConsumed))
|
w.WriteU64LE(uint64(aer.GasConsumed))
|
||||||
w.WriteArray(aer.Stack)
|
stackitem.EncodeBinaryStackItem(stackitem.NewArray(aer.Stack), w)
|
||||||
w.WriteArray(aer.Events)
|
w.WriteArray(aer.Events)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +68,14 @@ func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
||||||
aer.Trigger = trigger.Type(r.ReadB())
|
aer.Trigger = trigger.Type(r.ReadB())
|
||||||
aer.VMState = vm.State(r.ReadB())
|
aer.VMState = vm.State(r.ReadB())
|
||||||
aer.GasConsumed = int64(r.ReadU64LE())
|
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)
|
r.ReadArray(&aer.Events)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
"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/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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +25,7 @@ func TestEncodeDecodeAppExecResult(t *testing.T) {
|
||||||
Trigger: 1,
|
Trigger: 1,
|
||||||
VMState: vm.HaltState,
|
VMState: vm.HaltState,
|
||||||
GasConsumed: 10,
|
GasConsumed: 10,
|
||||||
Stack: []smartcontract.Parameter{},
|
Stack: []stackitem.Item{},
|
||||||
Events: []NotificationEvent{},
|
Events: []NotificationEvent{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"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"
|
"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)
|
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
|
index := len(st) - 1 // top stack element is last in the array
|
||||||
var decimals int64
|
bi, err := st[index].TryInteger()
|
||||||
switch typ := st[index].Type; typ {
|
if err != nil {
|
||||||
case smartcontract.IntegerType:
|
return 0, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
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
|
index := len(st) - 1 // top stack element is last in the array
|
||||||
var s string
|
bs, err := st[index].TryBytes()
|
||||||
switch typ := st[index].Type; typ {
|
if err != nil {
|
||||||
case smartcontract.StringType:
|
return "", err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
return s, nil
|
return string(bs), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,27 +52,26 @@ func (c *Client) GetBlockedAccounts() (native.BlockedAccounts, error) {
|
||||||
return topBlockedAccountsFromStack(result.Stack)
|
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
|
index := len(st) - 1 // top stack element is last in the array
|
||||||
var (
|
var (
|
||||||
ba native.BlockedAccounts
|
ba native.BlockedAccounts
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
switch typ := st[index].Type; typ {
|
items, ok := st[index].Value().([]stackitem.Item)
|
||||||
case smartcontract.ArrayType:
|
if !ok {
|
||||||
data, ok := st[index].Value.([]smartcontract.Parameter)
|
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 {
|
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))
|
ba[i], err = util.Uint160DecodeBytesLE(val)
|
||||||
for i, account := range data {
|
if err != nil {
|
||||||
ba[i], err = util.Uint160DecodeBytesLE(account.Value.([]byte))
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid stack item type: %s", typ)
|
|
||||||
}
|
}
|
||||||
return ba, nil
|
return ba, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -638,7 +639,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
Account: util.Uint160{1, 2, 3},
|
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{} {
|
result: func(c *Client) interface{} {
|
||||||
bytes, err := hex.DecodeString("262bec084432")
|
bytes, err := hex.DecodeString("262bec084432")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -648,12 +649,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
State: "HALT",
|
State: "HALT",
|
||||||
GasConsumed: 31100000,
|
GasConsumed: 31100000,
|
||||||
Script: "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf",
|
Script: "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf",
|
||||||
Stack: []smartcontract.Parameter{
|
Stack: []stackitem.Item{stackitem.NewByteArray(bytes)},
|
||||||
{
|
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: bytes,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -670,7 +666,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
Account: util.Uint160{1, 2, 3},
|
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{} {
|
result: func(c *Client) interface{} {
|
||||||
bytes, err := hex.DecodeString("4e45503520474153")
|
bytes, err := hex.DecodeString("4e45503520474153")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -680,12 +676,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
State: "HALT",
|
State: "HALT",
|
||||||
GasConsumed: 16100000,
|
GasConsumed: 16100000,
|
||||||
Script: "00046e616d656724058e5e1b6008847cd662728549088a9ee82191",
|
Script: "00046e616d656724058e5e1b6008847cd662728549088a9ee82191",
|
||||||
Stack: []smartcontract.Parameter{
|
Stack: []stackitem.Item{stackitem.NewByteArray(bytes)},
|
||||||
{
|
|
||||||
Type: smartcontract.ByteArrayType,
|
|
||||||
Value: bytes,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -116,9 +116,9 @@ func TestWSClientEvents(t *testing.T) {
|
||||||
var ok bool
|
var ok bool
|
||||||
// Events from RPC server test chain.
|
// Events from RPC server test chain.
|
||||||
var events = []string{
|
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":"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","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":"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","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":"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":"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":[]}`,
|
`{"jsonrpc":"2.0","method":"event_missed","params":[]}`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ type ApplicationLog struct {
|
||||||
//NotificationEvent response wrapper
|
//NotificationEvent response wrapper
|
||||||
type NotificationEvent struct {
|
type NotificationEvent struct {
|
||||||
Contract util.Uint160 `json:"contract"`
|
Contract util.Uint160 `json:"contract"`
|
||||||
|
Name string `json:"eventname"`
|
||||||
Item smartcontract.Parameter `json:"state"`
|
Item smartcontract.Parameter `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,13 +29,10 @@ type NotificationEvent struct {
|
||||||
// result.NotificationEvent.
|
// result.NotificationEvent.
|
||||||
func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent {
|
func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent {
|
||||||
seen := make(map[stackitem.Item]bool)
|
seen := make(map[stackitem.Item]bool)
|
||||||
args := stackitem.NewArray([]stackitem.Item{
|
item := smartcontract.ParameterFromStackItem(event.Item, seen)
|
||||||
stackitem.Make(event.Name),
|
|
||||||
event.Item,
|
|
||||||
})
|
|
||||||
item := smartcontract.ParameterFromStackItem(args, seen)
|
|
||||||
return NotificationEvent{
|
return NotificationEvent{
|
||||||
Contract: event.ScriptHash,
|
Contract: event.ScriptHash,
|
||||||
|
Name: event.Name,
|
||||||
Item: item,
|
Item: item,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,13 +43,17 @@ func NewApplicationLog(appExecRes *state.AppExecResult) ApplicationLog {
|
||||||
for _, e := range appExecRes.Events {
|
for _, e := range appExecRes.Events {
|
||||||
events = append(events, StateEventToResultNotification(e))
|
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{
|
return ApplicationLog{
|
||||||
TxHash: appExecRes.TxHash,
|
TxHash: appExecRes.TxHash,
|
||||||
Trigger: appExecRes.Trigger.String(),
|
Trigger: appExecRes.Trigger.String(),
|
||||||
VMState: appExecRes.VMState.String(),
|
VMState: appExecRes.VMState.String(),
|
||||||
GasConsumed: appExecRes.GasConsumed,
|
GasConsumed: appExecRes.GasConsumed,
|
||||||
Stack: appExecRes.Stack,
|
Stack: st,
|
||||||
Events: events,
|
Events: events,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,76 @@
|
||||||
package result
|
package result
|
||||||
|
|
||||||
import (
|
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
|
// Invoke represents code invocation result and is used by several RPC calls
|
||||||
// that invoke functions, scripts and generic bytecode.
|
// that invoke functions, scripts and generic bytecode.
|
||||||
type Invoke struct {
|
type Invoke struct {
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
GasConsumed int64 `json:"gasconsumed,string"`
|
GasConsumed int64 `json:"gasconsumed,string"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Stack []smartcontract.Parameter `json:"stack"`
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network"
|
"github.com/nspcc-dev/neo-go/pkg/network"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc"
|
"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}
|
d := decimals{Hash: h}
|
||||||
switch item := res.Stack[len(res.Stack)-1]; item.Type {
|
bi, err := res.Stack[len(res.Stack)-1].TryInteger()
|
||||||
case smartcontract.IntegerType:
|
if err != nil {
|
||||||
d.Value = item.Value.(int64)
|
return decimals{}, err
|
||||||
case smartcontract.ByteArrayType:
|
|
||||||
d.Value = bigint.FromBytes(item.Value.([]byte)).Int64()
|
|
||||||
default:
|
|
||||||
return d, errors.New("invalid result: not an integer")
|
|
||||||
}
|
}
|
||||||
|
d.Value = bi.Int64()
|
||||||
if d.Value < 0 {
|
if d.Value < 0 {
|
||||||
return d, errors.New("incorrect result: negative result")
|
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(),
|
State: vm.State().String(),
|
||||||
GasConsumed: vm.GasConsumed(),
|
GasConsumed: vm.GasConsumed(),
|
||||||
Script: hex.EncodeToString(script),
|
Script: hex.EncodeToString(script),
|
||||||
Stack: vm.Estack().ToContractParameters(),
|
Stack: vm.Estack().ToArray(),
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -531,7 +531,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "HALT", res.State)
|
assert.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
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)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "HALT", res.State)
|
assert.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
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)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "HALT", res.State)
|
assert.Equal(t, "HALT", res.State)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
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)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "HALT", res.State)
|
assert.Equal(t, "HALT", res.State)
|
||||||
assert.Equal(t, 1, len(res.Stack))
|
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())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,11 +32,11 @@ var marshalJSONTestCases = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
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},
|
input: Parameter{Type: ByteArrayType},
|
||||||
result: `{"type":"ByteArray","value":null}`,
|
result: `{"type":"ByteString","value":null}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: Parameter{
|
input: Parameter{
|
||||||
|
@ -68,8 +68,8 @@ var marshalJSONTestCases = []struct {
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
result: `{"type":"Array","value":[{"type":"ByteArray","value":"` + hexToBase64("0102") + `"},{"type":"Array","value":[` +
|
result: `{"type":"Array","value":[{"type":"ByteString","value":"` + hexToBase64("0102") + `"},{"type":"Array","value":[` +
|
||||||
`{"type":"ByteArray","value":"` + hexToBase64("030201") + `"},{"type":"ByteArray","value":"` + hexToBase64("070809") + `"}]}]}`,
|
`{"type":"ByteString","value":"` + hexToBase64("030201") + `"},{"type":"ByteString","value":"` + hexToBase64("070809") + `"}]}]}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: Parameter{
|
input: Parameter{
|
||||||
|
@ -185,7 +185,7 @@ var unmarshalJSONTestCases = []struct {
|
||||||
result: Parameter{Type: IntegerType, Value: int64(12345)},
|
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}},
|
result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -288,9 +288,9 @@ var unmarshalJSONTestCases = []struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var unmarshalJSONErrorCases = []string{
|
var unmarshalJSONErrorCases = []string{
|
||||||
`{"type": "ByteArray","value":`, // incorrect JSON
|
`{"type": "ByteString","value":`, // incorrect JSON
|
||||||
`{"type": "ByteArray","value":1}`, // incorrect Value
|
`{"type": "ByteString","value":1}`, // incorrect Value
|
||||||
`{"type": "ByteArray","value":"12^"}`, // incorrect ByteArray value
|
`{"type": "ByteString","value":"12^"}`, // incorrect ByteArray value
|
||||||
`{"type": "String","value":`, // incorrect JSON
|
`{"type": "String","value":`, // incorrect JSON
|
||||||
`{"type": "String","value":1}`, // incorrect Value
|
`{"type": "String","value":1}`, // incorrect Value
|
||||||
`{"type": "Integer","value": "nn"}`, // incorrect Integer value
|
`{"type": "Integer","value": "nn"}`, // incorrect Integer value
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -406,18 +405,24 @@ func (s *Stack) PopSigElements() ([][]byte, error) {
|
||||||
return elems, nil
|
return elems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToContractParameters converts Stack to slice of smartcontract.Parameter.
|
// ToArray converts stack to an array of stackitems with top item being the last.
|
||||||
func (s *Stack) ToContractParameters() []smartcontract.Parameter {
|
func (s *Stack) ToArray() []stackitem.Item {
|
||||||
items := make([]smartcontract.Parameter, 0, s.Len())
|
items := make([]stackitem.Item, 0, s.len)
|
||||||
s.IterBack(func(e *Element) {
|
s.IterBack(func(e *Element) {
|
||||||
// Each item is independent.
|
items = append(items, e.Item())
|
||||||
seen := make(map[stackitem.Item]bool)
|
|
||||||
items = append(items, smartcontract.ParameterFromStackItem(e.value, seen))
|
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements JSON marshalling interface.
|
// MarshalJSON implements JSON marshalling interface.
|
||||||
func (s *Stack) MarshalJSON() ([]byte, error) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,6 +237,22 @@ func TestPushVal(t *testing.T) {
|
||||||
assert.IsType(t, elem.value, &stackitem.Array{})
|
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) {
|
func TestSwapElemValues(t *testing.T) {
|
||||||
s := NewStack("test")
|
s := NewStack("test")
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ func (i *Struct) Bool() bool { return true }
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i *Struct) TryBytes() ([]byte, error) {
|
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.
|
// TryInteger implements Item interface.
|
||||||
|
@ -301,7 +301,7 @@ func (i Null) Bool() bool { return false }
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i Null) TryBytes() ([]byte, error) {
|
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.
|
// TryInteger implements Item interface.
|
||||||
|
@ -497,7 +497,7 @@ func (i *ByteArray) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ByteArray) String() string {
|
func (i *ByteArray) String() string {
|
||||||
return "ByteArray"
|
return "ByteString"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool implements Item interface.
|
// Bool implements Item interface.
|
||||||
|
@ -606,7 +606,7 @@ func (i *Array) Bool() bool { return true }
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i *Array) TryBytes() ([]byte, error) {
|
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.
|
// TryInteger implements Item interface.
|
||||||
|
@ -696,7 +696,7 @@ func (i *Map) Bool() bool { return true }
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i *Map) TryBytes() ([]byte, error) {
|
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.
|
// TryInteger implements Item interface.
|
||||||
|
@ -812,7 +812,7 @@ func (i *Interop) Bool() bool { return true }
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i *Interop) TryBytes() ([]byte, error) {
|
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.
|
// TryInteger implements Item interface.
|
||||||
|
@ -893,7 +893,7 @@ func (p *Pointer) Bool() bool {
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (p *Pointer) TryBytes() ([]byte, error) {
|
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.
|
// TryInteger implements Item interface.
|
||||||
|
|
|
@ -112,7 +112,7 @@ var stringerTestCases = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: NewByteArray([]byte{}),
|
input: NewByteArray([]byte{}),
|
||||||
result: "ByteArray",
|
result: "ByteString",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: NewArray([]Item{}),
|
input: NewArray([]Item{}),
|
||||||
|
|
|
@ -208,3 +208,171 @@ func (d *decoder) decodeMap() (*Map, error) {
|
||||||
m.Add(NewByteArray([]byte(k)), val)
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package stackitem
|
package stackitem
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
// Type represents type of the stack item.
|
// Type represents type of the stack item.
|
||||||
type Type byte
|
type Type byte
|
||||||
|
|
||||||
|
@ -29,7 +31,7 @@ func (t Type) String() string {
|
||||||
case IntegerT:
|
case IntegerT:
|
||||||
return "Integer"
|
return "Integer"
|
||||||
case ByteArrayT:
|
case ByteArrayT:
|
||||||
return "ByteArray"
|
return "ByteString"
|
||||||
case BufferT:
|
case BufferT:
|
||||||
return "Buffer"
|
return "Buffer"
|
||||||
case ArrayT:
|
case ArrayT:
|
||||||
|
@ -54,3 +56,31 @@ func (t Type) IsValid() bool {
|
||||||
return false
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
pkg/vm/stackitem/type_test.go
Normal file
16
pkg/vm/stackitem/type_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -321,7 +321,7 @@ func (v *VM) Stack(n string) string {
|
||||||
if n == "estack" {
|
if n == "estack" {
|
||||||
s = v.estack
|
s = v.estack
|
||||||
}
|
}
|
||||||
b, _ := json.MarshalIndent(s.ToContractParameters(), "", " ")
|
b, _ := json.MarshalIndent(s, "", " ")
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue