forked from TrueCloudLab/neoneo-go
4fdcefc87c
Closes #1379 We should check owner witness before adding more tokens to circulation. Also we shouldn't allow to add to circulation more than TokenSupply tokens.
284 lines
7.5 KiB
Go
284 lines
7.5 KiB
Go
package tokensale
|
|
|
|
import (
|
|
"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("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt")
|
|
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
|
|
}
|
|
|
|
// 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) {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|