mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-28 11:36:17 +00:00
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
|
* `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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,6 @@ events:
|
||||||
parameters:
|
parameters:
|
||||||
- name: event
|
- name: event
|
||||||
type: Any
|
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) {
|
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
1
pkg/interop/LICENSE.md
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE.md
|
|
@ -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
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