neo-go/examples/token-sale/token_sale.go

278 lines
7.4 KiB
Go
Raw Normal View History

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
)
2020-08-10 10:42:02 +00:00
var (
2021-03-22 15:12:56 +00:00
owner = util.FromAddress("NX1yL5wDx3inK2qUVLRVaqCLUxYnAbv85S")
2020-08-10 10:42:02 +00:00
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
}
2020-08-10 10:42:02 +00:00
// 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
}
2020-08-10 10:42:02 +00:00
// 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
}
2020-08-10 10:42:02 +00:00
supply := getIntFromDB(ctx, token.CirculationKey)
supply += amount
if supply > token.TotalSupply {
return false
}
2020-08-10 10:42:02 +00:00
storage.Put(ctx, token.CirculationKey, supply)
return true
}
2020-08-10 10:42:02 +00:00
// AvailableAmount returns the total amount of available tokens left
// to be distributed.
2020-08-10 10:42:02 +00:00
func AvailableAmount() int {
inCirc := getIntFromDB(ctx, token.CirculationKey)
return token.TotalSupply - inCirc
}
2020-08-10 10:42:02 +00:00
// 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()
}
2020-08-10 10:42:02 +00:00
// 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 {
2020-08-10 10:42:02 +00:00
// Check if the invoker is the owner of the contract.
return runtime.CheckWitness(token.Owner)
}
2020-08-10 10:42:02 +00:00
return false
}
2020-08-10 10:42:02 +00:00
// Decimals returns the token decimals
func Decimals() int {
2020-08-10 10:42:02 +00:00
if trigger != runtime.Application {
panic("invalid trigger")
}
2020-08-10 10:42:02 +00:00
return token.Decimals
}
// Symbol returns the token symbol
func Symbol() string {
2020-08-10 10:42:02 +00:00
if trigger != runtime.Application {
panic("invalid trigger")
}
2020-08-10 10:42:02 +00:00
return token.Symbol
}
// TotalSupply returns the token total supply value
func TotalSupply() int {
2020-08-10 10:42:02 +00:00
if trigger != runtime.Application {
panic("invalid trigger")
}
2020-08-10 10:42:02 +00:00
return getIntFromDB(ctx, token.CirculationKey)
}
// BalanceOf returns the amount of token on the specified address
func BalanceOf(holder interop.Hash160) int {
2020-08-10 10:42:02 +00:00
if trigger != runtime.Application {
panic("invalid trigger")
}
2020-08-10 10:42:02 +00:00
return getIntFromDB(ctx, holder)
}
2020-08-10 10:42:02 +00:00
// Transfer transfers specified amount of token from one user to another
func Transfer(from, to interop.Hash160, amount int, _ interface{}) bool {
2020-08-10 10:42:02 +00:00
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
storage.Put(ctx, to, totalAmountTo)
return true
}
2020-08-10 10:42:02 +00:00
// 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
}
2020-08-10 10:42:02 +00:00
// 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
}
2020-08-10 10:42:02 +00:00
// 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
}