mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 23:42:23 +00:00
examples: change NFT contract to use storage iterators
This commit is contained in:
parent
a4b54b2e8a
commit
721748acfd
2 changed files with 67 additions and 92 deletions
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -233,8 +234,13 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
// tokensOf: good, several tokens
|
// tokensOf: good, several tokens
|
||||||
tokenID1 := mint(t)
|
tokenID1 := mint(t)
|
||||||
e.Run(t, cmdTokensOf...)
|
e.Run(t, cmdTokensOf...)
|
||||||
e.checkNextLine(t, string(tokenID))
|
fst, snd := tokenID, tokenID1
|
||||||
e.checkNextLine(t, string(tokenID1))
|
if bytes.Compare(tokenID, tokenID1) == 1 {
|
||||||
|
fst, snd = snd, fst
|
||||||
|
}
|
||||||
|
|
||||||
|
e.checkNextLine(t, string(fst))
|
||||||
|
e.checkNextLine(t, string(snd))
|
||||||
|
|
||||||
// tokens: missing contract hash
|
// tokens: missing contract hash
|
||||||
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
||||||
|
@ -245,8 +251,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// tokens: good, several tokens
|
// tokens: good, several tokens
|
||||||
e.Run(t, cmdTokens...)
|
e.Run(t, cmdTokens...)
|
||||||
e.checkNextLine(t, string(tokenID))
|
e.checkNextLine(t, string(fst))
|
||||||
e.checkNextLine(t, string(tokenID1))
|
e.checkNextLine(t, string(snd))
|
||||||
|
|
||||||
// balance check: several tokens, ok
|
// balance check: several tokens, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
|
|
@ -23,9 +23,12 @@ import (
|
||||||
// Prefixes used for contract data storage.
|
// Prefixes used for contract data storage.
|
||||||
const (
|
const (
|
||||||
totalSupplyPrefix = "s"
|
totalSupplyPrefix = "s"
|
||||||
accountPrefix = "a"
|
// balancePrefix contains map from addresses to balances.
|
||||||
tokenPrefix = "t"
|
balancePrefix = "b"
|
||||||
tokensPrefix = "ts"
|
// accountPrefix contains map from address + token id to tokens
|
||||||
|
accountPrefix = "a"
|
||||||
|
// tokenPrefix contains map from token id to it's owner.
|
||||||
|
tokenPrefix = "t"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -63,22 +66,25 @@ func totalSupply(ctx storage.Context) int {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkAccountKey creates DB key for account specified by concatenating accountPrefix
|
// mkAccountPrefix creates DB key-prefix for account tokens specified
|
||||||
// and account address.
|
// by concatenating accountPrefix and account address.
|
||||||
func mkAccountKey(holder interop.Hash160) []byte {
|
func mkAccountPrefix(holder interop.Hash160) []byte {
|
||||||
res := []byte(accountPrefix)
|
res := []byte(accountPrefix)
|
||||||
return append(res, holder...)
|
return append(res, holder...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkStringKey creates DB key for token specified by concatenating tokenPrefix
|
// mkBalanceKey creates DB key for account specified by concatenating balancePrefix
|
||||||
// and token ID.
|
// and account address.
|
||||||
func mkTokenKey(token []byte) []byte {
|
func mkBalanceKey(holder interop.Hash160) []byte {
|
||||||
res := []byte(tokenPrefix)
|
res := []byte(balancePrefix)
|
||||||
return append(res, token...)
|
return append(res, holder...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkTokensKey() []byte {
|
// mkTokenKey creates DB key for token specified by concatenating tokenPrefix
|
||||||
return []byte(tokensPrefix)
|
// and token ID.
|
||||||
|
func mkTokenKey(tokenID []byte) []byte {
|
||||||
|
res := []byte(tokenPrefix)
|
||||||
|
return append(res, tokenID...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceOf returns the number of tokens owned by specified address.
|
// BalanceOf returns the number of tokens owned by specified address.
|
||||||
|
@ -87,64 +93,48 @@ func BalanceOf(holder interop.Hash160) int {
|
||||||
panic("bad owner address")
|
panic("bad owner address")
|
||||||
}
|
}
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
tokens := getTokensOf(ctx, holder)
|
return getBalanceOf(ctx, mkBalanceKey(holder))
|
||||||
return len(tokens)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTokensOf is an internal implementation of TokensOf, tokens are stored
|
// getBalanceOf returns balance of the account using database key.
|
||||||
// as a serialized slice of strings in the DB, so it gets and unwraps them
|
func getBalanceOf(ctx storage.Context, balanceKey []byte) int {
|
||||||
// (or returns an empty slice).
|
val := storage.Get(ctx, balanceKey)
|
||||||
func getTokensOf(ctx storage.Context, holder interop.Hash160) []string {
|
|
||||||
var res = []string{}
|
|
||||||
|
|
||||||
key := mkAccountKey(holder)
|
|
||||||
val := storage.Get(ctx, key)
|
|
||||||
if val != nil {
|
if val != nil {
|
||||||
res = std.Deserialize(val.([]byte)).([]string)
|
return val.(int)
|
||||||
}
|
}
|
||||||
return res
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTokensOf saves current tokens owned by account if there are any,
|
// addToBalance adds amount to the account balance. Amount can be negative.
|
||||||
// otherwise it just drops the appropriate key from the DB.
|
func addToBalance(ctx storage.Context, holder interop.Hash160, amount int) {
|
||||||
func setTokensOf(ctx storage.Context, holder interop.Hash160, tokens []string) {
|
key := mkBalanceKey(holder)
|
||||||
key := mkAccountKey(holder)
|
old := getBalanceOf(ctx, key)
|
||||||
if len(tokens) != 0 {
|
old += amount
|
||||||
val := std.Serialize(tokens)
|
if old > 0 {
|
||||||
storage.Put(ctx, key, val)
|
storage.Put(ctx, key, old)
|
||||||
} else {
|
} else {
|
||||||
storage.Delete(ctx, key)
|
storage.Delete(ctx, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTokens saves minted token if it is not saved yet.
|
// addToken adds token to the account.
|
||||||
func setTokens(ctx storage.Context, newToken string) {
|
func addToken(ctx storage.Context, holder interop.Hash160, token []byte) {
|
||||||
key := mkTokensKey()
|
key := mkAccountPrefix(holder)
|
||||||
var tokens = []string{}
|
storage.Put(ctx, append(key, token...), token)
|
||||||
val := storage.Get(ctx, key)
|
}
|
||||||
if val != nil {
|
|
||||||
tokens = std.Deserialize(val.([]byte)).([]string)
|
// removeToken removes token from the account.
|
||||||
}
|
func removeToken(ctx storage.Context, holder interop.Hash160, token []byte) {
|
||||||
for i := 0; i < len(tokens); i++ {
|
key := mkAccountPrefix(holder)
|
||||||
if util.Equals(tokens[i], newToken) {
|
storage.Delete(ctx, append(key, token...))
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens = append(tokens, newToken)
|
|
||||||
val = std.Serialize(tokens)
|
|
||||||
storage.Put(ctx, key, val)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tokens returns an iterator that contains all of the tokens minted by the contract.
|
// Tokens returns an iterator that contains all of the tokens minted by the contract.
|
||||||
func Tokens() iterator.Iterator {
|
func Tokens() iterator.Iterator {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
var arr = []string{}
|
key := []byte(tokenPrefix)
|
||||||
key := mkTokensKey()
|
iter := storage.Find(ctx, key, storage.RemovePrefix|storage.KeysOnly)
|
||||||
val := storage.Get(ctx, key)
|
return iter
|
||||||
if val != nil {
|
|
||||||
arr = std.Deserialize(val.([]byte)).([]string)
|
|
||||||
}
|
|
||||||
return iterator.Create(arr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokensOf returns an iterator with all tokens held by specified address.
|
// TokensOf returns an iterator with all tokens held by specified address.
|
||||||
|
@ -153,9 +143,9 @@ func TokensOf(holder interop.Hash160) iterator.Iterator {
|
||||||
panic("bad owner address")
|
panic("bad owner address")
|
||||||
}
|
}
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
tokens := getTokensOf(ctx, holder)
|
key := mkAccountPrefix(holder)
|
||||||
|
iter := storage.Find(ctx, key, storage.ValuesOnly)
|
||||||
return iterator.Create(tokens)
|
return iter
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOwnerOf returns current owner of the specified token or panics if token
|
// getOwnerOf returns current owner of the specified token or panics if token
|
||||||
|
@ -197,18 +187,11 @@ func Transfer(to interop.Hash160, token []byte, data interface{}) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(owner) != string(to) {
|
if string(owner) != string(to) {
|
||||||
toksOwner := getTokensOf(ctx, owner)
|
addToBalance(ctx, owner, -1)
|
||||||
toksTo := getTokensOf(ctx, to)
|
removeToken(ctx, owner, token)
|
||||||
|
|
||||||
var newToksOwner = []string{}
|
addToBalance(ctx, to, 1)
|
||||||
for _, tok := range toksOwner {
|
addToken(ctx, to, token)
|
||||||
if tok != string(token) {
|
|
||||||
newToksOwner = append(newToksOwner, tok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toksTo = append(toksTo, string(token))
|
|
||||||
setTokensOf(ctx, owner, newToksOwner)
|
|
||||||
setTokensOf(ctx, to, toksTo)
|
|
||||||
setOwnerOf(ctx, token, to)
|
setOwnerOf(ctx, token, to)
|
||||||
}
|
}
|
||||||
postTransfer(owner, to, token, data)
|
postTransfer(owner, to, token, data)
|
||||||
|
@ -246,14 +229,12 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
||||||
tokIn = append(tokIn, std.Serialize(data)...)
|
tokIn = append(tokIn, std.Serialize(data)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenHash := crypto.Sha256(tokIn)
|
tokenHash := crypto.Ripemd160(tokIn)
|
||||||
token := std.Base58Encode(tokenHash)
|
token := std.Base58Encode(tokenHash)
|
||||||
|
|
||||||
toksOf := getTokensOf(ctx, from)
|
addToken(ctx, from, []byte(token))
|
||||||
toksOf = append(toksOf, token)
|
|
||||||
setTokensOf(ctx, from, toksOf)
|
|
||||||
setOwnerOf(ctx, []byte(token), from)
|
setOwnerOf(ctx, []byte(token), from)
|
||||||
setTokens(ctx, token)
|
addToBalance(ctx, from, 1)
|
||||||
|
|
||||||
total++
|
total++
|
||||||
storage.Put(ctx, []byte(totalSupplyPrefix), total)
|
storage.Put(ctx, []byte(totalSupplyPrefix), total)
|
||||||
|
@ -287,20 +268,8 @@ func Update(nef, manifest []byte) {
|
||||||
// Properties returns properties of the given NFT.
|
// Properties returns properties of the given NFT.
|
||||||
func Properties(id []byte) map[string]string {
|
func Properties(id []byte) map[string]string {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
var tokens = []string{}
|
owner := storage.Get(ctx, mkTokenKey(id)).(interop.Hash160)
|
||||||
key := mkTokensKey()
|
if owner == nil {
|
||||||
val := storage.Get(ctx, key)
|
|
||||||
if val != nil {
|
|
||||||
tokens = std.Deserialize(val.([]byte)).([]string)
|
|
||||||
}
|
|
||||||
var exists bool
|
|
||||||
for i := 0; i < len(tokens); i++ {
|
|
||||||
if util.Equals(tokens[i], id) {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
panic("unknown token")
|
panic("unknown token")
|
||||||
}
|
}
|
||||||
result := map[string]string{
|
result := map[string]string{
|
||||||
|
|
Loading…
Reference in a new issue