neo-go icon indicating copy to clipboard operation
neo-go copied to clipboard

neotest: Provide the ability to write any values ​​in the tested contract storage

Open cthulhu-rider opened this issue 2 years ago • 5 comments

Context

I try to test migration of contracts' storage items using framework provided by neotest package. Sometimes contract stores items within its methods, so technically I could just call exact method. The inconvenience is that such elements are stored in the contract as a result of API methods that are not always easy to call within the test due to the need to follow business logic. At the same time, I did not find other ways.

Proposal

Add functionality to neotest package which allows to directly write key-value item into the contract storage.

Possible solution

The least affecting approach I thought about is to add analogue of https://pkg.go.dev/github.com/nspcc-dev/[email protected]/pkg/neotest#CompileFile

func CompileContractWithDirectStorageAccess(/*same as CompileFile*/) (writeMethod string)

which, after reading the source code of the contract (but before compiling), adds direct-write method for the life of the test contract. You can call this method later like any other one.

func AnyFreeName(key, value []byte) {
  storage.Put(storage.GetContext(), key, value)
}

cthulhu-rider avatar Feb 22 '23 11:02 cthulhu-rider

Changing contract on the fly is not a good idea, we're supposed to compile/deploy them exactly. Currently it's possible to use custom stores for chain.NewSingleWithCustomConfigAndStore() and chain.NewMultiWithCustomConfigAndStore where you have a complete storage API (seems like you're already doing it). But as you know underlying store may be a bit outdated compared to the internal Blockchain memory cache. Blockchain is specifically designed to never expose its memcache/DAO, but we may think about some specific hooks for tests, that I think will be safer and more appropriate way to handle this problem.

roman-khimov avatar Feb 22 '23 12:02 roman-khimov

think about some specific hooks for tests

Ofc test hooks in core lib would be much better than contract surgery.

cthulhu-rider avatar Feb 22 '23 12:02 cthulhu-rider

Here's a workaround suggested by @roman-khimov for the case here when you just want to add contract with some data in advance.

	lowLevelStore := storage.NewMemoryStore()
	_dao := dao.NewSimple(lowLevelStore, false, true)

	nativeContracts := native.NewContracts(config.ProtocolConfiguration{})

	err := nativeContracts.Management.InitializeCache(_dao)
	require.NoError(tb, err)

	err = native.PutContractState(_dao, &_state)
	require.NoError(tb, err)

	// store values in lowLevelStore

	_, err = _dao.PersistSync()
	_, err = cachedStore.PersistSync()

	// init blockchain
	useDefaultConfig := func(*config.Blockchain) {}
	blockChain, alphabetSigner := chain.NewSingleWithCustomConfigAndStore(tb, useDefaultConfig, lowLevelStore, true)

However, it is worth noting that after that contract invocations fail with contract not found exceptions. https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/core#Blockchain.GetContractState returns nil, but https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/core#Blockchain.GetContractScriptHash works.

cthulhu-rider avatar Mar 01 '23 11:03 cthulhu-rider

Please try

blockChain, _ := chain.NewSingleWithCustomConfigAndStore(tb, useDefaultConfig, lowLevelStore, true)
blockChain.Close()
blockChain, alphabetSigner := chain.NewSingleWithCustomConfigAndStore(tb, useDefaultConfig, lowLevelStore, true)

Yeah, it's weird. But otherwise management contract's cache is not really initialized, because we're starting with an (almost) clean DB, it's inited for the genesis and that's it.

roman-khimov avatar Mar 01 '23 14:03 roman-khimov

Please try

It really works. The only thing I also needed to do is to make no-op Close method of the storage.Store passed on the first init. Without this, blockChain.Close reset underlying maps.

cthulhu-rider avatar Mar 01 '23 15:03 cthulhu-rider