rpc: add NEP11 commands which return iterator

These are Tokens (optional), TokensOf and OwnerOf (divisible).
This commit is contained in:
Anna Shaleva 2021-04-28 17:47:44 +03:00
parent 9eeebf481c
commit e27c894338
4 changed files with 148 additions and 1 deletions

View file

@ -25,6 +25,7 @@ const (
totalSupplyPrefix = "s"
accountPrefix = "a"
tokenPrefix = "t"
tokensPrefix = "ts"
)
var (
@ -76,6 +77,10 @@ func mkTokenKey(token []byte) []byte {
return append(res, token...)
}
func mkTokensKey() []byte {
return []byte(tokensPrefix)
}
// BalanceOf returns the number of tokens owned by specified address.
func BalanceOf(holder interop.Hash160) int {
if len(holder) != 20 {
@ -112,6 +117,36 @@ func setTokensOf(ctx storage.Context, holder interop.Hash160, tokens []string) {
}
}
// setTokens saves minted token if it is not saved yet.
func setTokens(ctx storage.Context, newToken string) {
key := mkTokensKey()
var tokens = []string{}
val := storage.Get(ctx, key)
if val != nil {
tokens = std.Deserialize(val.([]byte)).([]string)
}
for i := 0; i < len(tokens); i++ {
if util.Equals(tokens[i], newToken) {
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.
func Tokens() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
var arr = []string{}
key := mkTokensKey()
val := storage.Get(ctx, key)
if val != nil {
arr = std.Deserialize(val.([]byte)).([]string)
}
return iterator.Create(arr)
}
// TokensOf returns an iterator with all tokens held by specified address.
func TokensOf(holder interop.Hash160) iterator.Iterator {
if len(holder) != 20 {
@ -219,6 +254,7 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
toksOf = append(toksOf, token)
setTokensOf(ctx, from, toksOf)
setOwnerOf(ctx, []byte(token), from)
setTokens(ctx, token)
total++
storage.Put(ctx, []byte(totalSupplyPrefix), total)

View file

@ -1,6 +1,6 @@
name: "HASHY NFT"
supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "tokens"]
events:
- name: Transfer
parameters:

View file

@ -95,3 +95,38 @@ func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) {
}
return st[index].(*stackitem.Map), nil
}
// topIterableFromStack returns top list of elements of `resultItemType` type from stack.
func topIterableFromStack(st []stackitem.Item, resultItemType interface{}) ([]interface{}, error) {
index := len(st) - 1 // top stack element is last in the array
if t := st[index].Type(); t != stackitem.InteropT {
return nil, fmt.Errorf("invalid return stackitem type: %s (InteropInterface expected)", t.String())
}
iter, ok := st[index].Value().(result.Iterator)
if !ok {
return nil, fmt.Errorf("failed to deserialize iterable from interop stackitem: invalid value type (Array expected)")
}
result := make([]interface{}, len(iter.Values))
for i := range iter.Values {
switch resultItemType.(type) {
case string:
bytes, err := iter.Values[i].TryBytes()
if err != nil {
return nil, fmt.Errorf("failed to deserialize string from stackitem #%d: %w", i, err)
}
result[i] = string(bytes)
case util.Uint160:
bytes, err := iter.Values[i].TryBytes()
if err != nil {
return nil, fmt.Errorf("failed to deserialize uint160 from stackitem #%d: %w", i, err)
}
result[i], err = util.Uint160DecodeBytesBE(bytes)
if err != nil {
return nil, fmt.Errorf("failed to decode uint160 from stackitem #%d: %w", i, err)
}
default:
return nil, errors.New("unsupported iterable type")
}
}
return result, nil
}

View file

@ -85,6 +85,33 @@ func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint1
}}, cosigners...))
}
// NEP11TokensOf returns an array of token IDs for the specified owner of the specified NFT token.
func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) ([]string, error) {
result, err := c.InvokeFunction(tokenHash, "tokensOf", []smartcontract.Parameter{
{
Type: smartcontract.Hash160Type,
Value: owner,
},
}, nil)
if err != nil {
return nil, err
}
err = getInvocationError(result)
if err != nil {
return nil, err
}
arr, err := topIterableFromStack(result.Stack, string(""))
if err != nil {
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
}
ids := make([]string, len(arr))
for i := range ids {
ids[i] = arr[i].(string)
}
return ids, nil
}
// Non-divisible NFT methods section start.
// NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP11 method with the
@ -138,6 +165,33 @@ func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string)
return c.nepBalanceOf(tokenHash, owner, &tokenID)
}
// NEP11DOwnerOf returns list of the specified NEP11 divisible token owners.
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID string) ([]util.Uint160, error) {
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
{
Type: smartcontract.StringType,
Value: tokenID,
},
}, nil)
if err != nil {
return nil, err
}
err = getInvocationError(result)
if err != nil {
return nil, err
}
arr, err := topIterableFromStack(result.Stack, util.Uint160{})
if err != nil {
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
}
owners := make([]util.Uint160, len(arr))
for i := range owners {
owners[i] = arr[i].(util.Uint160)
}
return owners, nil
}
// Divisible NFT methods section end.
// Optional NFT methods section start.
@ -160,4 +214,26 @@ func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stack
return topMapFromStack(result.Stack)
}
// NEP11Tokens returns list of the tokens minted by the contract.
func (c *Client) NEP11Tokens(tokenHash util.Uint160) ([]string, error) {
result, err := c.InvokeFunction(tokenHash, "tokens", []smartcontract.Parameter{}, nil)
if err != nil {
return nil, err
}
err = getInvocationError(result)
if err != nil {
return nil, err
}
arr, err := topIterableFromStack(result.Stack, string(""))
if err != nil {
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
}
tokens := make([]string, len(arr))
for i := range tokens {
tokens[i] = arr[i].(string)
}
return tokens, nil
}
// Optional NFT methods section end.