mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 23:33:37 +00:00
Merge pull request #1296 from nspcc-dev/smartcontract/examples
examples: update examples
This commit is contained in:
commit
c3f7a419a0
19 changed files with 248 additions and 275 deletions
|
@ -60,8 +60,16 @@ const (
|
||||||
|
|
||||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
|
||||||
func Main(op string, args []interface{}) {
|
var notificationName string
|
||||||
runtime.Notify("Hello world!")
|
|
||||||
|
// init initializes notificationName before calling any other smart-contract method
|
||||||
|
func init() {
|
||||||
|
notificationName = "Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeNotify sends runtime notification with "Hello world!" name
|
||||||
|
func RuntimeNotify(args []interface{}) {
|
||||||
|
runtime.Notify(notificationName, args)
|
||||||
}`
|
}`
|
||||||
// cosignersSeparator is a special value which is used to distinguish
|
// cosignersSeparator is a special value which is used to distinguish
|
||||||
// parameters and cosigners for invoke* commands
|
// parameters and cosigners for invoke* commands
|
||||||
|
@ -325,6 +333,17 @@ func initSmartContract(ctx *cli.Context) error {
|
||||||
|
|
||||||
m := ProjectConfig{
|
m := ProjectConfig{
|
||||||
SupportedStandards: []string{},
|
SupportedStandards: []string{},
|
||||||
|
Events: []manifest.Event{
|
||||||
|
{
|
||||||
|
Name: "Hello world!",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{
|
||||||
|
Name: "args",
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
b, err := yaml.Marshal(m)
|
b, err := yaml.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -369,6 +388,7 @@ func contractCompile(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.ContractFeatures = conf.GetFeatures()
|
o.ContractFeatures = conf.GetFeatures()
|
||||||
|
o.ContractEvents = conf.Events
|
||||||
o.ContractSupportedStandards = conf.SupportedStandards
|
o.ContractSupportedStandards = conf.SupportedStandards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,26 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is that famous Main() function, you know.
|
// NotifyScriptContainer sends runtime notification with script container hash
|
||||||
func Main() bool {
|
func NotifyScriptContainer() {
|
||||||
tx := runtime.GetScriptContainer()
|
tx := runtime.GetScriptContainer()
|
||||||
runtime.Notify("Tx", tx.Hash)
|
runtime.Notify("Tx", tx.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyCallingScriptHash sends runtime notification with calling script hash
|
||||||
|
func NotifyCallingScriptHash() {
|
||||||
callingScriptHash := runtime.GetCallingScriptHash()
|
callingScriptHash := runtime.GetCallingScriptHash()
|
||||||
runtime.Notify("Calling", callingScriptHash)
|
runtime.Notify("Calling", callingScriptHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyExecutingScriptHash sends runtime notification about executing script hash
|
||||||
|
func NotifyExecutingScriptHash() {
|
||||||
execScriptHash := runtime.GetExecutingScriptHash()
|
execScriptHash := runtime.GetExecutingScriptHash()
|
||||||
runtime.Notify("Executing", execScriptHash)
|
runtime.Notify("Executing", execScriptHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyEntryScriptHash sends notification about entry script hash
|
||||||
|
func NotifyEntryScriptHash() {
|
||||||
entryScriptHash := runtime.GetEntryScriptHash()
|
entryScriptHash := runtime.GetEntryScriptHash()
|
||||||
runtime.Notify("Entry", entryScriptHash)
|
runtime.Notify("Entry", entryScriptHash)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
8
examples/engine/engine.yml
Normal file
8
examples/engine/engine.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
hasstorage: false
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Tx
|
||||||
|
parameters:
|
||||||
|
- name: txHash
|
||||||
|
type: ByteString
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is Main(), really.
|
// NotifyKeysAndValues sends notification with `foo` storage keys and values
|
||||||
func Main() bool {
|
func NotifyKeysAndValues() bool {
|
||||||
iter := storage.Find(storage.GetContext(), []byte("foo"))
|
iter := storage.Find(storage.GetContext(), []byte("foo"))
|
||||||
values := iterator.Values(iter)
|
values := iterator.Values(iter)
|
||||||
keys := iterator.Keys(iter)
|
keys := iterator.Keys(iter)
|
||||||
|
|
12
examples/iterator/iterator.yml
Normal file
12
examples/iterator/iterator.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: found storage values
|
||||||
|
parameters:
|
||||||
|
- name: values
|
||||||
|
type: Any
|
||||||
|
- name: found storage keys
|
||||||
|
parameters:
|
||||||
|
- name: keys
|
||||||
|
type: Any
|
|
@ -5,40 +5,23 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if the invoker of the contract is the specified owner
|
var (
|
||||||
var owner = util.FromAddress("Nis7Cu1Qn6iBb8kbeQ5HgdZT7AsQPqywTC")
|
// Check if the invoker of the contract is the specified owner
|
||||||
|
owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt")
|
||||||
|
trigger byte
|
||||||
|
)
|
||||||
|
|
||||||
// Main is something to be ran from outside.
|
// init initializes trigger before any other contract method is called
|
||||||
func Main(operation string, args []interface{}) bool {
|
func init() {
|
||||||
trigger := runtime.GetTrigger()
|
trigger = runtime.GetTrigger()
|
||||||
|
|
||||||
// Log owner upon Verification trigger
|
|
||||||
if trigger == runtime.Verification {
|
|
||||||
return CheckWitness()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discerns between log and notify for this test
|
|
||||||
if trigger == runtime.Application {
|
|
||||||
return handleOperation(operation, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleOperation(operation string, args []interface{}) bool {
|
|
||||||
if operation == "log" {
|
|
||||||
return Log(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "notify" {
|
|
||||||
return Notify(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckWitness checks owner's witness
|
// CheckWitness checks owner's witness
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
@ -46,14 +29,19 @@ func CheckWitness() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log logs given message
|
// Log logs given message
|
||||||
func Log(args []interface{}) bool {
|
func Log(message string) bool {
|
||||||
message := args[0].(string)
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
runtime.Log(message)
|
runtime.Log(message)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify notifies about given message
|
// Notify notifies about given message
|
||||||
func Notify(args []interface{}) bool {
|
func Notify(event interface{}) bool {
|
||||||
runtime.Notify("Event", args[0])
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
runtime.Notify("Event", event)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
8
examples/runtime/runtime.yml
Normal file
8
examples/runtime/runtime.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
hasstorage: false
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Event
|
||||||
|
parameters:
|
||||||
|
- name: event
|
||||||
|
type: Any
|
|
@ -5,62 +5,33 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is a very useful function.
|
// ctx holds storage context for contract methods
|
||||||
func Main(operation string, args []interface{}) interface{} {
|
var ctx storage.Context
|
||||||
if operation == "put" {
|
|
||||||
return Put(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "get" {
|
// init inits storage context before any other contract method is called
|
||||||
return Get(args)
|
func init() {
|
||||||
}
|
ctx = storage.GetContext()
|
||||||
|
|
||||||
if operation == "delete" {
|
|
||||||
return Delete(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "find" {
|
|
||||||
return Find(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put puts value at key.
|
// Put puts value at key.
|
||||||
func Put(args []interface{}) interface{} {
|
func Put(key, value []byte) []byte {
|
||||||
ctx := storage.GetContext()
|
|
||||||
if checkArgs(args, 2) {
|
|
||||||
key := args[0].([]byte)
|
|
||||||
value := args[1].([]byte)
|
|
||||||
storage.Put(ctx, key, value)
|
storage.Put(ctx, key, value)
|
||||||
return key
|
return key
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value at passed key.
|
// Get returns the value at passed key.
|
||||||
func Get(args []interface{}) interface{} {
|
func Get(key []byte) interface{} {
|
||||||
ctx := storage.GetContext()
|
|
||||||
if checkArgs(args, 1) {
|
|
||||||
key := args[0].([]byte)
|
|
||||||
return storage.Get(ctx, key)
|
return storage.Get(ctx, key)
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the value at passed key.
|
// Delete deletes the value at passed key.
|
||||||
func Delete(args []interface{}) interface{} {
|
func Delete(key []byte) bool {
|
||||||
ctx := storage.GetContext()
|
|
||||||
key := args[0].([]byte)
|
|
||||||
storage.Delete(ctx, key)
|
storage.Delete(ctx, key)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find returns an array of key-value pairs with key that matched the passed value.
|
// Find returns an array of key-value pairs with key that matched the passed value
|
||||||
func Find(args []interface{}) interface{} {
|
func Find(value []byte) []string {
|
||||||
ctx := storage.GetContext()
|
|
||||||
if checkArgs(args, 1) {
|
|
||||||
value := args[0].([]byte)
|
|
||||||
iter := storage.Find(ctx, value)
|
iter := storage.Find(ctx, value)
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for iterator.Next(iter) {
|
for iterator.Next(iter) {
|
||||||
|
@ -69,14 +40,4 @@ func Find(args []interface{}) interface{} {
|
||||||
result = append(result, key.(string)+":"+val.(string))
|
result = append(result, key.(string)+":"+val.(string))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkArgs(args []interface{}, length int) bool {
|
|
||||||
if len(args) == length {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
4
examples/storage/storage.yml
Normal file
4
examples/storage/storage.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: []
|
||||||
|
events: []
|
|
@ -11,7 +11,12 @@ const (
|
||||||
multiplier = decimals * 10
|
multiplier = decimals * 10
|
||||||
)
|
)
|
||||||
|
|
||||||
var owner = util.FromAddress("NNf7GXcNMFHU8pLvU84afYZCfzXDopy71M")
|
var (
|
||||||
|
owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt")
|
||||||
|
trigger byte
|
||||||
|
token TokenConfig
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
// TokenConfig holds information about the token we want to use for the sale.
|
// TokenConfig holds information about the token we want to use for the sale.
|
||||||
type TokenConfig struct {
|
type TokenConfig struct {
|
||||||
|
@ -51,8 +56,8 @@ type TokenConfig struct {
|
||||||
KYCKey []byte
|
KYCKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTokenConfig returns the initialized TokenConfig.
|
// newTokenConfig returns the initialized TokenConfig.
|
||||||
func NewTokenConfig() TokenConfig {
|
func newTokenConfig() TokenConfig {
|
||||||
return TokenConfig{
|
return TokenConfig{
|
||||||
Name: "My awesome token",
|
Name: "My awesome token",
|
||||||
Symbol: "MAT",
|
Symbol: "MAT",
|
||||||
|
@ -82,108 +87,91 @@ func getIntFromDB(ctx storage.Context, key []byte) int {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// InCirculation return the amount of total tokens that are in circulation.
|
// InCirculation returns the amount of total tokens that are in circulation.
|
||||||
func (t TokenConfig) InCirculation(ctx storage.Context) int {
|
func InCirculation() int {
|
||||||
return getIntFromDB(ctx, t.CirculationKey)
|
return getIntFromDB(ctx, token.CirculationKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToCirculation sets the given amount as "in circulation" in the storage.
|
// AddToCirculation sets the given amount as "in circulation" in the storage.
|
||||||
func (t TokenConfig) AddToCirculation(ctx storage.Context, amount int) bool {
|
func AddToCirculation(amount int) bool {
|
||||||
supply := getIntFromDB(ctx, t.CirculationKey)
|
supply := getIntFromDB(ctx, token.CirculationKey)
|
||||||
supply += amount
|
supply += amount
|
||||||
storage.Put(ctx, t.CirculationKey, supply)
|
storage.Put(ctx, token.CirculationKey, supply)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenSaleAvailableAmount returns the total amount of available tokens left
|
// AvailableAmount returns the total amount of available tokens left
|
||||||
// to be distributed.
|
// to be distributed.
|
||||||
func (t TokenConfig) TokenSaleAvailableAmount(ctx storage.Context) int {
|
func AvailableAmount() int {
|
||||||
inCirc := getIntFromDB(ctx, t.CirculationKey)
|
inCirc := getIntFromDB(ctx, token.CirculationKey)
|
||||||
return t.TotalSupply - inCirc
|
return token.TotalSupply - inCirc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main smart contract entry point.
|
// init initializes runtime trigger, TokenConfig and storage context before any
|
||||||
func Main(operation string, args []interface{}) interface{} {
|
// other contract method is called
|
||||||
var (
|
func init() {
|
||||||
trigger = runtime.GetTrigger()
|
trigger = runtime.GetTrigger()
|
||||||
cfg = NewTokenConfig()
|
token = newTokenConfig()
|
||||||
ctx = storage.GetContext()
|
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)
|
// This is used to verify if a transfer of system assets (NEO and Gas)
|
||||||
// involving this contract's address can proceed.
|
// involving this contract's address can proceed.
|
||||||
if trigger == runtime.Verification {
|
|
||||||
// Check if the invoker is the owner of the contract.
|
|
||||||
if runtime.CheckWitness(cfg.Owner) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Otherwise TODO
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if trigger == runtime.Application {
|
if trigger == runtime.Application {
|
||||||
return handleOperation(operation, args, ctx, cfg)
|
// Check if the invoker is the owner of the contract.
|
||||||
}
|
return runtime.CheckWitness(token.Owner)
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleOperation(op string, args []interface{}, ctx storage.Context, cfg TokenConfig) interface{} {
|
|
||||||
// NEP-5 handlers
|
|
||||||
if op == "name" {
|
|
||||||
return cfg.Name
|
|
||||||
}
|
|
||||||
if op == "decimals" {
|
|
||||||
return cfg.Decimals
|
|
||||||
}
|
|
||||||
if op == "symbol" {
|
|
||||||
return cfg.Symbol
|
|
||||||
}
|
|
||||||
if op == "totalSupply" {
|
|
||||||
return getIntFromDB(ctx, cfg.CirculationKey)
|
|
||||||
}
|
|
||||||
if op == "balanceOf" {
|
|
||||||
if len(args) == 1 {
|
|
||||||
return getIntFromDB(ctx, args[0].([]byte))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if op == "transfer" {
|
|
||||||
if len(args) != 3 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
from := args[0].([]byte)
|
|
||||||
to := args[1].([]byte)
|
|
||||||
amount := args[2].(int)
|
|
||||||
return transfer(cfg, ctx, from, to, amount)
|
|
||||||
}
|
|
||||||
if op == "transferFrom" {
|
|
||||||
if len(args) != 3 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
from := args[0].([]byte)
|
|
||||||
to := args[1].([]byte)
|
|
||||||
amount := args[2].(int)
|
|
||||||
return transferFrom(cfg, ctx, from, to, amount)
|
|
||||||
}
|
|
||||||
if op == "approve" {
|
|
||||||
if len(args) != 3 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
from := args[0].([]byte)
|
|
||||||
to := args[1].([]byte)
|
|
||||||
amount := args[2].(int)
|
|
||||||
return approve(ctx, from, to, amount)
|
|
||||||
}
|
|
||||||
if op == "allowance" {
|
|
||||||
if len(args) != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
from := args[0].([]byte)
|
|
||||||
to := args[1].([]byte)
|
|
||||||
return allowance(ctx, from, to)
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func transfer(cfg TokenConfig, ctx storage.Context, from, to []byte, amount int) bool {
|
// Name returns the token name
|
||||||
|
func Name() interface{} {
|
||||||
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return token.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimals returns the token decimals
|
||||||
|
func Decimals() interface{} {
|
||||||
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return token.Decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol returns the token symbol
|
||||||
|
func Symbol() interface{} {
|
||||||
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return token.Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalSupply returns the token total supply value
|
||||||
|
func TotalSupply() interface{} {
|
||||||
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return getIntFromDB(ctx, token.CirculationKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceOf returns the amount of token on the specified address
|
||||||
|
func BalanceOf(holder []byte) interface{} {
|
||||||
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return getIntFromDB(ctx, holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer transfers specified amount of token from one user to another
|
||||||
|
func Transfer(from, to []byte, amount int) bool {
|
||||||
|
if trigger != runtime.Application {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if amount <= 0 || len(to) != 20 || !runtime.CheckWitness(from) {
|
if amount <= 0 || len(to) != 20 || !runtime.CheckWitness(from) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -203,7 +191,13 @@ func transfer(cfg TokenConfig, ctx storage.Context, from, to []byte, amount int)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferFrom(cfg TokenConfig, ctx storage.Context, from, to []byte, amount int) bool {
|
// 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 {
|
if amount <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -234,8 +228,9 @@ func transferFrom(cfg TokenConfig, ctx storage.Context, from, to []byte, amount
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func approve(ctx storage.Context, owner, spender []byte, amount int) bool {
|
// Approve stores token transfer data if the owner has enough token to send.
|
||||||
if !runtime.CheckWitness(owner) || amount < 0 {
|
func Approve(owner, spender []byte, amount int) bool {
|
||||||
|
if !checkOwnerWitness() || amount < 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(spender) != 20 {
|
if len(spender) != 20 {
|
||||||
|
@ -254,7 +249,11 @@ func approve(ctx storage.Context, owner, spender []byte, amount int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func allowance(ctx storage.Context, from, to []byte) int {
|
// 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...)
|
key := append(from, to...)
|
||||||
return getIntFromDB(ctx, key)
|
return getIntFromDB(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
4
examples/token-sale/token_sale.yml
Normal file
4
examples/token-sale/token_sale.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: ["NEP-5"]
|
||||||
|
events: []
|
|
@ -34,12 +34,12 @@ func getIntFromDB(ctx storage.Context, key []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSupply gets the token totalSupply value from VM storage
|
// GetSupply gets the token totalSupply value from VM storage
|
||||||
func (t Token) GetSupply(ctx storage.Context) interface{} {
|
func (t Token) GetSupply(ctx storage.Context) int {
|
||||||
return getIntFromDB(ctx, []byte(t.CirculationKey))
|
return getIntFromDB(ctx, []byte(t.CirculationKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceOf gets the token balance of a specific address
|
// BalanceOf gets the token balance of a specific address
|
||||||
func (t Token) BalanceOf(ctx storage.Context, holder []byte) interface{} {
|
func (t Token) BalanceOf(ctx storage.Context, holder []byte) int {
|
||||||
return getIntFromDB(ctx, holder)
|
return getIntFromDB(ctx, holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ func IsUsableAddress(addr []byte) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mint initial supply of tokens.
|
// Mint initial supply of tokens
|
||||||
func (t Token) Mint(ctx storage.Context, to []byte) bool {
|
func (t Token) Mint(ctx storage.Context, to []byte) bool {
|
||||||
if !IsUsableAddress(t.Owner) {
|
if !IsUsableAddress(t.Owner) {
|
||||||
return false
|
return false
|
||||||
|
@ -116,6 +116,7 @@ func (t Token) Mint(ctx storage.Context, to []byte) bool {
|
||||||
|
|
||||||
storage.Put(ctx, to, t.TotalSupply)
|
storage.Put(ctx, to, t.TotalSupply)
|
||||||
storage.Put(ctx, []byte("minted"), true)
|
storage.Put(ctx, []byte("minted"), true)
|
||||||
|
storage.Put(ctx, []byte(t.CirculationKey), t.TotalSupply)
|
||||||
runtime.Notify("transfer", "", to, t.TotalSupply)
|
runtime.Notify("transfer", "", to, t.TotalSupply)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,16 @@ const (
|
||||||
multiplier = 100000000
|
multiplier = 100000000
|
||||||
)
|
)
|
||||||
|
|
||||||
var owner = util.FromAddress("NMipL5VsNoLUBUJKPKLhxaEbPQVCZnyJyB")
|
var (
|
||||||
|
owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt")
|
||||||
|
token nep5.Token
|
||||||
|
ctx storage.Context
|
||||||
|
)
|
||||||
|
|
||||||
// createToken initializes the Token Interface for the Smart Contract to operate with
|
// init initializes the Token Interface and storage context for the Smart
|
||||||
func createToken() nep5.Token {
|
// Contract to operate with
|
||||||
return nep5.Token{
|
func init() {
|
||||||
|
token = nep5.Token{
|
||||||
Name: "Awesome NEO Token",
|
Name: "Awesome NEO Token",
|
||||||
Symbol: "ANT",
|
Symbol: "ANT",
|
||||||
Decimals: decimals,
|
Decimals: decimals,
|
||||||
|
@ -23,95 +28,40 @@ func createToken() nep5.Token {
|
||||||
TotalSupply: 11000000 * multiplier,
|
TotalSupply: 11000000 * multiplier,
|
||||||
CirculationKey: "TokenCirculation",
|
CirculationKey: "TokenCirculation",
|
||||||
}
|
}
|
||||||
}
|
ctx = storage.GetContext()
|
||||||
|
|
||||||
// Main function = contract entry
|
|
||||||
func Main(operation string, args []interface{}) interface{} {
|
|
||||||
if operation == "name" {
|
|
||||||
return Name()
|
|
||||||
}
|
|
||||||
if operation == "symbol" {
|
|
||||||
return Symbol()
|
|
||||||
}
|
|
||||||
if operation == "decimals" {
|
|
||||||
return Decimals()
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "totalSupply" {
|
|
||||||
return TotalSupply()
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "balanceOf" {
|
|
||||||
hodler := args[0].([]byte)
|
|
||||||
return BalanceOf(hodler)
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "transfer" && checkArgs(args, 3) {
|
|
||||||
from := args[0].([]byte)
|
|
||||||
to := args[1].([]byte)
|
|
||||||
amount := args[2].(int)
|
|
||||||
return Transfer(from, to, amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
if operation == "mint" && checkArgs(args, 1) {
|
|
||||||
addr := args[0].([]byte)
|
|
||||||
return Mint(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkArgs checks args array against a length indicator
|
|
||||||
func checkArgs(args []interface{}, length int) bool {
|
|
||||||
if len(args) == length {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the token name
|
// Name returns the token name
|
||||||
func Name() string {
|
func Name() string {
|
||||||
t := createToken()
|
return token.Name
|
||||||
return t.Name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symbol returns the token symbol
|
// Symbol returns the token symbol
|
||||||
func Symbol() string {
|
func Symbol() string {
|
||||||
t := createToken()
|
return token.Symbol
|
||||||
return t.Symbol
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decimals returns the token decimals
|
// Decimals returns the token decimals
|
||||||
func Decimals() int {
|
func Decimals() int {
|
||||||
t := createToken()
|
return token.Decimals
|
||||||
return t.Decimals
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalSupply returns the token total supply value
|
// TotalSupply returns the token total supply value
|
||||||
func TotalSupply() interface{} {
|
func TotalSupply() int {
|
||||||
t := createToken()
|
return token.GetSupply(ctx)
|
||||||
ctx := storage.GetContext()
|
|
||||||
return t.GetSupply(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceOf returns the amount of token on the specified address
|
// BalanceOf returns the amount of token on the specified address
|
||||||
func BalanceOf(holder []byte) interface{} {
|
func BalanceOf(holder []byte) interface{} {
|
||||||
t := createToken()
|
return token.BalanceOf(ctx, holder)
|
||||||
ctx := storage.GetContext()
|
|
||||||
return t.BalanceOf(ctx, holder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer token from one user to another
|
// Transfer token from one user to another
|
||||||
func Transfer(from []byte, to []byte, amount int) bool {
|
func Transfer(from []byte, to []byte, amount int) bool {
|
||||||
t := createToken()
|
return token.Transfer(ctx, from, to, amount)
|
||||||
ctx := storage.GetContext()
|
|
||||||
return t.Transfer(ctx, from, to, amount)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mint initial supply of tokens
|
// Mint initial supply of tokens
|
||||||
func Mint(to []byte) bool {
|
func Mint(to []byte) bool {
|
||||||
t := createToken()
|
return token.Mint(ctx, to)
|
||||||
ctx := storage.GetContext()
|
|
||||||
return t.Mint(ctx, to)
|
|
||||||
}
|
}
|
||||||
|
|
12
examples/token/token.yml
Normal file
12
examples/token/token.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: ["NEP-5"]
|
||||||
|
events:
|
||||||
|
- name: transfer
|
||||||
|
parameters:
|
||||||
|
- name: from
|
||||||
|
type: ByteString
|
||||||
|
- name: to
|
||||||
|
type: ByteString
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +38,9 @@ type Options struct {
|
||||||
// Contract features.
|
// Contract features.
|
||||||
ContractFeatures smartcontract.PropertyState
|
ContractFeatures smartcontract.PropertyState
|
||||||
|
|
||||||
|
// Runtime notifications.
|
||||||
|
ContractEvents []manifest.Event
|
||||||
|
|
||||||
// The list of standards supported by the contract.
|
// The list of standards supported by the contract.
|
||||||
ContractSupportedStandards []string
|
ContractSupportedStandards []string
|
||||||
}
|
}
|
||||||
|
@ -190,7 +194,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.ManifestFile != "" {
|
if o.ManifestFile != "" {
|
||||||
m, err := di.ConvertToManifest(o.ContractFeatures, o.ContractSupportedStandards...)
|
m, err := di.ConvertToManifest(o.ContractFeatures, o.ContractEvents, o.ContractSupportedStandards...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b, fmt.Errorf("failed to convert debug info to manifest: %w", err)
|
return b, fmt.Errorf("failed to convert debug info to manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,8 +363,7 @@ func parsePairJSON(data []byte, sep string) (string, string, error) {
|
||||||
|
|
||||||
// ConvertToManifest converts contract to the manifest.Manifest struct for debugger.
|
// ConvertToManifest converts contract to the manifest.Manifest struct for debugger.
|
||||||
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
|
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
|
||||||
func (di *DebugInfo) ConvertToManifest(fs smartcontract.PropertyState, supportedStandards ...string) (*manifest.Manifest, error) {
|
func (di *DebugInfo) ConvertToManifest(fs smartcontract.PropertyState, events []manifest.Event, supportedStandards ...string) (*manifest.Manifest, error) {
|
||||||
var err error
|
|
||||||
if di.MainPkg == "" {
|
if di.MainPkg == "" {
|
||||||
return nil, errors.New("no Main method was found")
|
return nil, errors.New("no Main method was found")
|
||||||
}
|
}
|
||||||
|
@ -378,19 +377,15 @@ func (di *DebugInfo) ConvertToManifest(fs smartcontract.PropertyState, supported
|
||||||
methods = append(methods, mMethod)
|
methods = append(methods, mMethod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events := make([]manifest.Event, len(di.Events))
|
|
||||||
for i, event := range di.Events {
|
|
||||||
events[i], err = event.ToManifestEvent()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := manifest.NewManifest(di.Hash)
|
result := manifest.NewManifest(di.Hash)
|
||||||
result.Features = fs
|
result.Features = fs
|
||||||
if supportedStandards != nil {
|
if supportedStandards != nil {
|
||||||
result.SupportedStandards = supportedStandards
|
result.SupportedStandards = supportedStandards
|
||||||
}
|
}
|
||||||
|
if events == nil {
|
||||||
|
events = make([]manifest.Event, 0)
|
||||||
|
}
|
||||||
result.ABI = manifest.ABI{
|
result.ABI = manifest.ABI{
|
||||||
Hash: di.Hash,
|
Hash: di.Hash,
|
||||||
Methods: methods,
|
Methods: methods,
|
||||||
|
|
|
@ -127,7 +127,7 @@ func unexportedMethod() int { return 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("convert to Manifest", func(t *testing.T) {
|
t.Run("convert to Manifest", func(t *testing.T) {
|
||||||
actual, err := d.ConvertToManifest(smartcontract.HasStorage)
|
actual, err := d.ConvertToManifest(smartcontract.HasStorage, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// note: offsets are hard to predict, so we just take them from the output
|
// note: offsets are hard to predict, so we just take them from the output
|
||||||
expected := &manifest.Manifest{
|
expected := &manifest.Manifest{
|
||||||
|
|
|
@ -88,7 +88,7 @@ func TestAppCall(t *testing.T) {
|
||||||
|
|
||||||
inner, di, err := compiler.CompileWithDebugInfo("foo.go", strings.NewReader(srcInner))
|
inner, di, err := compiler.CompileWithDebugInfo("foo.go", strings.NewReader(srcInner))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
m, err := di.ConvertToManifest(smartcontract.NoProperties)
|
m, err := di.ConvertToManifest(smartcontract.NoProperties, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ih := hash.Hash160(inner)
|
ih := hash.Hash160(inner)
|
||||||
|
|
|
@ -233,7 +233,7 @@ func TestCreateBasicChain(t *testing.T) {
|
||||||
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
|
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
|
||||||
|
|
||||||
script := io.NewBufBinWriter()
|
script := io.NewBufBinWriter()
|
||||||
m, err := di.ConvertToManifest(smartcontract.HasStorage)
|
m, err := di.ConvertToManifest(smartcontract.HasStorage, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
bs, err := m.MarshalJSON()
|
bs, err := m.MarshalJSON()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
Loading…
Reference in a new issue