Merge pull request #2408 from nspcc-dev/improve-examples

Improve examples&documentation
This commit is contained in:
Roman Khimov 2022-03-25 18:16:50 +03:00 committed by GitHub
commit c039133acf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 151 additions and 344 deletions

View file

@ -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 * `defer` and `recover` are supported except for cases where panic occurs in
`return` statement, because this complicates implementation and imposes runtime `return` statement, because this complicates implementation and imposes runtime
overhead for all contracts. This can easily be mitigated by first storing values 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 in variables and returning the result.
conditional code (#2293).
* lambdas are supported, but closures are not. * lambdas are supported, but closures are not.
* maps are supported, but valid map keys are booleans, integers and strings with length <= 64 * maps are supported, but valid map keys are booleans, integers and strings with length <= 64

View file

@ -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. | | [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-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](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. | | [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). | | [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). | | [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) | | [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](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 ## Compile

View file

@ -6,11 +6,28 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "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 { 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) { 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 return true
} }

View file

@ -2,7 +2,11 @@ name: "Iterator example"
sourceurl: https://github.com/nspcc-dev/neo-go/ sourceurl: https://github.com/nspcc-dev/neo-go/
supportedstandards: [] supportedstandards: []
events: events:
- name: found storage key-value pair - name: Key-Value
parameters:
- name: value
type: Any
- name: Value
parameters: parameters:
- name: value - name: value
type: Any type: Any

View file

@ -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 // this method directly, instead it's called by GAS contract when you transfer
// GAS from your address to the address of this NFT contract. // GAS from your address to the address of this NFT contract.
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { 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 { if string(runtime.GetCallingScriptHash()) != gas.Hash {
panic("only GAS is accepted") panic("only GAS is accepted")
} }

View file

@ -1,4 +1,4 @@
package tests package nns_test
import ( import (
"strings" "strings"
@ -17,7 +17,7 @@ import (
func newNSClient(t *testing.T) *neotest.ContractInvoker { func newNSClient(t *testing.T) *neotest.ContractInvoker {
bc, acc := chain.NewSingle(t) bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc) 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) e.DeployContract(t, c, nil)
return e.CommitteeInvoker(c.Hash) return e.CommitteeInvoker(c.Hash)

View file

@ -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 // this method directly, instead it's called by GAS contract when you transfer
// GAS from your address to the address of this NFT contract. // GAS from your address to the address of this NFT contract.
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { 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 { if string(runtime.GetCallingScriptHash()) != gas.Hash {
panic("only GAS is accepted") panic("only GAS is accepted")
} }

View file

@ -1,55 +1,81 @@
package runtimecontract package runtimecontract
import ( 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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/util" "github.com/nspcc-dev/neo-go/pkg/interop/util"
) )
var ( var (
// Check if the invoker of the contract is the specified owner // Check if the invoker of the contract is the specified owner
owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB") owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB")
trigger byte
) )
// 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() { 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) { func _deploy(_ interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
Log("_deploy method called before contract update") Log("_deploy method called after contract update")
return 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 { func CheckWitness() bool {
// Log owner upon Verification trigger
if trigger != runtime.Verification {
return false
}
if runtime.CheckWitness(owner) { if runtime.CheckWitness(owner) {
runtime.Log("Verified Owner") runtime.Log("Verified Owner")
return true
} }
return true return false
} }
// Log logs given message // Log logs given message.
func Log(message string) bool { func Log(message string) {
if trigger != runtime.Application {
return false
}
runtime.Log(message) runtime.Log(message)
return true
} }
// Notify notifies about given message // Notify emits an event with the specified data.
func Notify(event interface{}) bool { func Notify(event interface{}) {
if trigger != runtime.Application { 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 return false
} }
runtime.Notify("Event", event) return CheckWitness()
return true }
// 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)
} }

View file

@ -6,3 +6,6 @@ events:
parameters: parameters:
- name: event - name: event
type: Any type: Any
permissions:
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
methods: ["update", "destroy"]

View file

@ -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

View file

@ -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=

View file

@ -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
}

View file

@ -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

View file

@ -131,7 +131,7 @@ func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr) {
func (c *codegen) processNotify(f *funcScope, args []ast.Expr) { func (c *codegen) processNotify(f *funcScope, args []ast.Expr) {
if c.scope != nil && c.isVerifyFunc(c.scope.decl) && 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) c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name)
return return
} }

1
pkg/interop/LICENSE.md Symbolic link
View file

@ -0,0 +1 @@
../../LICENSE.md

View file

@ -17,6 +17,16 @@ import (
"go.uber.org/zap/zaptest" "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" const singleValidatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY"
// committeeWIFs is a list of unencrypted WIFs sorted by public key. // 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 // 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) { func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) {
return NewSingleWithCustomConfig(t, nil) return NewSingleWithCustomConfig(t, nil)
} }
// NewSingleWithCustomConfig creates new blockchain instance with custom protocol // NewSingleWithCustomConfig is similar to NewSingle, but allows to override the
// configuration and a single validator. It also setups cleanup functions. // default configuration.
func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) {
st := storage.NewMemoryStore() st := storage.NewMemoryStore()
return NewSingleWithCustomConfigAndStore(t, f, st, true) 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) { func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) {
protoCfg := config.ProtocolConfiguration{ protoCfg := config.ProtocolConfiguration{
Magic: netmode.UnitTestNet, Magic: netmode.UnitTestNet,
MaxTraceableBlocks: 1000, // We don't need a lot of traceable blocks for tests. MaxTraceableBlocks: MaxTraceableBlocks,
SecondsPerBlock: 1, SecondsPerBlock: SecondsPerBlock,
StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())}, StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())},
ValidatorsCount: 1, ValidatorsCount: 1,
VerifyBlocks: true, VerifyBlocks: true,
@ -141,19 +160,20 @@ func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.Protocol
return bc, neotest.NewMultiSigner(committeeAcc) return bc, neotest.NewMultiSigner(committeeAcc)
} }
// NewMulti creates new blockchain instance with 4 validators and 6 committee members. // NewMulti creates new blockchain instance with four validators and six
// Second return value is for validator signer, third -- for committee. // 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) { func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) {
return NewMultiWithCustomConfig(t, nil) return NewMultiWithCustomConfig(t, nil)
} }
// NewMultiWithCustomConfig creates new blockchain instance with custom protocol // NewMultiWithCustomConfig is similar to NewMulti except it allows to override the
// configuration, 4 validators and 6 committee members. Second return value is // default configuration.
// for validator signer, third -- for committee.
func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) {
protoCfg := config.ProtocolConfiguration{ protoCfg := config.ProtocolConfiguration{
Magic: netmode.UnitTestNet, Magic: netmode.UnitTestNet,
SecondsPerBlock: 1, MaxTraceableBlocks: MaxTraceableBlocks,
SecondsPerBlock: SecondsPerBlock,
StandbyCommittee: standByCommittee, StandbyCommittee: standByCommittee,
ValidatorsCount: 4, ValidatorsCount: 4,
VerifyBlocks: true, VerifyBlocks: true,

7
pkg/neotest/chain/doc.go Normal file
View file

@ -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

18
pkg/neotest/doc.go Normal file
View file

@ -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