diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index 72d8f9cd0..501254ae9 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -3,6 +3,7 @@ package neotest import ( "encoding/json" "fmt" + "math/big" "strings" "testing" @@ -62,6 +63,14 @@ func (e *Executor) NativeHash(t *testing.T, name string) util.Uint160 { return h } +// NativeID returns native contract ID by name. +func (e *Executor) NativeID(t *testing.T, name string) int32 { + h := e.NativeHash(t, name) + cs := e.Chain.GetContractState(h) + require.NotNil(t, cs) + return cs.ID +} + // NewUnsignedTx creates new unsigned transaction which invokes method of contract with hash. func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction { w := io.NewBufBinWriter() @@ -202,6 +211,12 @@ func (e *Executor) CheckTxNotificationEvent(t *testing.T, h util.Uint256, index require.Equal(t, expected, aer[0].Events[index]) } +// CheckGASBalance ensures that provided account owns specified amount of GAS. +func (e *Executor) CheckGASBalance(t *testing.T, acc util.Uint160, expected *big.Int) { + actual := e.Chain.GetUtilityTokenBalance(acc) + require.Equal(t, expected, actual, fmt.Errorf("invalid GAS balance: expected %s, got %s", expected.String(), actual.String())) +} + // NewDeployTx returns new deployment tx for contract signed by committee. func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction { rawManifest, err := json.Marshal(c.Manifest) @@ -277,6 +292,13 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *b return b } +// GenerateNewBlocks adds specified number of empty blocks to the chain. +func (e *Executor) GenerateNewBlocks(t *testing.T, count int) { + for i := 0; i < count; i++ { + e.AddNewBlock(t) + } +} + // SignBlock add validators signature to b. func (e *Executor) SignBlock(b *block.Block) *block.Block { invoc := e.Validator.SignHashable(uint32(e.Chain.GetConfig().Magic), b) @@ -316,3 +338,27 @@ func TestInvoke(bc blockchainer.Blockchainer, tx *transaction.Transaction) (*vm. err = v.Run() return v, err } + +// GetTransaction returns transaction and its height by the specified hash. +func (e *Executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Transaction, uint32) { + tx, height, err := e.Chain.GetTransaction(h) + require.NoError(t, err) + return tx, height +} + +// GetBlockByIndex returns block by the specified index. +func (e *Executor) GetBlockByIndex(t *testing.T, idx int) *block.Block { + h := e.Chain.GetHeaderHash(idx) + require.NotEmpty(t, h) + b, err := e.Chain.GetBlock(h) + require.NoError(t, err) + return b +} + +// GetTxExecResult returns application execution results for the specified transaction. +func (e *Executor) GetTxExecResult(t *testing.T, h util.Uint256) *state.AppExecResult { + aer, err := e.Chain.GetAppExecResults(h, trigger.Application) + require.NoError(t, err) + require.Equal(t, 1, len(aer)) + return &aer[0] +} diff --git a/pkg/neotest/chain/chain.go b/pkg/neotest/chain/chain.go index 7eaa5bd9c..db45ba75f 100644 --- a/pkg/neotest/chain/chain.go +++ b/pkg/neotest/chain/chain.go @@ -108,14 +108,24 @@ func init() { // NewSingle creates new blockchain instance with a single validator and // setups cleanup functions. func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) { + return NewSingleWithCustomConfig(t, nil) +} + +// NewSingleWithCustomConfig creates new blockchain instance with custom protocol +// configuration and a single validator. It also setups cleanup functions. +func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, + MaxTraceableBlocks: 1000, // We don't need a lot of traceable blocks for tests. SecondsPerBlock: 1, StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())}, ValidatorsCount: 1, VerifyBlocks: true, VerifyTransactions: true, } + if f != nil { + f(&protoCfg) + } st := storage.NewMemoryStore() log := zaptest.NewLogger(t) @@ -129,6 +139,13 @@ func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) { // NewMulti creates new blockchain instance with 4 validators and 6 committee members. // Second return value is for validator signer, third -- for committee. func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) { + return NewMultiWithCustomConfig(t, nil) +} + +// NewMultiWithCustomConfig creates new blockchain instance with custom protocol +// configuration, 4 validators and 6 committee members. Second return value is +// for validator signer, third -- for committee. +func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, SecondsPerBlock: 1, @@ -137,6 +154,9 @@ func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) { VerifyBlocks: true, VerifyTransactions: true, } + if f != nil { + f(&protoCfg) + } st := storage.NewMemoryStore() log := zaptest.NewLogger(t) diff --git a/pkg/neotest/client.go b/pkg/neotest/client.go index 646f58143..89ac48cf7 100644 --- a/pkg/neotest/client.go +++ b/pkg/neotest/client.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" ) // ContractInvoker is a client for specific contract. @@ -65,6 +66,20 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string return tx.Hash() } +// InvokeAndCheck invokes method with args, persists transaction and checks the result +// using provided function. Returns transaction hash. +func (c *ContractInvoker) InvokeAndCheck(t *testing.T, checkResult func(t *testing.T, stack []stackitem.Item), method string, args ...interface{}) util.Uint256 { + tx := c.PrepareInvoke(t, method, args...) + c.AddNewBlock(t, tx) + aer, err := c.Chain.GetAppExecResults(tx.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) + if checkResult != nil { + checkResult(t, aer[0].Stack) + } + return tx.Hash() +} + // InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction. func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 { tx := c.PrepareInvokeNoSign(t, method, args...)