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

Open
opened 2025-12-28 17:15:11 +00:00 by sami · 5 comments
Owner

Originally created by @cthulhu-rider on GitHub (Feb 22, 2023).

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/neo-go@v0.101.0/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)
}
Originally created by @cthulhu-rider on GitHub (Feb 22, 2023). ## 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/neo-go@v0.101.0/pkg/neotest#CompileFile ```go 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. ```go func AnyFreeName(key, value []byte) { storage.Put(storage.GetContext(), key, value) } ```
Author
Owner

@roman-khimov commented on GitHub (Feb 22, 2023):

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 commented on GitHub (Feb 22, 2023): 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.
Author
Owner

@cthulhu-rider commented on GitHub (Feb 22, 2023):

think about some specific hooks for tests

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

@cthulhu-rider commented on GitHub (Feb 22, 2023): > think about some specific hooks for tests Ofc test hooks in core lib would be much better than contract surgery.
Author
Owner

@cthulhu-rider commented on GitHub (Mar 1, 2023):

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 commented on GitHub (Mar 1, 2023): Here's a workaround suggested by @roman-khimov for the case here when you just want to add contract with some data in advance. ```go 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.
Author
Owner

@roman-khimov commented on GitHub (Mar 1, 2023):

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 commented on GitHub (Mar 1, 2023): 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.
Author
Owner

@cthulhu-rider commented on GitHub (Mar 1, 2023):

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 commented on GitHub (Mar 1, 2023): > 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.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nspcc-dev/neo-go#1086
No description provided.