mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-26 19:43:48 +00:00
rpcclient: provide nep11 package for NEP-11 tokens
Unfortunately Go doesn't allow to easily reuse readers in full packages, still we can have this wrapper with a little overhead (the alternative is to move specific methods into types of their own, but I'm not sure how it's going to be accepted user-side).
This commit is contained in:
parent
d0702c2cf9
commit
194933a5cc
10 changed files with 1144 additions and 45 deletions
|
@ -5,16 +5,20 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
|
@ -23,6 +27,7 @@ import (
|
|||
)
|
||||
|
||||
func newNEP11Commands() []cli.Command {
|
||||
maxIters := strconv.Itoa(config.DefaultMaxIteratorResultItems)
|
||||
tokenAddressFlag := flags.AddressFlag{
|
||||
Name: "token",
|
||||
Usage: "Token contract address or hash in LE",
|
||||
|
@ -119,7 +124,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "ownerOfD",
|
||||
Usage: "print set of owners of divisible NEP-11 token with the specified ID (the default MaxIteratorResultItems will be printed at max)",
|
||||
Usage: "print set of owners of divisible NEP-11 token with the specified ID (" + maxIters + " will be printed at max)",
|
||||
UsageText: "ownerOfD --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>",
|
||||
Action: printNEP11DOwner,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -129,7 +134,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "tokensOf",
|
||||
Usage: "print list of tokens IDs for the specified NFT owner (the default MaxIteratorResultItems will be printed at max)",
|
||||
Usage: "print list of tokens IDs for the specified NFT owner (" + maxIters + " will be printed at max)",
|
||||
UsageText: "tokensOf --rpc-endpoint <node> --timeout <time> --token <hash> --address <addr>",
|
||||
Action: printNEP11TokensOf,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -139,7 +144,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "tokens",
|
||||
Usage: "print list of tokens IDs minted by the specified NFT (optional method; the default MaxIteratorResultItems will be printed at max)",
|
||||
Usage: "print list of tokens IDs minted by the specified NFT (optional method; " + maxIters + " will be printed at max)",
|
||||
UsageText: "tokens --rpc-endpoint <node> --timeout <time> --token <hash>",
|
||||
Action: printNEP11Tokens,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -211,6 +216,8 @@ func getNEP11Balance(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
}
|
||||
// Always initialize divisible token to be able to use both balanceOf methods.
|
||||
n11 := nep11.NewDivisibleReader(invoker.New(c, nil), token.Hash)
|
||||
|
||||
tokenID := ctx.String("id")
|
||||
tokenIDBytes, err := hex.DecodeString(tokenID)
|
||||
|
@ -228,16 +235,16 @@ func getNEP11Balance(ctx *cli.Context) error {
|
|||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||
|
||||
var amount int64
|
||||
var amount *big.Int
|
||||
if len(tokenIDBytes) == 0 {
|
||||
amount, err = c.NEP11BalanceOf(token.Hash, addrHash)
|
||||
amount, err = n11.BalanceOf(addrHash)
|
||||
} else {
|
||||
amount, err = c.NEP11DBalanceOf(token.Hash, addrHash, tokenIDBytes)
|
||||
amount, err = n11.BalanceOfD(addrHash, tokenIDBytes)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
amountStr := fixedn.ToString(big.NewInt(amount), int(token.Decimals))
|
||||
amountStr := fixedn.ToString(amount, int(token.Decimals))
|
||||
|
||||
format := "%s: %s (%s)\n"
|
||||
formatArgs := []interface{}{token.Symbol, token.Name, token.Hash.StringLE()}
|
||||
|
@ -270,9 +277,9 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("bad account address: %w", err), 1)
|
||||
}
|
||||
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, from, to, amount, tokenID, data)
|
||||
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, from, to, amount, tokenID, data) //nolint:staticcheck // SA1019: c.CreateNEP11TransferTx is deprecated
|
||||
} else {
|
||||
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, to, tokenID, data)
|
||||
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, to, tokenID, data) //nolint:staticcheck // SA1019: c.CreateNEP11TransferTx is deprecated
|
||||
}
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
|
@ -346,7 +353,8 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
|
|||
}
|
||||
|
||||
if divisible {
|
||||
result, err := c.NEP11DUnpackedOwnerOf(tokenHash.Uint160(), tokenIDBytes)
|
||||
n11 := nep11.NewDivisibleReader(invoker.New(c, nil), tokenHash.Uint160())
|
||||
result, err := n11.OwnerOfExpanded(tokenIDBytes, config.DefaultMaxIteratorResultItems)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 divisible `ownerOf` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
@ -354,7 +362,8 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
|
|||
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(h))
|
||||
}
|
||||
} else {
|
||||
result, err := c.NEP11NDOwnerOf(tokenHash.Uint160(), tokenIDBytes)
|
||||
n11 := nep11.NewNonDivisibleReader(invoker.New(c, nil), tokenHash.Uint160())
|
||||
result, err := n11.OwnerOf(tokenIDBytes)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 non-divisible `ownerOf` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
@ -384,7 +393,8 @@ func printNEP11TokensOf(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
result, err := c.NEP11UnpackedTokensOf(tokenHash.Uint160(), acc.Uint160())
|
||||
n11 := nep11.NewBaseReader(invoker.New(c, nil), tokenHash.Uint160())
|
||||
result, err := n11.TokensOfExpanded(acc.Uint160(), config.DefaultMaxIteratorResultItems)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `tokensOf` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
@ -413,7 +423,8 @@ func printNEP11Tokens(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
result, err := c.NEP11UnpackedTokens(tokenHash.Uint160())
|
||||
n11 := nep11.NewBaseReader(invoker.New(c, nil), tokenHash.Uint160())
|
||||
result, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call optional NEP-11 `tokens` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
@ -451,7 +462,8 @@ func printNEP11Properties(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
result, err := c.NEP11Properties(tokenHash.Uint160(), tokenIDBytes)
|
||||
n11 := nep11.NewBaseReader(invoker.New(c, nil), tokenHash.Uint160())
|
||||
result, err := n11.Properties(tokenIDBytes)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
|
|
@ -17,21 +17,33 @@ import (
|
|||
)
|
||||
|
||||
// NEP11Decimals invokes `decimals` NEP-11 method on the specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11Decimals(tokenHash util.Uint160) (int64, error) {
|
||||
return c.nepDecimals(tokenHash)
|
||||
}
|
||||
|
||||
// NEP11Symbol invokes `symbol` NEP-11 method on the specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11Symbol(tokenHash util.Uint160) (string, error) {
|
||||
return c.nepSymbol(tokenHash)
|
||||
}
|
||||
|
||||
// NEP11TotalSupply invokes `totalSupply` NEP-11 method on the specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11TotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||
return c.nepTotalSupply(tokenHash)
|
||||
}
|
||||
|
||||
// NEP11BalanceOf invokes `balanceOf` NEP-11 method on the specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) {
|
||||
return c.nepBalanceOf(tokenHash, owner, nil)
|
||||
}
|
||||
|
@ -44,6 +56,9 @@ func (c *Client) NEP11TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
|
|||
// TransferNEP11 creates an invocation transaction that invokes 'transfer' method
|
||||
// on the given token to move the whole NEP-11 token with the specified token ID to
|
||||
// the given account and sends it to the network returning just a hash of it.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
|
||||
tokenHash util.Uint160, tokenID string, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) {
|
||||
tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, to, tokenID, data)
|
||||
|
@ -61,6 +76,9 @@ func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
|
|||
// helper for TransferNEP11 and TransferNEP11D.
|
||||
// `args` for TransferNEP11: to util.Uint160, tokenID string, data interface{};
|
||||
// `args` for TransferNEP11D: from, to util.Uint160, amount int64, tokenID string, data interface{}.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint160,
|
||||
gas int64, cosigners []SignerAccount, args ...interface{}) (*transaction.Transaction, error) {
|
||||
script, err := smartcontract.CreateCallWithAssertScript(tokenHash, "transfer", args...)
|
||||
|
@ -85,6 +103,9 @@ func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint1
|
|||
// Iterator itself, the third one is an error. Use TraverseIterator method to
|
||||
// traverse iterator values or TerminateSession to terminate opened iterator
|
||||
// session. See TraverseIterator and TerminateSession documentation for more details.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid.UUID, result.Iterator, error) {
|
||||
return unwrap.SessionIterator(c.reader.Call(tokenHash, "tokensOf", owner))
|
||||
}
|
||||
|
@ -93,6 +114,9 @@ func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid
|
|||
// (config.DefaultMaxIteratorResultItems at max). It differs from NEP11TokensOf in that no iterator session
|
||||
// is used to retrieve values from iterator. Instead, unpacking VM script is created and invoked via
|
||||
// `invokescript` JSON-RPC call.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint160) ([][]byte, error) {
|
||||
return unwrap.ArrayOfBytes(c.reader.CallAndExpandIterator(tokenHash, "tokensOf", config.DefaultMaxIteratorResultItems, owner))
|
||||
}
|
||||
|
@ -101,6 +125,9 @@ func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint16
|
|||
|
||||
// NEP11NDOwnerOf invokes `ownerOf` non-divisible NEP-11 method with the
|
||||
// specified token ID on the specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID []byte) (util.Uint160, error) {
|
||||
return unwrap.Uint160(c.reader.Call(tokenHash, "ownerOf", tokenID))
|
||||
}
|
||||
|
@ -113,6 +140,9 @@ func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID []byte) (util.Ui
|
|||
// method on the given token to move the specified amount of divisible NEP-11 assets
|
||||
// (in FixedN format using contract's number of decimals) to the given account and
|
||||
// sends it to the network returning just a hash of it.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
|
||||
tokenHash util.Uint160, amount int64, tokenID []byte, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) {
|
||||
from, err := address.StringToUint160(acc.Address)
|
||||
|
@ -129,6 +159,9 @@ func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
|
|||
|
||||
// NEP11DBalanceOf invokes `balanceOf` divisible NEP-11 method on a
|
||||
// specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID []byte) (int64, error) {
|
||||
return c.nepBalanceOf(tokenHash, owner, tokenID)
|
||||
}
|
||||
|
@ -137,6 +170,9 @@ func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID []byte)
|
|||
// is the session ID, the second one is Iterator itself, the third one is an error. Use TraverseIterator
|
||||
// method to traverse iterator values or TerminateSession to terminate opened iterator session. See
|
||||
// TraverseIterator and TerminateSession documentation for more details.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUID, result.Iterator, error) {
|
||||
return unwrap.SessionIterator(c.reader.Call(tokenHash, "ownerOf", tokenID))
|
||||
}
|
||||
|
@ -145,6 +181,9 @@ func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUI
|
|||
// (config.DefaultMaxIteratorResultItems at max). It differs from NEP11DOwnerOf in that no
|
||||
// iterator session is used to retrieve values from iterator. Instead, unpacking VM
|
||||
// script is created and invoked via `invokescript` JSON-RPC call.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) ([]util.Uint160, error) {
|
||||
arr, err := unwrap.ArrayOfBytes(c.reader.CallAndExpandIterator(tokenHash, "ownerOf", config.DefaultMaxIteratorResultItems, tokenID))
|
||||
if err != nil {
|
||||
|
@ -166,6 +205,9 @@ func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) (
|
|||
|
||||
// NEP11Properties invokes `properties` optional NEP-11 method on the
|
||||
// specified contract.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID []byte) (*stackitem.Map, error) {
|
||||
return unwrap.Map(c.reader.Call(tokenHash, "properties", tokenID))
|
||||
}
|
||||
|
@ -175,6 +217,9 @@ func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID []byte) (*stack
|
|||
// error. Use TraverseIterator method to traverse iterator values or
|
||||
// TerminateSession to terminate opened iterator session. See TraverseIterator and
|
||||
// TerminateSession documentation for more details.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11Tokens(tokenHash util.Uint160) (uuid.UUID, result.Iterator, error) {
|
||||
return unwrap.SessionIterator(c.reader.Call(tokenHash, "tokens"))
|
||||
}
|
||||
|
@ -183,6 +228,9 @@ func (c *Client) NEP11Tokens(tokenHash util.Uint160) (uuid.UUID, result.Iterator
|
|||
// (config.DefaultMaxIteratorResultItems at max). It differs from NEP11Tokens in that no
|
||||
// iterator session is used to retrieve values from iterator. Instead, unpacking
|
||||
// VM script is created and invoked via `invokescript` JSON-RPC call.
|
||||
//
|
||||
// Deprecated: please use nep11 package, this method will be removed in future
|
||||
// versions.
|
||||
func (c *Client) NEP11UnpackedTokens(tokenHash util.Uint160) ([][]byte, error) {
|
||||
return unwrap.ArrayOfBytes(c.reader.CallAndExpandIterator(tokenHash, "tokens", config.DefaultMaxIteratorResultItems))
|
||||
}
|
||||
|
|
247
pkg/rpcclient/nep11/base.go
Normal file
247
pkg/rpcclient/nep11/base.go
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
Package nep11 contains RPC wrappers for NEP-11 contracts.
|
||||
|
||||
The set of types provided is split between common NEP-11 methods (BaseReader and
|
||||
Base types) and divisible (DivisibleReader and Divisible) and non-divisible
|
||||
(NonDivisibleReader and NonDivisible). If you don't know the type of NEP-11
|
||||
contract you're going to use you can use Base and BaseReader types for many
|
||||
purposes, otherwise more specific types are recommended.
|
||||
*/
|
||||
package nep11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neptoken"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// Invoker is used by reader types to call various methods.
|
||||
type Invoker interface {
|
||||
neptoken.Invoker
|
||||
|
||||
CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error)
|
||||
TerminateSession(sessionID uuid.UUID) error
|
||||
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
|
||||
}
|
||||
|
||||
// Actor is used by complete NEP-11 types to create and send transactions.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// BaseReader is a reader interface for common divisible and non-divisible NEP-11
|
||||
// methods. It allows to invoke safe methods.
|
||||
type BaseReader struct {
|
||||
neptoken.Base
|
||||
|
||||
invoker Invoker
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// Base is a state-changing interface for common divisible and non-divisible NEP-11
|
||||
// methods.
|
||||
type Base struct {
|
||||
BaseReader
|
||||
|
||||
actor Actor
|
||||
}
|
||||
|
||||
// TransferEvent represents a Transfer event as defined in the NEP-11 standard.
|
||||
type TransferEvent struct {
|
||||
From util.Uint160
|
||||
To util.Uint160
|
||||
Amount *big.Int
|
||||
ID []byte
|
||||
}
|
||||
|
||||
// TokenIterator is used for iterating over TokensOf results.
|
||||
type TokenIterator struct {
|
||||
client Invoker
|
||||
session uuid.UUID
|
||||
iterator result.Iterator
|
||||
}
|
||||
|
||||
// NewBaseReader creates an instance of BaseReader for a contract with the given
|
||||
// hash using the given invoker.
|
||||
func NewBaseReader(invoker Invoker, hash util.Uint160) *BaseReader {
|
||||
return &BaseReader{*neptoken.New(invoker, hash), invoker, hash}
|
||||
}
|
||||
|
||||
// NewBase creates an instance of Base for contract with the given
|
||||
// hash using the given actor.
|
||||
func NewBase(actor Actor, hash util.Uint160) *Base {
|
||||
return &Base{*NewBaseReader(actor, hash), actor}
|
||||
}
|
||||
|
||||
// BalanceOf returns the number of NFTs owned by the given account. For divisible
|
||||
// NFTs that's the sum of all parts of tokens.
|
||||
func (t *BaseReader) BalanceOf(account util.Uint160) (*big.Int, error) {
|
||||
return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", account))
|
||||
}
|
||||
|
||||
// Properties returns a set of token's properties such as name or URL. The map
|
||||
// is returned as is from this method (stack item) for maximum flexibility,
|
||||
// contracts can return a lot of specific data there. Most of the time though
|
||||
// they return well-defined properties outlined in NEP-11 and
|
||||
// UnwrapKnownProperties can be used to get them in more convenient way. It's an
|
||||
// optional method per NEP-11 specification, so it can fail.
|
||||
func (t *BaseReader) Properties(token []byte) (*stackitem.Map, error) {
|
||||
return unwrap.Map(t.invoker.Call(t.hash, "properties", token))
|
||||
}
|
||||
|
||||
// Tokens returns an iterator that allows to retrieve all tokens minted by the
|
||||
// contract. It depends on the server to provide proper session-based
|
||||
// iterator, but can also work with expanded one. The method itself is optional
|
||||
// per NEP-11 specification, so it can fail.
|
||||
func (t *BaseReader) Tokens() (*TokenIterator, error) {
|
||||
sess, iter, err := unwrap.SessionIterator(t.invoker.Call(t.hash, "tokens"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TokenIterator{t.invoker, sess, iter}, nil
|
||||
}
|
||||
|
||||
// TokensExpanded uses the same NEP-11 method as Tokens, but can be useful if
|
||||
// the server used doesn't support sessions and doesn't expand iterators. It
|
||||
// creates a script that will get num of result items from the iterator right in
|
||||
// the VM and return them to you. It's only limited by VM stack and GAS available
|
||||
// for RPC invocations.
|
||||
func (t *BaseReader) TokensExpanded(num int) ([][]byte, error) {
|
||||
return unwrap.ArrayOfBytes(t.invoker.CallAndExpandIterator(t.hash, "tokens", num))
|
||||
}
|
||||
|
||||
// TokensOf returns an iterator that allows to walk through all tokens owned by
|
||||
// the given account. It depends on the server to provide proper session-based
|
||||
// iterator, but can also work with expanded one.
|
||||
func (t *BaseReader) TokensOf(account util.Uint160) (*TokenIterator, error) {
|
||||
sess, iter, err := unwrap.SessionIterator(t.invoker.Call(t.hash, "tokensOf", account))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TokenIterator{t.invoker, sess, iter}, nil
|
||||
}
|
||||
|
||||
// TokensOfExpanded uses the same NEP-11 method as TokensOf, but can be useful if
|
||||
// the server used doesn't support sessions and doesn't expand iterators. It
|
||||
// creates a script that will get num of result items from the iterator right in
|
||||
// the VM and return them to you. It's only limited by VM stack and GAS available
|
||||
// for RPC invocations.
|
||||
func (t *BaseReader) TokensOfExpanded(account util.Uint160, num int) ([][]byte, error) {
|
||||
return unwrap.ArrayOfBytes(t.invoker.CallAndExpandIterator(t.hash, "tokensOf", num, account))
|
||||
}
|
||||
|
||||
// Transfer creates and sends a transaction that performs a `transfer` method
|
||||
// call using the given parameters and checks for this call result, failing the
|
||||
// transaction if it's not true. It works for divisible NFTs only when there is
|
||||
// one owner for the particular token. The returned values are transaction hash,
|
||||
// its ValidUntilBlock value and an error if any.
|
||||
func (t *Base) Transfer(to util.Uint160, id []byte, data interface{}) (util.Uint256, uint32, error) {
|
||||
script, err := t.transferScript(to, id, data)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return t.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// TransferTransaction creates a transaction that performs a `transfer` method
|
||||
// call using the given parameters and checks for this call result, failing the
|
||||
// transaction if it's not true. It works for divisible NFTs only when there is
|
||||
// one owner for the particular token. This transaction is signed, but not sent
|
||||
// to the network, instead it's returned to the caller.
|
||||
func (t *Base) TransferTransaction(to util.Uint160, id []byte, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := t.transferScript(to, id, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// TransferUnsigned creates a transaction that performs a `transfer` method
|
||||
// call using the given parameters and checks for this call result, failing the
|
||||
// transaction if it's not true. It works for divisible NFTs only when there is
|
||||
// one owner for the particular token. This transaction is not signed and just
|
||||
// returned to the caller.
|
||||
func (t *Base) TransferUnsigned(to util.Uint160, id []byte, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := t.transferScript(to, id, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
func (t *Base) transferScript(params ...interface{}) ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(t.hash, "transfer", params...)
|
||||
}
|
||||
|
||||
// Next returns the next set of elements from the iterator (up to num of them).
|
||||
// It can return less than num elements in case iterator doesn't have that many
|
||||
// or zero elements if the iterator has no more elements or the session is
|
||||
// expired.
|
||||
func (v *TokenIterator) Next(num int) ([][]byte, error) {
|
||||
items, err := v.client.TraverseIterator(v.session, &v.iterator, num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([][]byte, len(items))
|
||||
for i := range items {
|
||||
b, err := items[i].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("element %d is not a byte string: %w", i, err)
|
||||
}
|
||||
res[i] = b
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Terminate closes the iterator session used by TokenIterator (if it's
|
||||
// session-based).
|
||||
func (v *TokenIterator) Terminate() error {
|
||||
if v.iterator.ID == nil {
|
||||
return nil
|
||||
}
|
||||
return v.client.TerminateSession(v.session)
|
||||
}
|
||||
|
||||
// UnwrapKnownProperties can be used as a proxy function to extract well-known
|
||||
// NEP-11 properties (name/description/image/tokenURI) defined in the standard.
|
||||
// These properties are checked to be valid UTF-8 strings, but can contain
|
||||
// control codes or special characters.
|
||||
func UnwrapKnownProperties(m *stackitem.Map, err error) (map[string]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems := m.Value().([]stackitem.MapElement)
|
||||
res := make(map[string]string)
|
||||
for _, e := range elems {
|
||||
k, err := e.Key.TryBytes()
|
||||
if err != nil { // Shouldn't ever happen in the valid Map, but.
|
||||
continue
|
||||
}
|
||||
ks := string(k)
|
||||
if !result.KnownNEP11Properties[ks] { // Some additional elements are OK.
|
||||
continue
|
||||
}
|
||||
v, err := e.Value.TryBytes()
|
||||
if err != nil { // But known ones MUST be proper strings.
|
||||
return nil, fmt.Errorf("invalid %s property: %w", ks, err)
|
||||
}
|
||||
if !utf8.Valid(v) {
|
||||
return nil, fmt.Errorf("invalid %s property: not a UTF-8 string", ks)
|
||||
}
|
||||
res[ks] = string(v)
|
||||
}
|
||||
return res, nil
|
||||
}
|
305
pkg/rpcclient/nep11/base_test.go
Normal file
305
pkg/rpcclient/nep11/base_test.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
package nep11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testAct struct {
|
||||
err error
|
||||
res *result.Invoke
|
||||
tx *transaction.Transaction
|
||||
txh util.Uint256
|
||||
vub uint32
|
||||
}
|
||||
|
||||
func (t *testAct) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||
return t.res, t.err
|
||||
}
|
||||
func (t *testAct) MakeRun(script []byte) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) SendRun(script []byte) (util.Uint256, uint32, error) {
|
||||
return t.txh, t.vub, t.err
|
||||
}
|
||||
func (t *testAct) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) {
|
||||
return t.res, t.err
|
||||
}
|
||||
func (t *testAct) TerminateSession(sessionID uuid.UUID) error {
|
||||
return t.err
|
||||
}
|
||||
func (t *testAct) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) {
|
||||
return t.res.Stack, t.err
|
||||
}
|
||||
|
||||
func TestReaderBalanceOf(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewBaseReader(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := tr.BalanceOf(util.Uint160{3, 2, 1})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}
|
||||
bal, err := tr.BalanceOf(util.Uint160{3, 2, 1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(100500), bal)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = tr.BalanceOf(util.Uint160{3, 2, 1})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReaderProperties(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewBaseReader(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := tr.Properties([]byte{3, 2, 1})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = tr.Properties([]byte{3, 2, 1})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewMap(),
|
||||
},
|
||||
}
|
||||
m, err := tr.Properties([]byte{3, 2, 1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, m.Len())
|
||||
}
|
||||
|
||||
func TestReaderTokensOfExpanded(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewBaseReader(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for name, fun := range map[string]func(int) ([][]byte, error){
|
||||
"Tokens": tr.TokensExpanded,
|
||||
"TokensOf": func(n int) ([][]byte, error) {
|
||||
return tr.TokensOfExpanded(util.Uint160{1, 2, 3}, n)
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(1)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}
|
||||
_, err = fun(1)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{stackitem.Make("one")}),
|
||||
},
|
||||
}
|
||||
toks, err := fun(1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [][]byte{[]byte("one")}, toks)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderTokensOf(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewBaseReader(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for name, fun := range map[string]func() (*TokenIterator, error){
|
||||
"Tokens": tr.Tokens,
|
||||
"TokensOf": func() (*TokenIterator, error) {
|
||||
return tr.TokensOf(util.Uint160{1, 2, 3})
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun()
|
||||
require.Error(t, err)
|
||||
|
||||
iid := uuid.New()
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
Session: uuid.New(),
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
ID: &iid,
|
||||
}),
|
||||
},
|
||||
}
|
||||
iter, err := fun()
|
||||
require.NoError(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make("one"),
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = iter.Next(10)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make("one"),
|
||||
stackitem.Make("two"),
|
||||
},
|
||||
}
|
||||
vals, err := iter.Next(10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [][]byte{[]byte("one"), []byte("two")}, vals)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err = iter.Next(1)
|
||||
require.Error(t, err)
|
||||
|
||||
err = iter.Terminate()
|
||||
require.Error(t, err)
|
||||
|
||||
// Value-based iterator.
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
Values: []stackitem.Item{
|
||||
stackitem.Make("one"),
|
||||
stackitem.Make("two"),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
iter, err = fun()
|
||||
require.NoError(t, err)
|
||||
|
||||
ta.err = errors.New("")
|
||||
err = iter.Terminate()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenTransfer(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tok := NewBase(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err := tok.Transfer(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
h, vub, err := tok.Transfer(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.Transfer(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTokenTransferTransaction(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tok := NewBase(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for _, fun := range []func(to util.Uint160, token []byte, data interface{}) (*transaction.Transaction, error){
|
||||
tok.TransferTransaction,
|
||||
tok.TransferUnsigned,
|
||||
} {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := fun(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnwrapKnownProperties(t *testing.T) {
|
||||
_, err := UnwrapKnownProperties(stackitem.NewMap(), errors.New(""))
|
||||
require.Error(t, err)
|
||||
|
||||
m, err := UnwrapKnownProperties(stackitem.NewMap(), nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, m)
|
||||
require.Equal(t, 0, len(m))
|
||||
|
||||
m, err = UnwrapKnownProperties(stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{Key: stackitem.Make("some"), Value: stackitem.Make("thing")},
|
||||
}), nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, m)
|
||||
require.Equal(t, 0, len(m))
|
||||
|
||||
m, err = UnwrapKnownProperties(stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{Key: stackitem.Make([]stackitem.Item{}), Value: stackitem.Make("thing")},
|
||||
}), nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, m)
|
||||
require.Equal(t, 0, len(m))
|
||||
|
||||
_, err = UnwrapKnownProperties(stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{Key: stackitem.Make("name"), Value: stackitem.Make([]stackitem.Item{})},
|
||||
}), nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = UnwrapKnownProperties(stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{Key: stackitem.Make("name"), Value: stackitem.Make([]byte{0xff})},
|
||||
}), nil)
|
||||
require.Error(t, err)
|
||||
|
||||
m, err = UnwrapKnownProperties(stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{Key: stackitem.Make("name"), Value: stackitem.Make("thing")},
|
||||
{Key: stackitem.Make("description"), Value: stackitem.Make("good NFT")},
|
||||
}), nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, m)
|
||||
require.Equal(t, 2, len(m))
|
||||
require.Equal(t, "thing", m["name"])
|
||||
require.Equal(t, "good NFT", m["description"])
|
||||
}
|
157
pkg/rpcclient/nep11/divisible.go
Normal file
157
pkg/rpcclient/nep11/divisible.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package nep11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// DivisibleReader is a reader interface for divisible NEP-11 contract.
|
||||
type DivisibleReader struct {
|
||||
BaseReader
|
||||
}
|
||||
|
||||
// Divisible is a state-changing interface for divisible NEP-11 contract.
|
||||
type Divisible struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// OwnerIterator is used for iterating over OwnerOf (for divisible NFTs) results.
|
||||
type OwnerIterator struct {
|
||||
client Invoker
|
||||
session uuid.UUID
|
||||
iterator result.Iterator
|
||||
}
|
||||
|
||||
// NewDivisibleReader creates an instance of DivisibleReader for a contract
|
||||
// with the given hash using the given invoker.
|
||||
func NewDivisibleReader(invoker Invoker, hash util.Uint160) *DivisibleReader {
|
||||
return &DivisibleReader{*NewBaseReader(invoker, hash)}
|
||||
}
|
||||
|
||||
// NewDivisible creates an instance of Divisible for a contract
|
||||
// with the given hash using the given actor.
|
||||
func NewDivisible(actor Actor, hash util.Uint160) *Divisible {
|
||||
return &Divisible{*NewBase(actor, hash)}
|
||||
}
|
||||
|
||||
// OwnerOf returns returns an iterator that allows to walk through all owners of
|
||||
// the given token. It depends on the server to provide proper session-based
|
||||
// iterator, but can also work with expanded one.
|
||||
func (t *DivisibleReader) OwnerOf(token []byte) (*OwnerIterator, error) {
|
||||
sess, iter, err := unwrap.SessionIterator(t.invoker.Call(t.hash, "ownerOf", token))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &OwnerIterator{t.invoker, sess, iter}, nil
|
||||
}
|
||||
|
||||
// OwnerOfExpanded uses the same NEP-11 method as OwnerOf, but can be useful if
|
||||
// the server used doesn't support sessions and doesn't expand iterators. It
|
||||
// creates a script that will get num of result items from the iterator right in
|
||||
// the VM and return them to you. It's only limited by VM stack and GAS available
|
||||
// for RPC invocations.
|
||||
func (t *DivisibleReader) OwnerOfExpanded(token []byte, num int) ([]util.Uint160, error) {
|
||||
return unwrap.ArrayOfUint160(t.invoker.CallAndExpandIterator(t.hash, "ownerOf", num, token))
|
||||
}
|
||||
|
||||
// BalanceOfD is a BalanceOf for divisible NFTs, it returns the amount of token
|
||||
// owned by a particular account.
|
||||
func (t *DivisibleReader) BalanceOfD(owner util.Uint160, token []byte) (*big.Int, error) {
|
||||
return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", owner, token))
|
||||
}
|
||||
|
||||
// OwnerOf is the same as (*DivisibleReader).OwnerOf.
|
||||
func (t *Divisible) OwnerOf(token []byte) (*OwnerIterator, error) {
|
||||
r := DivisibleReader{t.BaseReader}
|
||||
return r.OwnerOf(token)
|
||||
}
|
||||
|
||||
// OwnerOfExpanded is the same as (*DivisibleReader).OwnerOfExpanded.
|
||||
func (t *Divisible) OwnerOfExpanded(token []byte, num int) ([]util.Uint160, error) {
|
||||
r := DivisibleReader{t.BaseReader}
|
||||
return r.OwnerOfExpanded(token, num)
|
||||
}
|
||||
|
||||
// BalanceOfD is the same as (*DivisibleReader).BalanceOfD.
|
||||
func (t *Divisible) BalanceOfD(owner util.Uint160, token []byte) (*big.Int, error) {
|
||||
r := DivisibleReader{t.BaseReader}
|
||||
return r.BalanceOfD(owner, token)
|
||||
}
|
||||
|
||||
// TransferD is a divisible version of (*Base).Transfer, allowing to transfer a
|
||||
// part of NFT. It creates and sends a transaction that performs a `transfer`
|
||||
// method call using the given parameters and checks for this call result,
|
||||
// failing the transaction if it's not true. The returned values are transaction
|
||||
// hash, its ValidUntilBlock value and an error if any.
|
||||
func (t *Divisible) TransferD(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (util.Uint256, uint32, error) {
|
||||
script, err := t.transferScript(from, to, amount, id, data)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return t.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// TransferDTransaction is a divisible version of (*Base).TransferTransaction,
|
||||
// allowing to transfer a part of NFT. It creates a transaction that performs a
|
||||
// `transfer` method call using the given parameters and checks for this call
|
||||
// result, failing the transaction if it's not true. This transaction is signed,
|
||||
// but not sent to the network, instead it's returned to the caller.
|
||||
func (t *Divisible) TransferDTransaction(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := t.transferScript(from, to, amount, id, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// TransferDUnsigned is a divisible version of (*Base).TransferUnsigned,
|
||||
// allowing to transfer a part of NFT. It creates a transaction that performs a
|
||||
// `transfer` method call using the given parameters and checks for this call
|
||||
// result, failing the transaction if it's not true. This transaction is not
|
||||
// signed and just returned to the caller.
|
||||
func (t *Divisible) TransferDUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := t.transferScript(from, to, amount, id, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
// Next returns the next set of elements from the iterator (up to num of them).
|
||||
// It can return less than num elements in case iterator doesn't have that many
|
||||
// or zero elements if the iterator has no more elements or the session is
|
||||
// expired.
|
||||
func (v *OwnerIterator) Next(num int) ([]util.Uint160, error) {
|
||||
items, err := v.client.TraverseIterator(v.session, &v.iterator, num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]util.Uint160, len(items))
|
||||
for i := range items {
|
||||
b, err := items[i].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("element %d is not a byte string: %w", i, err)
|
||||
}
|
||||
u, err := util.Uint160DecodeBytesBE(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("element %d is not a uint160: %w", i, err)
|
||||
}
|
||||
res[i] = u
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Terminate closes the iterator session used by OwnerIterator (if it's
|
||||
// session-based).
|
||||
func (v *OwnerIterator) Terminate() error {
|
||||
if v.iterator.ID == nil {
|
||||
return nil
|
||||
}
|
||||
return v.client.TerminateSession(v.session)
|
||||
}
|
218
pkg/rpcclient/nep11/divisible_test.go
Normal file
218
pkg/rpcclient/nep11/divisible_test.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package nep11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDivisibleBalanceOf(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewDivisibleReader(ta, util.Uint160{1, 2, 3})
|
||||
tt := NewDivisible(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for name, fun := range map[string]func(util.Uint160, []byte) (*big.Int, error){
|
||||
"Reader": tr.BalanceOfD,
|
||||
"Full": tt.BalanceOfD,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(util.Uint160{3, 2, 1}, []byte{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}
|
||||
bal, err := fun(util.Uint160{3, 2, 1}, []byte{1, 2, 3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(100500), bal)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = fun(util.Uint160{3, 2, 1}, []byte{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDivisibleOwnerOfExpanded(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewDivisibleReader(ta, util.Uint160{1, 2, 3})
|
||||
tt := NewDivisible(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for name, fun := range map[string]func([]byte, int) ([]util.Uint160, error){
|
||||
"Reader": tr.OwnerOfExpanded,
|
||||
"Full": tt.OwnerOfExpanded,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun([]byte{1, 2, 3}, 1)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}
|
||||
_, err = fun([]byte{1, 2, 3}, 1)
|
||||
require.Error(t, err)
|
||||
|
||||
h := util.Uint160{3, 2, 1}
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{stackitem.Make(h.BytesBE())}),
|
||||
},
|
||||
}
|
||||
owls, err := fun([]byte{1, 2, 3}, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []util.Uint160{h}, owls)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDivisibleOwnerOf(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewDivisibleReader(ta, util.Uint160{1, 2, 3})
|
||||
tt := NewDivisible(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for name, fun := range map[string]func([]byte) (*OwnerIterator, error){
|
||||
"Reader": tr.OwnerOf,
|
||||
"Full": tt.OwnerOf,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun([]byte{1})
|
||||
require.Error(t, err)
|
||||
|
||||
iid := uuid.New()
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
Session: uuid.New(),
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
ID: &iid,
|
||||
}),
|
||||
},
|
||||
}
|
||||
iter, err := fun([]byte{1})
|
||||
require.NoError(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = iter.Next(10)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make("not uint160"),
|
||||
},
|
||||
}
|
||||
_, err = iter.Next(10)
|
||||
require.Error(t, err)
|
||||
|
||||
h1 := util.Uint160{1, 2, 3}
|
||||
h2 := util.Uint160{3, 2, 1}
|
||||
ta.res = &result.Invoke{
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(h1.BytesBE()),
|
||||
stackitem.Make(h2.BytesBE()),
|
||||
},
|
||||
}
|
||||
vals, err := iter.Next(10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []util.Uint160{h1, h2}, vals)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err = iter.Next(1)
|
||||
require.Error(t, err)
|
||||
|
||||
err = iter.Terminate()
|
||||
require.Error(t, err)
|
||||
|
||||
// Value-based iterator.
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
Values: []stackitem.Item{
|
||||
stackitem.Make(h1.BytesBE()),
|
||||
stackitem.Make(h2.BytesBE()),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
iter, err = fun([]byte{1})
|
||||
require.NoError(t, err)
|
||||
|
||||
ta.err = errors.New("")
|
||||
err = iter.Terminate()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDivisibleTransfer(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tok := NewDivisible(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err := tok.TransferD(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
h, vub, err := tok.TransferD(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.TransferD(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDivisibleTransferTransaction(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tok := NewDivisible(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for _, fun := range []func(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error){
|
||||
tok.TransferDTransaction,
|
||||
tok.TransferDUnsigned,
|
||||
} {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := fun(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
39
pkg/rpcclient/nep11/nondivisible.go
Normal file
39
pkg/rpcclient/nep11/nondivisible.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package nep11
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// NonDivisibleReader is a reader interface for non-divisble NEP-11 contract.
|
||||
type NonDivisibleReader struct {
|
||||
BaseReader
|
||||
}
|
||||
|
||||
// NonDivisible is a state-changing interface for non-divisble NEP-11 contract.
|
||||
type NonDivisible struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// NewNonDivisibleReader creates an instance of NonDivisibleReader for a contract
|
||||
// with the given hash using the given invoker.
|
||||
func NewNonDivisibleReader(invoker Invoker, hash util.Uint160) *NonDivisibleReader {
|
||||
return &NonDivisibleReader{*NewBaseReader(invoker, hash)}
|
||||
}
|
||||
|
||||
// NewNonDivisible creates an instance of NonDivisible for a contract
|
||||
// with the given hash using the given actor.
|
||||
func NewNonDivisible(actor Actor, hash util.Uint160) *NonDivisible {
|
||||
return &NonDivisible{*NewBase(actor, hash)}
|
||||
}
|
||||
|
||||
// OwnerOf returns the owner of the given NFT.
|
||||
func (t *NonDivisibleReader) OwnerOf(token []byte) (util.Uint160, error) {
|
||||
return unwrap.Uint160(t.invoker.Call(t.hash, "ownerOf", token))
|
||||
}
|
||||
|
||||
// OwnerOf is the same as (*NonDivisibleReader).OwnerOf.
|
||||
func (t *NonDivisible) OwnerOf(token []byte) (util.Uint160, error) {
|
||||
r := NonDivisibleReader{t.BaseReader}
|
||||
return r.OwnerOf(token)
|
||||
}
|
49
pkg/rpcclient/nep11/nondivisible_test.go
Normal file
49
pkg/rpcclient/nep11/nondivisible_test.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package nep11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNDOwnerOf(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tr := NewNonDivisibleReader(ta, util.Uint160{1, 2, 3})
|
||||
tt := NewNonDivisible(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for name, fun := range map[string]func([]byte) (util.Uint160, error){
|
||||
"Reader": tr.OwnerOf,
|
||||
"Full": tt.OwnerOf,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun([]byte{3, 2, 1})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}
|
||||
_, err = fun([]byte{3, 2, 1})
|
||||
require.Error(t, err)
|
||||
|
||||
own := util.Uint160{1, 2, 3}
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(own.BytesBE()),
|
||||
},
|
||||
}
|
||||
owl, err := fun([]byte{3, 2, 1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, own, owl)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -302,7 +302,7 @@ func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances,
|
|||
}
|
||||
|
||||
// GetNEP11Properties is a wrapper for getnep11properties RPC. We recommend using
|
||||
// NEP11Properties method instead of this to receive proper VM types and work with them.
|
||||
// nep11 package and Properties method there to receive proper VM types and work with them.
|
||||
// This method is provided mostly for the sake of completeness. For well-known
|
||||
// attributes like "description", "image", "name" and "tokenURI" it returns strings,
|
||||
// while for all others []byte (which can be nil).
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/oracle"
|
||||
|
@ -1221,20 +1222,24 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
|
||||
h, err := util.Uint160DecodeStringLE(nnsContractHash)
|
||||
require.NoError(t, err)
|
||||
acc := testchain.PrivateKeyByID(0).GetScriptHash()
|
||||
priv0 := testchain.PrivateKeyByID(0)
|
||||
act, err := actor.NewSimple(c, wallet.NewAccountFromPrivateKey(priv0))
|
||||
require.NoError(t, err)
|
||||
n11 := nep11.NewNonDivisible(act, h)
|
||||
acc := priv0.GetScriptHash()
|
||||
|
||||
t.Run("Decimals", func(t *testing.T) {
|
||||
d, err := c.NEP11Decimals(h)
|
||||
d, err := n11.Decimals()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, d) // non-divisible
|
||||
})
|
||||
t.Run("TotalSupply", func(t *testing.T) {
|
||||
s, err := c.NEP11TotalSupply(h)
|
||||
s, err := n11.TotalSupply()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, s) // the only `neo.com` of acc0
|
||||
require.EqualValues(t, big.NewInt(1), s) // the only `neo.com` of acc0
|
||||
})
|
||||
t.Run("Symbol", func(t *testing.T) {
|
||||
sym, err := c.NEP11Symbol(h)
|
||||
sym, err := n11.Symbol()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "NNS", sym)
|
||||
})
|
||||
|
@ -1250,17 +1255,31 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
}, tok)
|
||||
})
|
||||
t.Run("BalanceOf", func(t *testing.T) {
|
||||
b, err := c.NEP11BalanceOf(h, acc)
|
||||
b, err := n11.BalanceOf(acc)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, b)
|
||||
require.EqualValues(t, big.NewInt(1), b)
|
||||
})
|
||||
t.Run("OwnerOf", func(t *testing.T) {
|
||||
b, err := c.NEP11NDOwnerOf(h, []byte("neo.com"))
|
||||
b, err := n11.OwnerOf([]byte("neo.com"))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, acc, b)
|
||||
})
|
||||
t.Run("Tokens", func(t *testing.T) {
|
||||
iter, err := n11.Tokens()
|
||||
require.NoError(t, err)
|
||||
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(items))
|
||||
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
|
||||
require.NoError(t, iter.Terminate())
|
||||
})
|
||||
t.Run("TokensExpanded", func(t *testing.T) {
|
||||
items, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
|
||||
})
|
||||
t.Run("Properties", func(t *testing.T) {
|
||||
p, err := c.NEP11Properties(h, []byte("neo.com"))
|
||||
p, err := n11.Properties([]byte("neo.com"))
|
||||
require.NoError(t, err)
|
||||
blockRegisterDomain, err := chain.GetBlock(chain.GetHeaderHash(14)) // `neo.com` domain was registered in 14th block
|
||||
require.NoError(t, err)
|
||||
|
@ -1271,7 +1290,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
require.EqualValues(t, expected, p)
|
||||
})
|
||||
t.Run("Transfer", func(t *testing.T) {
|
||||
_, err := c.TransferNEP11(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)), testchain.PrivateKeyByID(1).GetScriptHash(), h, "neo.com", nil, 0, nil)
|
||||
_, _, err := n11.Transfer(testchain.PrivateKeyByID(1).GetScriptHash(), []byte("neo.com"), nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -1285,23 +1304,28 @@ func TestClient_NEP11_D(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
priv0 := testchain.PrivateKeyByID(0).GetScriptHash()
|
||||
pkey0 := testchain.PrivateKeyByID(0)
|
||||
priv0 := pkey0.GetScriptHash()
|
||||
priv1 := testchain.PrivateKeyByID(1).GetScriptHash()
|
||||
token1ID, err := hex.DecodeString(nfsoToken1ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
act, err := actor.NewSimple(c, wallet.NewAccountFromPrivateKey(pkey0))
|
||||
require.NoError(t, err)
|
||||
n11 := nep11.NewDivisible(act, nfsoHash)
|
||||
|
||||
t.Run("Decimals", func(t *testing.T) {
|
||||
d, err := c.NEP11Decimals(nfsoHash)
|
||||
d, err := n11.Decimals()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 2, d) // Divisible.
|
||||
})
|
||||
t.Run("TotalSupply", func(t *testing.T) {
|
||||
s, err := c.NEP11TotalSupply(nfsoHash)
|
||||
s, err := n11.TotalSupply()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, s) // the only NFSO of acc0
|
||||
require.EqualValues(t, big.NewInt(1), s) // the only NFSO of acc0
|
||||
})
|
||||
t.Run("Symbol", func(t *testing.T) {
|
||||
sym, err := c.NEP11Symbol(nfsoHash)
|
||||
sym, err := n11.Symbol()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "NFSO", sym)
|
||||
})
|
||||
|
@ -1317,29 +1341,31 @@ func TestClient_NEP11_D(t *testing.T) {
|
|||
}, tok)
|
||||
})
|
||||
t.Run("BalanceOf", func(t *testing.T) {
|
||||
b, err := c.NEP11BalanceOf(nfsoHash, priv0)
|
||||
b, err := n11.BalanceOf(priv0)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 80, b)
|
||||
require.EqualValues(t, big.NewInt(80), b)
|
||||
})
|
||||
t.Run("BalanceOfD", func(t *testing.T) {
|
||||
b, err := n11.BalanceOfD(priv0, token1ID)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, big.NewInt(80), b)
|
||||
})
|
||||
t.Run("OwnerOf", func(t *testing.T) {
|
||||
sessID, iter, err := c.NEP11DOwnerOf(nfsoHash, token1ID)
|
||||
iter, err := n11.OwnerOf(token1ID)
|
||||
require.NoError(t, err)
|
||||
items, err := c.TraverseIterator(sessID, *iter.ID, config.DefaultMaxIteratorResultItems)
|
||||
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(items))
|
||||
actual1, err := util.Uint160DecodeBytesBE(items[0].Value().([]byte))
|
||||
require.NoError(t, err)
|
||||
actual0, err := util.Uint160DecodeBytesBE(items[1].Value().([]byte))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []util.Uint160{priv1, priv0}, []util.Uint160{actual1, actual0})
|
||||
require.Equal(t, []util.Uint160{priv1, priv0}, items)
|
||||
require.NoError(t, iter.Terminate())
|
||||
})
|
||||
t.Run("UnpackedOwnerOf", func(t *testing.T) {
|
||||
b, err := c.NEP11DUnpackedOwnerOf(nfsoHash, token1ID)
|
||||
t.Run("OwnerOfExpanded", func(t *testing.T) {
|
||||
b, err := n11.OwnerOfExpanded(token1ID, config.DefaultMaxIteratorResultItems)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []util.Uint160{priv1, priv0}, b)
|
||||
})
|
||||
t.Run("Properties", func(t *testing.T) {
|
||||
p, err := c.NEP11Properties(nfsoHash, token1ID)
|
||||
p, err := n11.Properties(token1ID)
|
||||
require.NoError(t, err)
|
||||
expected := stackitem.NewMap()
|
||||
expected.Add(stackitem.Make([]byte("name")), stackitem.NewBuffer([]byte("NeoFS Object "+base64.StdEncoding.EncodeToString(token1ID))))
|
||||
|
@ -1348,9 +1374,7 @@ func TestClient_NEP11_D(t *testing.T) {
|
|||
require.EqualValues(t, expected, p)
|
||||
})
|
||||
t.Run("Transfer", func(t *testing.T) {
|
||||
_, err := c.TransferNEP11D(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)),
|
||||
testchain.PrivateKeyByID(1).GetScriptHash(),
|
||||
nfsoHash, 20, token1ID, nil, 0, nil)
|
||||
_, _, err := n11.TransferD(priv0, priv1, big.NewInt(20), token1ID, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue