diff --git a/.gitignore b/.gitignore index 383d43756..b6c1b81c1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Added by CoZ developers vendor/ bin/ +!examples/**/vendor # text editors # vscode diff --git a/examples/token/token.go b/examples/token/token.go new file mode 100644 index 000000000..6505ce454 --- /dev/null +++ b/examples/token/token.go @@ -0,0 +1,70 @@ +package token_contract + +import ( + "token" + + "github.com/CityOfZion/neo-storm/interop/storage" + "github.com/CityOfZion/neo-storm/interop/util" +) + +const ( + decimals = 8 + multiplier = 100000000 +) + +var owner = util.FromAddress("AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y") + +// CreateToken initializes the Token Interface for the Smart Contract to operate with +func CreateToken() token.Token { + return token.Token{ + Name: "Awesome NEO Token", + Symbol: "ANT", + Decimals: decimals, + Owner: owner, + TotalSupply: 11000000 * multiplier, + CirculationKey: "TokenCirculation", + } +} + +// Main function = contract entry +func Main(operation string, args []interface{}) interface{} { + token := CreateToken() + + if operation == "name" { + return token.Name + } + if operation == "symbol" { + return token.Symbol + } + if operation == "decimals" { + return token.Decimals + } + + // The following operations need ctx + ctx := storage.GetContext() + + if operation == "totalSupply" { + return token.GetSupply(ctx) + } + if operation == "balanceOf" { + hodler := args[0].([]byte) + return token.BalanceOf(ctx, hodler) + } + if operation == "transfer" && CheckArgs(args, 3) { + from := args[0].([]byte) + to := args[1].([]byte) + amount := args[2].(int) + return token.Transfer(ctx, from, to, amount) + } + + 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 +} diff --git a/examples/token/vendor/token/token.go b/examples/token/vendor/token/token.go new file mode 100644 index 000000000..7a064350c --- /dev/null +++ b/examples/token/vendor/token/token.go @@ -0,0 +1,115 @@ +package token + +import ( + "github.com/CityOfZion/neo-storm/interop/engine" + "github.com/CityOfZion/neo-storm/interop/runtime" + "github.com/CityOfZion/neo-storm/interop/storage" +) + +// Token holds all token info +type Token struct { + // Token name + Name string + // Ticker symbol + Symbol string + // Amount of decimals + Decimals int + // Token owner address + Owner []byte + // Total tokens * multiplier + TotalSupply int + // Storage key for circulation value + CirculationKey string +} + +// TODO: Transfer event +// DoTransfer := action.RegisterAction("transfer", "from", "to", "amount") + +// GetSupply gets the token totalSupply value from VM storage +func (t Token) GetSupply(ctx storage.Context) interface{} { + return storage.Get(ctx, t.CirculationKey) +} + +// BalanceOf gets the token balance of a specific address +func (t Token) BalanceOf(ctx storage.Context, hodler []byte) interface{} { + return storage.Get(ctx, hodler) +} + +// Transfer token from one user to another +func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int) bool { + amountFrom := t.CanTransfer(ctx, from, to, amount) + if amountFrom == -1 { + return false + } + + if amountFrom == 0 { + storage.Delete(ctx, from) + } + + if amountFrom > 0 { + diff := amountFrom - amount + storage.Put(ctx, from, diff) + } + + amountTo := storage.Get(ctx, to).(int) + totalAmountTo := amountTo + amount + storage.Put(ctx, to, totalAmountTo) + // DoTransfer(from, to, amount) + return true +} + +// CanTransfer returns the amount it can transfer +func (t Token) CanTransfer(ctx storage.Context, from []byte, to []byte, amount int) int { + if len(to) != 20 && !IsUsableAddress(from) { + return -1 + } + + amountFrom := storage.Get(ctx, from).(int) + if amountFrom < amount { + return -1 + } + + // Tell Transfer the result is equal - special case since it uses Delete + if amountFrom == amount { + return 0 + } + + // return amountFrom value back to Transfer, reduces extra Get + return amountFrom +} + +// IsUsableAddress checks if the sender is either the correct NEO address or SC address +func IsUsableAddress(addr []byte) bool { + if len(addr) == 20 { + + if runtime.CheckWitness(addr) { + return true + } + + // Check if a smart contract is calling scripthash + callingScriptHash := engine.GetCallingScriptHash() + if EqualAddresses(callingScriptHash, addr) { + return true + } + } + + return false +} + +// EqualAddresses compares two addresses if they're equal +// also returns false if one of the two - or both - aren't actual addresses +func EqualAddresses(a []byte, b []byte) bool { + aLen := len(a) + bLen := len(b) + if aLen != bLen || aLen != 20 || bLen != 20 { + return false + } + + for i := 0; i < aLen; i++ { + if a[i] != b[i] { + return false + } + } + + return true +}