Merge pull request #2408 from nspcc-dev/improve-examples
Improve examples&documentation
This commit is contained in:
commit
c039133acf
18 changed files with 151 additions and 344 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -6,3 +6,6 @@ events:
|
|||
parameters:
|
||||
- name: event
|
||||
type: Any
|
||||
permissions:
|
||||
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
|
||||
methods: ["update", "destroy"]
|
||||
|
|
|
@ -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
|
|
@ -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=
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
1
pkg/interop/LICENSE.md
Symbolic link
1
pkg/interop/LICENSE.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE.md
|
|
@ -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,
|
||||
|
|
7
pkg/neotest/chain/doc.go
Normal file
7
pkg/neotest/chain/doc.go
Normal 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
18
pkg/neotest/doc.go
Normal 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
|
Loading…
Reference in a new issue