diff --git a/docs/compiler.md b/docs/compiler.md index 4a0760b51..9c60bb7ee 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -22,8 +22,7 @@ a dialect of Go rather than a complete port of the language: * `defer` and `recover` are supported except for cases where panic occurs in `return` statement, because this complicates implementation and imposes runtime overhead for all contracts. This can easily be mitigated by first storing values - in variables and returning the result. `defer` can't be used in - conditional code (#2293). + in variables and returning the result. * lambdas are supported, but closures are not. * maps are supported, but valid map keys are booleans, integers and strings with length <= 64 diff --git a/examples/README.md b/examples/README.md index c24eff814..e548a7f20 100644 --- a/examples/README.md +++ b/examples/README.md @@ -27,13 +27,12 @@ See the table below for the detailed examples description. | [iterator](iterator) | This example describes a way to work with NEO iterators. Please, refer to the `iterator` [package documentation](../pkg/interop/iterator/iterator.go) for details. | | [nft-d](nft-d) | NEP-11 divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) for details. | | [nft-nd](nft-nd) | NEP-11 non-divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) for details. | -| [nft-nd-nns](nft-nd-nns) | Neo Name Service contract which is NEP-11 non-divisible NFT. The contract implements methods for Neo domain name system managing such as domains registration/transferring, records addition and names resolving. | +| [nft-nd-nns](nft-nd-nns) | Neo Name Service contract which is NEP-11 non-divisible NFT. The contract implements methods for Neo domain name system managing such as domains registration/transferring, records addition and names resolving. The package also contains tests implemented with [neotest](https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/neotest). | | [oracle](oracle) | Oracle demo contract exposing two methods that you can use to process URLs. It uses oracle native contract, see [interop package documentation](../pkg/interop/native/oracle/oracle.go) also. | | [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). | | [storage](storage) | The contract implements API for basic operations with a contract storage. It shows hos to use `storage` interop package. See the `storage` [package documentation](../pkg/interop/storage/storage.go). | | [timer](timer) | The idea of the contract is to count `tick` method invocations and destroy itself after the third invocation. It shows how to use `contract.Call` interop function to call, update (migrate) and destroy the contract. Please, refer to the `contract.Call` [function documentation](../pkg/interop/contract/contract.go) | | [token](token) | This contract implements NEP-17 token standard (like NEO and GAS tokens) with all required methods and operations. See the NEP-17 token standard [specification](https://github.com/neo-project/proposals/pull/126) for details. | -| [token-sale](token-sale) | The contract represents a token with `allowance`. It means that the token owner should approve token withdrawing before the transfer. The contract demonstrates how interop packages can be combined to work together. | ## Compile diff --git a/examples/iterator/iterator.go b/examples/iterator/iterator.go index 03ffccee9..fe53881b8 100644 --- a/examples/iterator/iterator.go +++ b/examples/iterator/iterator.go @@ -6,11 +6,28 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) -// NotifyKeysAndValues sends notification with `foo` storage keys and values +// _deploy primes contract's storage with some data to be used later. +func _deploy(_ interface{}, _ bool) { + ctx := storage.GetContext() // RW context. + storage.Put(ctx, "foo1", "1") + storage.Put(ctx, "foo2", "2") + storage.Put(ctx, "foo3", "3") +} + +// NotifyKeysAndValues sends notification with `foo` storage keys and values. func NotifyKeysAndValues() bool { - iter := storage.Find(storage.GetContext(), []byte("foo"), storage.None) + iter := storage.Find(storage.GetReadOnlyContext(), []byte("foo"), storage.None) for iterator.Next(iter) { - runtime.Notify("found storage key-value pair", iterator.Value(iter)) + runtime.Notify("Key-Value", iterator.Value(iter)) + } + return true +} + +// NotifyValues sends notification with `foo` storage values. +func NotifyValues() bool { + iter := storage.Find(storage.GetReadOnlyContext(), []byte("foo"), storage.ValuesOnly) + for iterator.Next(iter) { + runtime.Notify("Value", iterator.Value(iter)) } return true } diff --git a/examples/iterator/iterator.yml b/examples/iterator/iterator.yml index 1c6477104..61170428c 100644 --- a/examples/iterator/iterator.yml +++ b/examples/iterator/iterator.yml @@ -2,7 +2,11 @@ name: "Iterator example" sourceurl: https://github.com/nspcc-dev/neo-go/ supportedstandards: [] events: - - name: found storage key-value pair + - name: Key-Value + parameters: + - name: value + type: Any + - name: Value parameters: - name: value type: Any diff --git a/examples/nft-d/nft.go b/examples/nft-d/nft.go index ed238f528..6257adce5 100644 --- a/examples/nft-d/nft.go +++ b/examples/nft-d/nft.go @@ -351,6 +351,12 @@ func removeOwner(ctx storage.Context, token []byte, holder interop.Hash160) { // this method directly, instead it's called by GAS contract when you transfer // GAS from your address to the address of this NFT contract. func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { + defer func() { + if r := recover(); r != nil { + runtime.Log(r.(string)) + util.Abort() + } + }() if string(runtime.GetCallingScriptHash()) != gas.Hash { panic("only GAS is accepted") } diff --git a/examples/nft-nd-nns/tests/nonnative_name_service_test.go b/examples/nft-nd-nns/nns_test.go similarity index 99% rename from examples/nft-nd-nns/tests/nonnative_name_service_test.go rename to examples/nft-nd-nns/nns_test.go index ecd602786..45219a0e8 100644 --- a/examples/nft-nd-nns/tests/nonnative_name_service_test.go +++ b/examples/nft-nd-nns/nns_test.go @@ -1,4 +1,4 @@ -package tests +package nns_test import ( "strings" @@ -17,7 +17,7 @@ import ( func newNSClient(t *testing.T) *neotest.ContractInvoker { bc, acc := chain.NewSingle(t) e := neotest.NewExecutor(t, bc, acc, acc) - c := neotest.CompileFile(t, e.CommitteeHash, "..", "../nns.yml") + c := neotest.CompileFile(t, e.CommitteeHash, ".", "nns.yml") e.DeployContract(t, c, nil) return e.CommitteeInvoker(c.Hash) diff --git a/examples/nft-nd/nft.go b/examples/nft-nd/nft.go index d54443802..b8f8a7faf 100644 --- a/examples/nft-nd/nft.go +++ b/examples/nft-nd/nft.go @@ -210,6 +210,12 @@ func postTransfer(from interop.Hash160, to interop.Hash160, token []byte, data i // this method directly, instead it's called by GAS contract when you transfer // GAS from your address to the address of this NFT contract. func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { + defer func() { + if r := recover(); r != nil { + runtime.Log(r.(string)) + util.Abort() + } + }() if string(runtime.GetCallingScriptHash()) != gas.Hash { panic("only GAS is accepted") } diff --git a/examples/runtime/runtime.go b/examples/runtime/runtime.go index 5acb63f0c..3536bc1ea 100644 --- a/examples/runtime/runtime.go +++ b/examples/runtime/runtime.go @@ -1,55 +1,81 @@ package runtimecontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/util" ) var ( // Check if the invoker of the contract is the specified owner - owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB") - trigger byte + owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB") ) -// init initializes trigger before any other contract method is called +// init is transformed into _initialize method that is called whenever contract +// is being loaded (so you'll see this log entry with every invocation). func init() { - trigger = runtime.GetTrigger() + // No events and logging allowed in verification context. + if runtime.GetTrigger() != runtime.Verification { + runtime.Log("init called") + } } +// _deploy is called after contract deployment or update, it'll be called +// in deployment transaction and if call update method of this contract. func _deploy(_ interface{}, isUpdate bool) { if isUpdate { - Log("_deploy method called before contract update") + Log("_deploy method called after contract update") return } - Log("_deploy method called before contract creation") + Log("_deploy method called after contract creation") } -// CheckWitness checks owner's witness +// CheckWitness checks owner's witness. It returns true if invoked by the owner +// and false otherwise. func CheckWitness() bool { - // Log owner upon Verification trigger - if trigger != runtime.Verification { - return false - } if runtime.CheckWitness(owner) { runtime.Log("Verified Owner") + return true } - return true + return false } -// Log logs given message -func Log(message string) bool { - if trigger != runtime.Application { - return false - } +// Log logs given message. +func Log(message string) { runtime.Log(message) - return true } -// Notify notifies about given message -func Notify(event interface{}) bool { - if trigger != runtime.Application { +// Notify emits an event with the specified data. +func Notify(event interface{}) { + runtime.Notify("Event", event) +} + +// Verify method is used when contract is being used as a signer of transaction, +// it can have parameters (that then need to be present in invocation script) +// and it returns simple pass/fail result. This implementation just checks for +// owner's signature presence. +func Verify() bool { + // Technically this restriction is not needed, but you can see the difference + // between invokefunction and invokecontractverify RPC methods with it. + if runtime.GetTrigger() != runtime.Verification { return false } - runtime.Notify("Event", event) - return true + return CheckWitness() +} + +// Destroy destroys the contract, only owner can do that. +func Destroy() { + if !Verify() { + panic("only owner can destroy") + } + management.Destroy() +} + +// Update updates the contract, only owner can do that. _deploy will be called +// after update. +func Update(nef, manifest []byte) { + if !Verify() { + panic("only owner can update") + } + management.Update(nef, manifest) } diff --git a/examples/runtime/runtime.yml b/examples/runtime/runtime.yml index b152cbc0a..0ed32855c 100644 --- a/examples/runtime/runtime.yml +++ b/examples/runtime/runtime.yml @@ -6,3 +6,6 @@ events: parameters: - name: event type: Any +permissions: + - hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd + methods: ["update", "destroy"] diff --git a/examples/token-sale/go.mod b/examples/token-sale/go.mod deleted file mode 100644 index 58e2026c0..000000000 --- a/examples/token-sale/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/nspcc-dev/neo-go/examples/token-sale - -go 1.16 - -require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220321144137-d5a9af5860af diff --git a/examples/token-sale/go.sum b/examples/token-sale/go.sum deleted file mode 100644 index d6c936018..000000000 --- a/examples/token-sale/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220321144137-d5a9af5860af h1:QO3pU/jSYyX3EHBX8BPO01oRkVhGBXPrQaQEhn+4fv8= -github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220321144137-d5a9af5860af/go.mod h1:QBE0I30F2kOAISNpT5oks82yF4wkkUq3SCfI3Hqgx/Y= diff --git a/examples/token-sale/token_sale.go b/examples/token-sale/token_sale.go deleted file mode 100644 index 8593e9a04..000000000 --- a/examples/token-sale/token_sale.go +++ /dev/null @@ -1,279 +0,0 @@ -package tokensale - -import ( - "github.com/nspcc-dev/neo-go/pkg/interop" - "github.com/nspcc-dev/neo-go/pkg/interop/runtime" - "github.com/nspcc-dev/neo-go/pkg/interop/storage" - "github.com/nspcc-dev/neo-go/pkg/interop/util" -) - -const ( - decimals = 8 - multiplier = decimals * 10 -) - -var ( - owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB") - trigger byte - token TokenConfig - ctx storage.Context -) - -// TokenConfig holds information about the token we want to use for the sale. -type TokenConfig struct { - // Name of the token. - Name string - // 3 letter abbreviation of the token. - Symbol string - // How decimals this token will have. - Decimals int - // Address of the token owner. This is the Uint160 hash. - Owner []byte - // The total amount of tokens created. Notice that we need to multiply the - // amount by 100000000. (10^8) - TotalSupply int - // Initial amount is number of tokens that are available for the token sale. - InitialAmount int - // How many NEO will be worth 1 token. For example: - // Lets say 1 euro per token, where 1 NEO is 60 euro. This means buyers - // will get (60 * 10^8) tokens for 1 NEO. - AmountPerNEO int - // How many Gas will be worth 1 token. This is the same calculation as - // for the AmountPerNEO, except Gas price will have a different value. - AmountPerGas int - // The maximum amount you can mint in the limited round. For example: - // 500 NEO/buyer * 60 tokens/NEO * 10^8 - MaxExchangeLimitRound int - // When to start the token sale. - SaleStart int - // When to end the initial limited round if there is one. For example: - // SaleStart + 10000 - LimitRoundEnd int - // The prefix used to store how many tokens there are in circulation. - CirculationKey []byte - // The prefix used to store how many tokens there are in the limited round. - LimitRoundKey []byte - // The prefix used to store the addresses that are registered with KYC. - KYCKey []byte -} - -// newTokenConfig returns the initialized TokenConfig. -func newTokenConfig() TokenConfig { - return TokenConfig{ - Name: "My awesome token", - Symbol: "MAT", - Decimals: decimals, - Owner: owner, - TotalSupply: 10000000 * multiplier, - InitialAmount: 5000000 * multiplier, - AmountPerNEO: 60 * multiplier, - AmountPerGas: 40 * multiplier, - MaxExchangeLimitRound: 500 * 60 * multiplier, - SaleStart: 75500, - LimitRoundEnd: 75500 + 10000, - CirculationKey: []byte("in_circulation"), - LimitRoundKey: []byte("r1"), - KYCKey: []byte("kyc_ok"), - } -} - -// getIntFromDB is a helper that checks for nil result of storage.Get and returns -// zero as the default value. -func getIntFromDB(ctx storage.Context, key []byte) int { - var res int - val := storage.Get(ctx, key) - if val != nil { - res = val.(int) - } - return res -} - -// InCirculation returns the amount of total tokens that are in circulation. -func InCirculation() int { - return getIntFromDB(ctx, token.CirculationKey) -} - -// addToCirculation sets the given amount as "in circulation" in the storage. -func addToCirculation(amount int) bool { - if amount < 0 { - return false - } - supply := getIntFromDB(ctx, token.CirculationKey) - supply += amount - if supply > token.TotalSupply { - return false - } - storage.Put(ctx, token.CirculationKey, supply) - return true -} - -// AvailableAmount returns the total amount of available tokens left -// to be distributed. -func AvailableAmount() int { - inCirc := getIntFromDB(ctx, token.CirculationKey) - return token.TotalSupply - inCirc -} - -// init initializes runtime trigger, TokenConfig and storage context before any -// other contract method is called -func init() { - trigger = runtime.GetTrigger() - token = newTokenConfig() - ctx = storage.GetContext() -} - -// checkOwnerWitness is a helper function which checks whether the invoker is the -// owner of the contract. -func checkOwnerWitness() bool { - // This is used to verify if a transfer of system assets (NEO and Gas) - // involving this contract's address can proceed. - if trigger == runtime.Application { - // Check if the invoker is the owner of the contract. - return runtime.CheckWitness(token.Owner) - } - return false -} - -// Decimals returns the token decimals -func Decimals() int { - if trigger != runtime.Application { - panic("invalid trigger") - } - return token.Decimals -} - -// Symbol returns the token symbol -func Symbol() string { - if trigger != runtime.Application { - panic("invalid trigger") - } - return token.Symbol -} - -// TotalSupply returns the token total supply value -func TotalSupply() int { - if trigger != runtime.Application { - panic("invalid trigger") - } - return getIntFromDB(ctx, token.CirculationKey) -} - -// BalanceOf returns the amount of token on the specified address -func BalanceOf(holder interop.Hash160) int { - if trigger != runtime.Application { - panic("invalid trigger") - } - return getIntFromDB(ctx, holder) -} - -// Transfer transfers specified amount of token from one user to another -func Transfer(from, to interop.Hash160, amount int, _ interface{}) bool { - if trigger != runtime.Application { - return false - } - if amount <= 0 || len(to) != 20 || !runtime.CheckWitness(from) { - return false - } - amountFrom := getIntFromDB(ctx, from) - if amountFrom < amount { - return false - } - if amountFrom == amount { - storage.Delete(ctx, from) - } else { - diff := amountFrom - amount - storage.Put(ctx, from, diff) - } - amountTo := getIntFromDB(ctx, to) - totalAmountTo := amountTo + amount - if totalAmountTo != 0 { - storage.Put(ctx, to, totalAmountTo) - } - return true -} - -// TransferFrom transfers specified amount of token from one user to another. -// It differs from Transfer in that it use allowance value to store the amount -// of token available to transfer. -func TransferFrom(from, to []byte, amount int) bool { - if trigger != runtime.Application { - return false - } - if amount <= 0 { - return false - } - availableKey := append(from, to...) - if len(availableKey) != 40 { - return false - } - availableTo := getIntFromDB(ctx, availableKey) - if availableTo < amount { - return false - } - fromBalance := getIntFromDB(ctx, from) - if fromBalance < amount { - return false - } - toBalance := getIntFromDB(ctx, to) - newFromBalance := fromBalance - amount - newToBalance := toBalance + amount - storage.Put(ctx, to, newToBalance) - storage.Put(ctx, from, newFromBalance) - - newAllowance := availableTo - amount - if newAllowance == 0 { - storage.Delete(ctx, availableKey) - } else { - storage.Put(ctx, availableKey, newAllowance) - } - return true -} - -// Approve stores token transfer data if the owner has enough token to send. -func Approve(owner, spender []byte, amount int) bool { - if !checkOwnerWitness() || amount < 0 { - return false - } - if len(spender) != 20 { - return false - } - toSpend := getIntFromDB(ctx, owner) - if toSpend < amount { - return false - } - approvalKey := append(owner, spender...) - if amount == 0 { - storage.Delete(ctx, approvalKey) - } else { - storage.Put(ctx, approvalKey, amount) - } - return true -} - -// Allowance returns allowance value for specified sender and receiver. -func Allowance(from, to []byte) interface{} { - if trigger != runtime.Application { - return false - } - key := append(from, to...) - return getIntFromDB(ctx, key) -} - -// Mint initial supply of tokens -func Mint(to []byte) bool { - if trigger != runtime.Application { - return false - } - if !checkOwnerWitness() { - return false - } - minted := storage.Get(ctx, []byte("minted")) - if minted != nil && minted.(bool) == true { - return false - } - - storage.Put(ctx, to, token.TotalSupply) - storage.Put(ctx, []byte("minted"), true) - addToCirculation(token.TotalSupply) - return true -} diff --git a/examples/token-sale/token_sale.yml b/examples/token-sale/token_sale.yml deleted file mode 100644 index 979bbc4a2..000000000 --- a/examples/token-sale/token_sale.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "My awesome token" -sourceurl: https://github.com/nspcc-dev/neo-go/ -supportedstandards: ["NEP-17"] -safemethods: ["balanceOf", "decimals", "symbol", "totalSupply"] -events: -- name: Transfer - parameters: - - name: from - type: Hash160 - - name: to - type: Hash160 - - name: amount - type: Integer diff --git a/pkg/compiler/inline.go b/pkg/compiler/inline.go index a80f85487..74e33c190 100644 --- a/pkg/compiler/inline.go +++ b/pkg/compiler/inline.go @@ -131,7 +131,7 @@ func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr) { func (c *codegen) processNotify(f *funcScope, args []ast.Expr) { if c.scope != nil && c.isVerifyFunc(c.scope.decl) && - c.scope.pkg == c.mainPkg.Types && !c.buildInfo.options.NoEventsCheck { + c.scope.pkg == c.mainPkg.Types && (c.buildInfo.options == nil || !c.buildInfo.options.NoEventsCheck) { c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name) return } diff --git a/pkg/interop/LICENSE.md b/pkg/interop/LICENSE.md new file mode 120000 index 000000000..f0608a63a --- /dev/null +++ b/pkg/interop/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/pkg/neotest/chain/chain.go b/pkg/neotest/chain/chain.go index b6fbdb2e6..5cbec396b 100644 --- a/pkg/neotest/chain/chain.go +++ b/pkg/neotest/chain/chain.go @@ -17,6 +17,16 @@ import ( "go.uber.org/zap/zaptest" ) +const ( + // MaxTraceableBlocks is the default MaxTraceableBlocks setting used for test chains. + // We don't need a lot of traceable blocks for tests. + MaxTraceableBlocks = 1000 + + // SecondsPerBlock is the default SecondsPerBlock setting used for test chains. + // Usually blocks are created by tests bypassing this setting. + SecondsPerBlock = 1 +) + const singleValidatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY" // committeeWIFs is a list of unencrypted WIFs sorted by public key. @@ -106,23 +116,32 @@ func init() { } // NewSingle creates new blockchain instance with a single validator and -// setups cleanup functions. +// setups cleanup functions. The configuration used is with netmode.UnitTestNet +// magic, and SecondsPerBlock/MaxTraceableBlocks options defined by constants in +// this package. MemoryStore is used as the backend storage, so all of the chain +// contents is always in RAM. The Signer returned is validator (and committee at +// the same time). 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. +// NewSingleWithCustomConfig is similar to NewSingle, but allows to override the +// default configuration. func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { st := storage.NewMemoryStore() return NewSingleWithCustomConfigAndStore(t, f, st, true) } +// NewSingleWithCustomConfigAndStore is similar to NewSingleWithCustomConfig, but +// also allows to override backend Store being used. The last parameter controls if +// Run method is called on the Blockchain instance, if not then it's caller's +// responsibility to do that before using the chain and its caller's responsibility +// also to properly Close the chain when done. func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*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, + MaxTraceableBlocks: MaxTraceableBlocks, + SecondsPerBlock: SecondsPerBlock, StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())}, ValidatorsCount: 1, VerifyBlocks: true, @@ -141,19 +160,20 @@ func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.Protocol return bc, neotest.NewMultiSigner(committeeAcc) } -// NewMulti creates new blockchain instance with 4 validators and 6 committee members. -// Second return value is for validator signer, third -- for committee. +// NewMulti creates new blockchain instance with four validators and six +// committee members, otherwise not differring much from NewSingle. The +// second value returned contains validators Signer, the third -- committee one. 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. +// NewMultiWithCustomConfig is similar to NewMulti except it allows to override the +// default configuration. func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, - SecondsPerBlock: 1, + MaxTraceableBlocks: MaxTraceableBlocks, + SecondsPerBlock: SecondsPerBlock, StandbyCommittee: standByCommittee, ValidatorsCount: 4, VerifyBlocks: true, diff --git a/pkg/neotest/chain/doc.go b/pkg/neotest/chain/doc.go new file mode 100644 index 000000000..1b0d6f286 --- /dev/null +++ b/pkg/neotest/chain/doc.go @@ -0,0 +1,7 @@ +/* +Package chain contains functions creating new test blockchain instances. +Different configurations can be used, but all chains created here use +well-known keys. Most of the time single-node chain is the best choice to use +unless you specifically need multiple validators and large committee. +*/ +package chain diff --git a/pkg/neotest/doc.go b/pkg/neotest/doc.go new file mode 100644 index 000000000..a86dc4615 --- /dev/null +++ b/pkg/neotest/doc.go @@ -0,0 +1,18 @@ +/* +Package neotest contains framework for automated contract testing. +It can be used to implement unit-tests for contracts in Go using regular Go +conventions. + +Usually it's used like this: + * an instance of blockchain is created using chain subpackage + * target contract is compiled using one of Compile* functions + * and Executor is created for blockchain + * it's used to deploy contract with DeployContract + * CommitteeInvoker and/or ValidatorInvoker are then created to perform test invocations + * if needed NewAccount is used to create appropriate number of accounts for the test + +Higher-order methods provided in Executor and ContractInvoker hide the details +of transaction creation for the most part, but there are lower-level methods as +well that can be used for specific tasks. +*/ +package neotest