forked from TrueCloudLab/neoneo-go
7a2d37cf7e
closes #1025 Now we put on stack stackitem.Array instead of Interop, so we're able to use all available block properties without extra interop getters. Removed Neo.Blockchain.GetBlock interop as we don't need it anymore.
382 lines
11 KiB
Go
382 lines
11 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
"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/internal/random"
|
|
"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"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
/* Missing tests:
|
|
* TestTxGetWitnesses
|
|
* TestBcGetAccount
|
|
* TestAccountGetBalance
|
|
* TestAccountIsStandard
|
|
* TestCreateContractStateFromVM
|
|
* TestContractCreate
|
|
* TestContractMigrate
|
|
* TestRuntimeSerialize
|
|
* TestRuntimeDeserialize
|
|
*/
|
|
|
|
func TestGetTrigger(t *testing.T) {
|
|
v, _, context, chain := createVMAndPushBlock(t)
|
|
defer chain.Close()
|
|
require.NoError(t, runtimeGetTrigger(context, v))
|
|
}
|
|
|
|
func TestStorageFind(t *testing.T) {
|
|
v, contractState, context, chain := createVMAndContractState(t)
|
|
defer chain.Close()
|
|
|
|
skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}, {0x01, 0x01}}
|
|
items := []*state.StorageItem{
|
|
{
|
|
Value: []byte{0x01, 0x02, 0x03, 0x04},
|
|
},
|
|
{
|
|
Value: []byte{0x04, 0x03, 0x02, 0x01},
|
|
},
|
|
{
|
|
Value: []byte{0x03, 0x04, 0x05, 0x06},
|
|
},
|
|
}
|
|
|
|
require.NoError(t, context.DAO.PutContractState(contractState))
|
|
|
|
scriptHash := contractState.ScriptHash()
|
|
|
|
for i := range skeys {
|
|
err := context.DAO.PutStorageItem(scriptHash, skeys[i], items[i])
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Run("normal invocation", func(t *testing.T) {
|
|
v.Estack().PushVal([]byte{0x01})
|
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: scriptHash}))
|
|
|
|
err := storageFind(context, v)
|
|
require.NoError(t, err)
|
|
|
|
var iter *stackitem.Interop
|
|
require.NotPanics(t, func() { iter = v.Estack().Top().Interop() })
|
|
|
|
require.NoError(t, enumerator.Next(context, v))
|
|
require.True(t, v.Estack().Pop().Bool())
|
|
|
|
v.Estack().PushVal(iter)
|
|
require.NoError(t, iterator.Key(context, v))
|
|
require.Equal(t, []byte{0x01, 0x01}, v.Estack().Pop().Bytes())
|
|
|
|
v.Estack().PushVal(iter)
|
|
require.NoError(t, enumerator.Value(context, v))
|
|
require.Equal(t, []byte{0x03, 0x04, 0x05, 0x06}, v.Estack().Pop().Bytes())
|
|
|
|
v.Estack().PushVal(iter)
|
|
require.NoError(t, enumerator.Next(context, v))
|
|
require.True(t, v.Estack().Pop().Bool())
|
|
|
|
v.Estack().PushVal(iter)
|
|
require.NoError(t, iterator.Key(context, v))
|
|
require.Equal(t, []byte{0x01, 0x02}, v.Estack().Pop().Bytes())
|
|
|
|
v.Estack().PushVal(iter)
|
|
require.NoError(t, enumerator.Value(context, v))
|
|
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, v.Estack().Pop().Bytes())
|
|
|
|
v.Estack().PushVal(iter)
|
|
require.NoError(t, enumerator.Next(context, v))
|
|
require.False(t, v.Estack().Pop().Bool())
|
|
})
|
|
|
|
t.Run("normal invocation, empty result", func(t *testing.T) {
|
|
v.Estack().PushVal([]byte{0x03})
|
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: scriptHash}))
|
|
|
|
err := storageFind(context, v)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, enumerator.Next(context, v))
|
|
require.False(t, v.Estack().Pop().Bool())
|
|
})
|
|
|
|
t.Run("invalid type for StorageContext", func(t *testing.T) {
|
|
v.Estack().PushVal([]byte{0x01})
|
|
v.Estack().PushVal(stackitem.NewInterop(nil))
|
|
|
|
require.Error(t, storageFind(context, v))
|
|
})
|
|
|
|
t.Run("invalid script hash", func(t *testing.T) {
|
|
invalidHash := scriptHash
|
|
invalidHash[0] = ^invalidHash[0]
|
|
|
|
v.Estack().PushVal([]byte{0x01})
|
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: invalidHash}))
|
|
|
|
require.Error(t, storageFind(context, v))
|
|
})
|
|
}
|
|
|
|
func TestHeaderGetVersion(t *testing.T) {
|
|
v, block, context, chain := createVMAndPushBlock(t)
|
|
defer chain.Close()
|
|
|
|
err := headerGetVersion(context, v)
|
|
require.NoError(t, err)
|
|
value := v.Estack().Pop().Value().(*big.Int)
|
|
require.Equal(t, uint64(block.Version), value.Uint64())
|
|
}
|
|
|
|
func TestHeaderGetVersion_Negative(t *testing.T) {
|
|
v := vm.New()
|
|
block := newDumbBlock()
|
|
chain := newTestChain(t)
|
|
defer chain.Close()
|
|
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), block, nil)
|
|
v.Estack().PushVal(stackitem.NewBool(false))
|
|
|
|
err := headerGetVersion(context, v)
|
|
require.Errorf(t, err, "value is not a header or block")
|
|
}
|
|
|
|
func TestHeaderGetMerkleRoot(t *testing.T) {
|
|
v, block, context, chain := createVMAndPushBlock(t)
|
|
defer chain.Close()
|
|
|
|
err := headerGetMerkleRoot(context, v)
|
|
require.NoError(t, err)
|
|
value := v.Estack().Pop().Value()
|
|
require.Equal(t, block.MerkleRoot.BytesBE(), value)
|
|
}
|
|
|
|
func TestHeaderGetNextConsensus(t *testing.T) {
|
|
v, block, context, chain := createVMAndPushBlock(t)
|
|
defer chain.Close()
|
|
|
|
err := headerGetNextConsensus(context, v)
|
|
require.NoError(t, err)
|
|
value := v.Estack().Pop().Value()
|
|
require.Equal(t, block.NextConsensus.BytesBE(), value)
|
|
}
|
|
|
|
func TestWitnessGetVerificationScript(t *testing.T) {
|
|
v := vm.New()
|
|
script := []byte{byte(opcode.PUSHM1), byte(opcode.RET)}
|
|
witness := transaction.Witness{InvocationScript: nil, VerificationScript: script}
|
|
|
|
chain := newTestChain(t)
|
|
defer chain.Close()
|
|
|
|
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil)
|
|
v.Estack().PushVal(stackitem.NewInterop(&witness))
|
|
err := witnessGetVerificationScript(context, v)
|
|
require.NoError(t, err)
|
|
value := v.Estack().Pop().Value().([]byte)
|
|
require.Equal(t, witness.VerificationScript, value)
|
|
}
|
|
|
|
func TestECDSAVerify(t *testing.T) {
|
|
priv, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
chain := newTestChain(t)
|
|
defer chain.Close()
|
|
|
|
ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil)
|
|
runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) {
|
|
v := vm.New()
|
|
for i := range args {
|
|
v.Estack().PushVal(args[i])
|
|
}
|
|
|
|
var err error
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("panic: %v", r)
|
|
}
|
|
}()
|
|
err = crypto.ECDSAVerify(ic, v)
|
|
}()
|
|
|
|
if isErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, v.Estack().Len())
|
|
require.Equal(t, result, v.Estack().Pop().Value().(bool))
|
|
}
|
|
|
|
msg := []byte("test message")
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
sign := priv.Sign(msg)
|
|
runCase(t, false, true, sign, priv.PublicKey().Bytes(), msg)
|
|
})
|
|
|
|
t.Run("signed interop item", func(t *testing.T) {
|
|
tx := transaction.New([]byte{0, 1, 2}, 1)
|
|
msg := tx.GetSignedPart()
|
|
sign := priv.Sign(msg)
|
|
runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.NewInterop(tx))
|
|
})
|
|
|
|
t.Run("signed script container", func(t *testing.T) {
|
|
tx := transaction.New([]byte{0, 1, 2}, 1)
|
|
msg := tx.GetSignedPart()
|
|
sign := priv.Sign(msg)
|
|
ic.Container = tx
|
|
runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.Null{})
|
|
})
|
|
|
|
t.Run("missing arguments", func(t *testing.T) {
|
|
runCase(t, true, false)
|
|
sign := priv.Sign(msg)
|
|
runCase(t, true, false, sign)
|
|
runCase(t, true, false, sign, priv.PublicKey().Bytes())
|
|
})
|
|
|
|
t.Run("invalid signature", func(t *testing.T) {
|
|
sign := priv.Sign(msg)
|
|
sign[0] = ^sign[0]
|
|
runCase(t, false, false, sign, priv.PublicKey().Bytes(), msg)
|
|
})
|
|
|
|
t.Run("invalid public key", func(t *testing.T) {
|
|
sign := priv.Sign(msg)
|
|
pub := priv.PublicKey().Bytes()
|
|
pub = pub[10:]
|
|
runCase(t, true, false, sign, pub, msg)
|
|
})
|
|
}
|
|
|
|
func TestAccountGetScriptHash(t *testing.T) {
|
|
v, accState, context, chain := createVMAndAccState(t)
|
|
defer chain.Close()
|
|
v.Estack().PushVal(stackitem.NewInterop(accState))
|
|
|
|
err := accountGetScriptHash(context, v)
|
|
require.NoError(t, err)
|
|
hash := v.Estack().Pop().Value()
|
|
require.Equal(t, accState.ScriptHash.BytesBE(), hash)
|
|
}
|
|
|
|
func TestContractGetScript(t *testing.T) {
|
|
v, contractState, context, chain := createVMAndContractState(t)
|
|
defer chain.Close()
|
|
v.Estack().PushVal(stackitem.NewInterop(contractState))
|
|
|
|
err := contractGetScript(context, v)
|
|
require.NoError(t, err)
|
|
script := v.Estack().Pop().Value()
|
|
require.Equal(t, contractState.Script, script)
|
|
}
|
|
|
|
func TestContractIsPayable(t *testing.T) {
|
|
v, contractState, context, chain := createVMAndContractState(t)
|
|
defer chain.Close()
|
|
v.Estack().PushVal(stackitem.NewInterop(contractState))
|
|
|
|
err := contractIsPayable(context, v)
|
|
require.NoError(t, err)
|
|
isPayable := v.Estack().Pop().Value()
|
|
require.Equal(t, contractState.IsPayable(), isPayable)
|
|
}
|
|
|
|
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
|
|
|
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
|
|
v := vm.New()
|
|
chain := newTestChain(t)
|
|
context := chain.newInteropContext(trigger.Application,
|
|
dao.NewSimple(storage.NewMemoryStore()), nil, nil)
|
|
return v, context, chain
|
|
}
|
|
|
|
func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
|
|
v, block, context, chain := createVMAndBlock(t)
|
|
v.Estack().PushVal(stackitem.NewInterop(block))
|
|
return v, block, context, chain
|
|
}
|
|
|
|
func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
|
|
v := vm.New()
|
|
block := newDumbBlock()
|
|
chain := newTestChain(t)
|
|
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), block, nil)
|
|
return v, block, context, chain
|
|
}
|
|
|
|
func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
|
v, tx, context, chain := createVMAndTX(t)
|
|
v.Estack().PushVal(stackitem.NewInterop(tx))
|
|
return v, tx, context, chain
|
|
}
|
|
|
|
func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
|
|
v := vm.New()
|
|
contractState := &state.Contract{
|
|
Script: []byte("testscript"),
|
|
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
|
ReturnType: smartcontract.ArrayType,
|
|
Properties: smartcontract.HasStorage,
|
|
Name: random.String(10),
|
|
CodeVersion: random.String(10),
|
|
Author: random.String(10),
|
|
Email: random.String(10),
|
|
Description: random.String(10),
|
|
}
|
|
|
|
chain := newTestChain(t)
|
|
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil)
|
|
return v, contractState, context, chain
|
|
}
|
|
|
|
func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interop.Context, *Blockchain) {
|
|
v := vm.New()
|
|
rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302"
|
|
hash, err := util.Uint160DecodeStringBE(rawHash)
|
|
accountState := state.NewAccount(hash)
|
|
|
|
require.NoError(t, err)
|
|
chain := newTestChain(t)
|
|
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil)
|
|
return v, accountState, context, chain
|
|
}
|
|
|
|
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
|
v := vm.New()
|
|
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
|
tx := transaction.New(script, 0)
|
|
|
|
bytes := make([]byte, 1)
|
|
attributes := append(tx.Attributes, transaction.Attribute{
|
|
Usage: transaction.Description,
|
|
Data: bytes,
|
|
})
|
|
|
|
tx.Attributes = attributes
|
|
chain := newTestChain(t)
|
|
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, tx)
|
|
return v, tx, context, chain
|
|
}
|