examples: change NFT contract to use storage iterators

This commit is contained in:
Evgeniy Stratonikov 2021-05-05 17:48:40 +03:00
parent a4b54b2e8a
commit 721748acfd
2 changed files with 67 additions and 92 deletions

View file

@ -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())...)

View file

@ -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{