mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 23:42:23 +00:00
Merge pull request #2667 from nspcc-dev/rpc-nep-token-info
Drop NEPXXTokenInfo from the RPC client
This commit is contained in:
commit
314cd3341b
14 changed files with 493 additions and 234 deletions
|
@ -167,19 +167,35 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||
"--wallet", wall,
|
||||
"--address", nftOwnerAddr}
|
||||
checkBalanceResult := func(t *testing.T, acc string, amount string) {
|
||||
checkBalanceResult := func(t *testing.T, acc string, ids ...[]byte) {
|
||||
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
|
||||
e.checkNextLine(t, "^\\s*HASHY:\\s+HASHY NFT \\("+h.StringLE()+"\\)")
|
||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+amount+"$")
|
||||
|
||||
// Hashes can be ordered in any way, so make a regexp for them.
|
||||
var tokstring = "("
|
||||
for i, id := range ids {
|
||||
if i > 0 {
|
||||
tokstring += "|"
|
||||
}
|
||||
tokstring += hex.EncodeToString(id)
|
||||
}
|
||||
tokstring += ")"
|
||||
|
||||
for range ids {
|
||||
e.checkNextLine(t, "^\\s*Token: "+tokstring+"\\s*$")
|
||||
e.checkNextLine(t, "^\\s*Amount: 1\\s*$")
|
||||
e.checkNextLine(t, "^\\s*Updated: [0-9]+\\s*$")
|
||||
}
|
||||
e.checkEOF(t)
|
||||
}
|
||||
// balance check: by symbol, token is not imported
|
||||
e.RunWithError(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||
// balance check: excessive parameters
|
||||
e.RunWithError(t, append(cmdCheckBalance, "--token", h.StringLE(), "neo-go")...)
|
||||
// balance check: by hash, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, nftOwnerAddr, "1")
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||
|
||||
// import token
|
||||
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
||||
|
@ -189,14 +205,14 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// balance check: by symbol, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||
checkBalanceResult(t, nftOwnerAddr, "1")
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||
|
||||
// balance check: all accounts
|
||||
e.Run(t, "neo-go", "wallet", "nep11", "balance",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", wall,
|
||||
"--token", h.StringLE())
|
||||
checkBalanceResult(t, nftOwnerAddr, "1")
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||
|
||||
// remove token from wallet
|
||||
e.In.WriteString("y\r")
|
||||
|
@ -276,7 +292,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// balance check: several tokens, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, nftOwnerAddr, "2")
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID, tokenID1)
|
||||
|
||||
cmdTransfer := []string{
|
||||
"neo-go", "wallet", "nep11", "transfer",
|
||||
|
@ -304,7 +320,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// check balance after transfer
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID1)
|
||||
|
||||
// transfer: good, to NEP-11-Payable contract, with data
|
||||
verifyH := deployVerifyContract(t, e)
|
||||
|
@ -341,7 +357,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// check balance after transfer
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, nftOwnerAddr, "0")
|
||||
checkBalanceResult(t, nftOwnerAddr)
|
||||
}
|
||||
|
||||
func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||
|
@ -406,31 +422,42 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
require.Equal(t, base64.StdEncoding.EncodeToString(object1ID.BytesBE()), props["objectID"])
|
||||
e.checkEOF(t)
|
||||
|
||||
type idAmount struct {
|
||||
id string
|
||||
amount string
|
||||
}
|
||||
|
||||
// check the balance
|
||||
cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||
"--wallet", wall,
|
||||
"--address", validatorAddr}
|
||||
checkBalanceResult := func(t *testing.T, acc string, amount string, id []byte) {
|
||||
checkBalanceResult := func(t *testing.T, acc string, objs ...idAmount) {
|
||||
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
|
||||
if id == nil {
|
||||
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)")
|
||||
} else {
|
||||
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+", "+hex.EncodeToString(id)+"\\)")
|
||||
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)")
|
||||
|
||||
for _, o := range objs {
|
||||
e.checkNextLine(t, "^\\s*Token: "+o.id+"\\s*$")
|
||||
e.checkNextLine(t, "^\\s*Amount: "+o.amount+"\\s*$")
|
||||
e.checkNextLine(t, "^\\s*Updated: [0-9]+\\s*$")
|
||||
}
|
||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+amount+"$")
|
||||
e.checkEOF(t)
|
||||
}
|
||||
tokz := []idAmount{
|
||||
{hex.EncodeToString(token1ID), "1"},
|
||||
{hex.EncodeToString(token2ID), "1"},
|
||||
}
|
||||
// balance check: by symbol, token is not imported
|
||||
e.RunWithError(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||
checkBalanceResult(t, validatorAddr, tokz...)
|
||||
|
||||
// overall NFSO balance check: by hash, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, validatorAddr, "2", nil)
|
||||
checkBalanceResult(t, validatorAddr, tokz...)
|
||||
|
||||
// particular NFSO balance check: by hash, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE(), "--id", hex.EncodeToString(token2ID))...)
|
||||
checkBalanceResult(t, validatorAddr, "1", token2ID)
|
||||
checkBalanceResult(t, validatorAddr, tokz[1])
|
||||
|
||||
// import token
|
||||
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
||||
|
@ -440,11 +467,11 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// overall balance check: by symbol, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||
checkBalanceResult(t, validatorAddr, "2", nil)
|
||||
checkBalanceResult(t, validatorAddr, tokz...)
|
||||
|
||||
// particular balance check: by symbol, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", "NFSO", "--id", hex.EncodeToString(token1ID))...)
|
||||
checkBalanceResult(t, validatorAddr, "1", token1ID)
|
||||
checkBalanceResult(t, validatorAddr, tokz[0])
|
||||
|
||||
// remove token from wallet
|
||||
e.In.WriteString("y\r")
|
||||
|
@ -531,7 +558,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// balance check: several tokens, ok
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, validatorAddr, "2", nil)
|
||||
checkBalanceResult(t, validatorAddr, tokz...)
|
||||
|
||||
cmdTransfer := []string{
|
||||
"neo-go", "wallet", "nep11", "transfer",
|
||||
|
@ -559,7 +586,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// check balance after transfer
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, validatorAddr, "1", nil) // only token2ID expected to be on the balance
|
||||
checkBalanceResult(t, validatorAddr, tokz[1]) // only token2ID expected to be on the balance
|
||||
|
||||
// transfer: good, 1/4 of the balance, to NEP-11-Payable contract, with data
|
||||
verifyH := deployVerifyContract(t, e)
|
||||
|
@ -597,7 +624,8 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// check balance after transfer
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, validatorAddr, "0.75", nil)
|
||||
tokz[1].amount = "0.75"
|
||||
checkBalanceResult(t, validatorAddr, tokz[1])
|
||||
}
|
||||
|
||||
func deployNFSContract(t *testing.T, e *executor) util.Uint160 {
|
||||
|
|
|
@ -106,6 +106,7 @@ func TestNEP17Balance(t *testing.T) {
|
|||
t.Run("Bad token", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "kek")...)
|
||||
e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr)
|
||||
e.checkNextLine(t, `^\s*Can't find data for "kek" token\s*`)
|
||||
e.checkEOF(t)
|
||||
})
|
||||
t.Run("Bad wallet", func(t *testing.T) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
|
@ -12,10 +11,12 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"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/neorpc/result"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
|
@ -48,9 +49,24 @@ func newNEP11Commands() []cli.Command {
|
|||
{
|
||||
Name: "balance",
|
||||
Usage: "get address balance",
|
||||
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] --token <hash-or-name> [--id <token-id>]",
|
||||
Action: getNEP11Balance,
|
||||
Flags: balanceFlags,
|
||||
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>] [--id <token-id>]",
|
||||
Description: `Prints NEP-11 balances for address and assets/IDs specified. By default (no
|
||||
address or token parameter) all tokens (NFT contracts) for all accounts in
|
||||
the specified wallet are listed with all tokens (actual NFTs) insied. A
|
||||
single account can be chosen with the address option and/or a single NFT
|
||||
contract can be selected with the token option. Further, you can specify a
|
||||
particular NFT ID (hex-encoded) to display (which is mostly useful for
|
||||
divisible NFTs). Tokens can be specified by hash, address, name or symbol.
|
||||
Hashes and addresses always work (as long as they belong to a correct NEP-11
|
||||
contract), while names or symbols are matched against the token data
|
||||
stored in the wallet (see import command) or balance data returned from the
|
||||
server. If the token is not specified directly (with hash/address) and is
|
||||
not found in the wallet then depending on the balances data from the server
|
||||
this command can print no data at all or print multiple tokens for one
|
||||
account (if they use the same names/symbols).
|
||||
`,
|
||||
Action: getNEP11Balance,
|
||||
Flags: balanceFlags,
|
||||
},
|
||||
{
|
||||
Name: "import",
|
||||
|
@ -162,95 +178,48 @@ func removeNEP11Token(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
func getNEP11Balance(ctx *cli.Context) error {
|
||||
var accounts []*wallet.Account
|
||||
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wall, _, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("bad wallet: %w", err), 1)
|
||||
}
|
||||
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
if addrFlag.IsSet {
|
||||
addrHash := addrFlag.Uint160()
|
||||
acc := wall.GetAccount(addrHash)
|
||||
if acc == nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addrHash)), 1)
|
||||
}
|
||||
accounts = append(accounts, acc)
|
||||
} else {
|
||||
if len(wall.Accounts) == 0 {
|
||||
return cli.NewExitError(errors.New("no accounts in the wallet"), 1)
|
||||
}
|
||||
accounts = wall.Accounts
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
name := ctx.String("token")
|
||||
if name == "" {
|
||||
return cli.NewExitError("token hash or name should be specified", 1)
|
||||
}
|
||||
token, err := getMatchingToken(ctx, wall, name, manifest.NEP11StandardName)
|
||||
if err != nil {
|
||||
tokenHash, err := flags.ParseAddress(name)
|
||||
return getNEPBalance(ctx, manifest.NEP11StandardName, func(ctx *cli.Context, c *rpcclient.Client, addrHash util.Uint160, name string, token *wallet.Token, nftID string) error {
|
||||
balances, err := c.GetNEP11Balances(addrHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
|
||||
return err
|
||||
}
|
||||
token, err = c.NEP11TokenInfo(tokenHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
var tokenFound bool
|
||||
for i := range balances.Balances {
|
||||
curToken := tokenFromNEP11Balance(&balances.Balances[i])
|
||||
if tokenMatch(curToken, token, name) {
|
||||
printNFTBalance(ctx, balances.Balances[i], nftID)
|
||||
tokenFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
|
||||
}
|
||||
for k, acc := range accounts {
|
||||
addrHash, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1)
|
||||
if name == "" || tokenFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
if k != 0 {
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||
|
||||
var amount *big.Int
|
||||
if len(tokenIDBytes) == 0 {
|
||||
amount, err = n11.BalanceOf(addrHash)
|
||||
if token != nil {
|
||||
// We have an exact token, but there is no balance data for it -> print without NFTs.
|
||||
printNFTBalance(ctx, result.NEP11AssetBalance{
|
||||
Asset: token.Hash,
|
||||
Decimals: int(token.Decimals),
|
||||
Name: token.Name,
|
||||
Symbol: token.Symbol,
|
||||
}, "")
|
||||
} else {
|
||||
amount, err = n11.BalanceOfD(addrHash, tokenIDBytes)
|
||||
// We have no data for this token at all, maybe it's not even correct -> complain.
|
||||
fmt.Fprintf(ctx.App.Writer, "Can't find data for %q token\n", name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func printNFTBalance(ctx *cli.Context, balance result.NEP11AssetBalance, nftID string) {
|
||||
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", balance.Symbol, balance.Name, balance.Asset.StringLE())
|
||||
for _, tok := range balance.Tokens {
|
||||
if len(nftID) > 0 && nftID != tok.ID {
|
||||
continue
|
||||
}
|
||||
amountStr := fixedn.ToString(amount, int(token.Decimals))
|
||||
|
||||
format := "%s: %s (%s)\n"
|
||||
formatArgs := []interface{}{token.Symbol, token.Name, token.Hash.StringLE()}
|
||||
if len(tokenIDBytes) != 0 {
|
||||
format = "%s: %s (%s, %s)\n"
|
||||
formatArgs = append(formatArgs, tokenID)
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, format, formatArgs...)
|
||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", amountStr)
|
||||
fmt.Fprintf(ctx.App.Writer, "\tToken: %s\n", tok.ID)
|
||||
fmt.Fprintf(ctx.App.Writer, "\t\tAmount: %s\n", decimalAmount(tok.Amount, balance.Decimals))
|
||||
fmt.Fprintf(ctx.App.Writer, "\t\tUpdated: %d\n", tok.LastUpdated)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func transferNEP11(ctx *cli.Context) error {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"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/core/native/nativenames"
|
||||
"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"
|
||||
|
@ -20,10 +21,10 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"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/neptoken"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -99,8 +100,21 @@ func newNEP17Commands() []cli.Command {
|
|||
Name: "balance",
|
||||
Usage: "get address balance",
|
||||
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>]",
|
||||
Action: getNEP17Balance,
|
||||
Flags: balanceFlags,
|
||||
Description: `Prints NEP-17 balances for address and tokens specified. By default (no
|
||||
address or token parameter) all tokens for all accounts in the specified wallet
|
||||
are listed. A single account can be chosen with the address option and/or a
|
||||
single token can be selected with the token option. Tokens can be specified
|
||||
by hash, address, name or symbol. Hashes and addresses always work (as long
|
||||
as they belong to a correct NEP-17 contract), while names or symbols (if
|
||||
they're not NEO or GAS names/symbols) are matched against the token data
|
||||
stored in the wallet (see import command) or balance data returned from the
|
||||
server. If the token is not specified directly (with hash/address) and is
|
||||
not found in the wallet then depending on the balances data from the server
|
||||
this command can print no data at all or print multiple tokens for one
|
||||
account (if they use the same names/symbols).
|
||||
`,
|
||||
Action: getNEP17Balance,
|
||||
Flags: balanceFlags,
|
||||
},
|
||||
{
|
||||
Name: "import",
|
||||
|
@ -156,7 +170,49 @@ func newNEP17Commands() []cli.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func tokenMatch(curToken *wallet.Token, expToken *wallet.Token, name string) bool {
|
||||
return name == "" || // No specification at all, everything matches.
|
||||
(expToken != nil && expToken.Hash == curToken.Hash) || // Exact token specification, matches perfectly.
|
||||
(expToken == nil && name != "" && (curToken.Name == name || curToken.Symbol == name)) // Loose (named non-native) token specification, best-effort.
|
||||
}
|
||||
|
||||
func getNEP17Balance(ctx *cli.Context) error {
|
||||
return getNEPBalance(ctx, manifest.NEP17StandardName, func(ctx *cli.Context, c *rpcclient.Client, addrHash util.Uint160, name string, token *wallet.Token, _ string) error {
|
||||
balances, err := c.GetNEP17Balances(addrHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tokenFound bool
|
||||
for i := range balances.Balances {
|
||||
curToken := tokenFromNEP17Balance(&balances.Balances[i])
|
||||
if tokenMatch(curToken, token, name) {
|
||||
printAssetBalance(ctx, balances.Balances[i])
|
||||
tokenFound = true
|
||||
}
|
||||
}
|
||||
if name == "" || tokenFound {
|
||||
return nil
|
||||
}
|
||||
if token != nil {
|
||||
// We have an exact token, but there is no balance data for it -> print 0.
|
||||
printAssetBalance(ctx, result.NEP17Balance{
|
||||
Asset: token.Hash,
|
||||
Amount: "0",
|
||||
Decimals: int(token.Decimals),
|
||||
LastUpdated: 0,
|
||||
Name: token.Name,
|
||||
Symbol: token.Symbol,
|
||||
})
|
||||
} else {
|
||||
// We have no data for this token at all, maybe it's not even correct -> complain.
|
||||
fmt.Fprintf(ctx.App.Writer, "Can't find data for %q token\n", name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Context, *rpcclient.Client, util.Uint160, string, *wallet.Token, string) error) error {
|
||||
var accounts []*wallet.Account
|
||||
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
|
@ -191,107 +247,76 @@ func getNEP17Balance(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
name := ctx.String("token")
|
||||
var token *wallet.Token
|
||||
|
||||
if name != "" {
|
||||
// Token was explicitly specified, let's try finding it, search in the wallet first.
|
||||
token, err = getMatchingToken(ctx, wall, name, standard)
|
||||
if err != nil {
|
||||
var h util.Uint160
|
||||
|
||||
// Well-known hardcoded names/symbols.
|
||||
if standard == manifest.NEP17StandardName && (name == nativenames.Neo || name == "NEO") {
|
||||
h = neo.Hash
|
||||
} else if standard == manifest.NEP17StandardName && (name == nativenames.Gas || name == "GAS") {
|
||||
h = gas.Hash
|
||||
} else {
|
||||
// The last resort, maybe it's a direct hash or address.
|
||||
h, _ = flags.ParseAddress(name)
|
||||
}
|
||||
// If the hash is not found then it's some kind of named token, there is
|
||||
// no way for us to find it, but it's not an error, maybe we'll find it
|
||||
// in balances.
|
||||
if !h.Equals(util.Uint160{}) {
|
||||
// But if we have an exact hash, it must be correct.
|
||||
token, err = getTokenWithStandard(c, h, standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("%q is not a valid NEP-17 token: %w", name, err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tokenID := ctx.String("id")
|
||||
if standard == manifest.NEP11StandardName {
|
||||
if len(tokenID) > 0 {
|
||||
_, err = hex.DecodeString(tokenID)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, acc := range accounts {
|
||||
addrHash, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1)
|
||||
}
|
||||
balances, err := c.GetNEP17Balances(addrHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
if k != 0 {
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||
|
||||
var tokenFound bool
|
||||
for i := range balances.Balances {
|
||||
var tokenName, tokenSymbol string
|
||||
tokenDecimals := 0
|
||||
asset := balances.Balances[i].Asset
|
||||
token, err := getMatchingToken(ctx, wall, asset.StringLE(), manifest.NEP17StandardName)
|
||||
if err != nil {
|
||||
token, err = c.NEP17TokenInfo(asset)
|
||||
}
|
||||
if err == nil {
|
||||
if name != "" && !(token.Name == name || token.Symbol == name || token.Address() == name || token.Hash.StringLE() == name) {
|
||||
continue
|
||||
}
|
||||
tokenName = token.Name
|
||||
tokenSymbol = token.Symbol
|
||||
tokenDecimals = int(token.Decimals)
|
||||
tokenFound = true
|
||||
} else {
|
||||
if name != "" {
|
||||
continue
|
||||
}
|
||||
tokenSymbol = "UNKNOWN"
|
||||
}
|
||||
printAssetBalance(ctx, asset, tokenName, tokenSymbol, tokenDecimals, balances.Balances[i])
|
||||
}
|
||||
if name == "" || tokenFound {
|
||||
continue
|
||||
}
|
||||
// Token was explicitly specified, but was not found among balances, thus either balance is 0
|
||||
// or the token doesn't exist. Try to find token by its address/hash/name/symbol and print zero
|
||||
// balance if found. Search into wallet first.
|
||||
token, err := getMatchingToken(ctx, wall, name, manifest.NEP17StandardName)
|
||||
err = accHandler(ctx, c, addrHash, name, token, tokenID)
|
||||
if err != nil {
|
||||
// The wallet doesn't contain specified token, so try to ask chain.
|
||||
h, err := flags.ParseAddress(name)
|
||||
if err != nil {
|
||||
h, err = c.GetNativeContractHash(name)
|
||||
if err != nil {
|
||||
// Try to get native NEP17 with matching symbol.
|
||||
var gasSymbol, neoSymbol string
|
||||
g := gas.NewReader(invoker.New(c, nil))
|
||||
gasSymbol, err = g.Symbol()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if gasSymbol != name {
|
||||
n := neo.NewReader(invoker.New(c, nil))
|
||||
neoSymbol, err = n.Symbol()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if neoSymbol != name {
|
||||
continue
|
||||
} else {
|
||||
h = neo.Hash
|
||||
}
|
||||
} else {
|
||||
h = gas.Hash
|
||||
}
|
||||
}
|
||||
}
|
||||
token, err = c.NEP17TokenInfo(h)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
printAssetBalance(ctx, token.Hash, token.Name, token.Symbol, int(token.Decimals), result.NEP17Balance{
|
||||
Asset: token.Hash,
|
||||
Amount: "0",
|
||||
LastUpdated: 0,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printAssetBalance(ctx *cli.Context, asset util.Uint160, tokenName, tokenSymbol string, tokenDecimals int, balance result.NEP17Balance) {
|
||||
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", tokenSymbol, tokenName, asset.StringLE())
|
||||
amount := balance.Amount
|
||||
if tokenDecimals != 0 {
|
||||
func decimalAmount(amount string, decimals int) string {
|
||||
if decimals != 0 {
|
||||
b, ok := new(big.Int).SetString(amount, 10)
|
||||
if ok {
|
||||
amount = fixedn.ToString(b, tokenDecimals)
|
||||
amount = fixedn.ToString(b, decimals)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", amount)
|
||||
return amount
|
||||
}
|
||||
|
||||
func printAssetBalance(ctx *cli.Context, balance result.NEP17Balance) {
|
||||
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", balance.Symbol, balance.Name, balance.Asset.StringLE())
|
||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", decimalAmount(balance.Amount, balance.Decimals))
|
||||
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balance.LastUpdated)
|
||||
}
|
||||
|
||||
|
@ -301,6 +326,14 @@ func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string, standard
|
|||
}, len(w.Extra.Tokens), name, standard)
|
||||
}
|
||||
|
||||
func tokenFromNEP17Balance(bal *result.NEP17Balance) *wallet.Token {
|
||||
return wallet.NewToken(bal.Asset, bal.Name, bal.Symbol, int64(bal.Decimals), manifest.NEP17StandardName)
|
||||
}
|
||||
|
||||
func tokenFromNEP11Balance(bal *result.NEP11AssetBalance) *wallet.Token {
|
||||
return wallet.NewToken(bal.Asset, bal.Name, bal.Symbol, int64(bal.Decimals), manifest.NEP11StandardName)
|
||||
}
|
||||
|
||||
func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint160, name string, standard string) (*wallet.Token, error) {
|
||||
switch standard {
|
||||
case manifest.NEP17StandardName:
|
||||
|
@ -309,20 +342,18 @@ func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint16
|
|||
return nil, err
|
||||
}
|
||||
get := func(i int) *wallet.Token {
|
||||
t, _ := c.NEP17TokenInfo(bs.Balances[i].Asset)
|
||||
return t
|
||||
return tokenFromNEP17Balance(&bs.Balances[i])
|
||||
}
|
||||
return getMatchingTokenAux(ctx, get, len(bs.Balances), name, standard)
|
||||
case manifest.NEP11StandardName:
|
||||
tokenHash, err := flags.ParseAddress(name)
|
||||
bs, err := c.GetNEP11Balances(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("valid token adress or hash in LE should be specified for %s RPC-node request: %s", standard, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
get := func(i int) *wallet.Token {
|
||||
t, _ := c.NEP11TokenInfo(tokenHash)
|
||||
return t
|
||||
return tokenFromNEP11Balance(&bs.Balances[i])
|
||||
}
|
||||
return getMatchingTokenAux(ctx, get, 1, name, standard)
|
||||
return getMatchingTokenAux(ctx, get, len(bs.Balances), name, standard)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported %s token", standard)
|
||||
}
|
||||
|
@ -383,15 +414,7 @@ func importNEPToken(ctx *cli.Context, standard string) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
var tok *wallet.Token
|
||||
switch standard {
|
||||
case manifest.NEP17StandardName:
|
||||
tok, err = c.NEP17TokenInfo(tokenHash)
|
||||
case manifest.NEP11StandardName:
|
||||
tok, err = c.NEP11TokenInfo(tokenHash)
|
||||
default:
|
||||
return cli.NewExitError(fmt.Sprintf("unsupported token standard: %s", standard), 1)
|
||||
}
|
||||
tok, err := getTokenWithStandard(c, tokenHash, standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't receive token info: %w", err), 1)
|
||||
}
|
||||
|
@ -404,6 +427,17 @@ func importNEPToken(ctx *cli.Context, standard string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getTokenWithStandard(c *rpcclient.Client, hash util.Uint160, std string) (*wallet.Token, error) {
|
||||
token, err := neptoken.Info(c, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token.Standard != std {
|
||||
return nil, fmt.Errorf("%s is not a %s token", hash.StringLE(), std)
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
func printTokenInfo(ctx *cli.Context, tok *wallet.Token) {
|
||||
w := ctx.App.Writer
|
||||
fmt.Fprintf(w, "Name:\t%s\n", tok.Name)
|
||||
|
|
13
docs/cli.md
13
docs/cli.md
|
@ -403,9 +403,10 @@ Getting balance is easy:
|
|||
./bin/neo-go wallet nep17 balance -w /etc/neo-go/wallet.json -r http://localhost:20332
|
||||
```
|
||||
|
||||
By default, you'll get data for all tokens for the default wallet's
|
||||
address. You can select non-default address with `-a` flag and/or select token
|
||||
with `--token` flag (token hash or name can be used as parameter).
|
||||
By default, you'll get data for all tokens that are owned by all accounts
|
||||
stored in the given wallet. You can specify a particular address with `-a`
|
||||
flag and/or select token with `--token` flag (token hash, address, name or
|
||||
symbol can be used as a parameter).
|
||||
|
||||
#### Transfers
|
||||
|
||||
|
@ -455,13 +456,13 @@ commands with the following adjustments.
|
|||
|
||||
#### Balance
|
||||
|
||||
Specify token ID via `--id` flag to call divisible NEP-11 `balanceOf` method:
|
||||
Specify token ID via `--id` flag to get data for a particular NFT:
|
||||
|
||||
```
|
||||
./bin/neo-go wallet nep11 balance -w /etc/neo-go/wallet.json --token 67ecb7766dba4acf7c877392207984d1b4d15731 --id R5OREI5BU+Uyd23/MuV/xzI3F+Q= -r http://localhost:20332
|
||||
./bin/neo-go wallet nep11 balance -w /etc/neo-go/wallet.json --token 67ecb7766dba4acf7c877392207984d1b4d15731 --id 7e244ffd6aa85fb1579d2ed22e9b761ab62e3486 -r http://localhost:20332
|
||||
```
|
||||
|
||||
By default, no token ID specified, i.e. common `balanceOf` method is called.
|
||||
By default, no token ID specified, i.e all NFTs returned by the server are listed.
|
||||
|
||||
#### Transfers
|
||||
|
||||
|
|
|
@ -49,6 +49,9 @@ func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) {
|
|||
}
|
||||
|
||||
// NEP11TokenInfo returns full NEP-11 token info.
|
||||
//
|
||||
// Deprecated: please use Info method from the neptoken subpackage. This method
|
||||
// will be removed in future versions.
|
||||
func (c *Client) NEP11TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
|
||||
return c.nepTokenInfo(tokenHash, manifest.NEP11StandardName)
|
||||
}
|
||||
|
|
|
@ -86,12 +86,6 @@ 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
|
||||
|
|
|
@ -61,6 +61,9 @@ func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) {
|
|||
}
|
||||
|
||||
// NEP17TokenInfo returns full NEP-17 token info.
|
||||
//
|
||||
// Deprecated: please use Info method from the neptoken subpackage. This method
|
||||
// will be removed in future versions.
|
||||
func (c *Client) NEP17TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
|
||||
return c.nepTokenInfo(tokenHash, manifest.NEP17StandardName)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"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"
|
||||
)
|
||||
|
@ -35,15 +34,13 @@ type Actor interface {
|
|||
// used to query various data.
|
||||
type TokenReader struct {
|
||||
neptoken.Base
|
||||
|
||||
invoker Invoker
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// Token provides full NEP-17 interface, both safe and state-changing methods.
|
||||
type Token struct {
|
||||
TokenReader
|
||||
|
||||
hash util.Uint160
|
||||
actor Actor
|
||||
}
|
||||
|
||||
|
@ -62,21 +59,16 @@ type TransferParameters struct {
|
|||
Data interface{}
|
||||
}
|
||||
|
||||
// NewReader creates an instance of TokenReader for contract with the given hash
|
||||
// using the given Invoker.
|
||||
// NewReader creates an instance of TokenReader for contract with the given
|
||||
// hash using the given Invoker.
|
||||
func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
|
||||
return &TokenReader{*neptoken.New(invoker, hash), invoker, hash}
|
||||
return &TokenReader{*neptoken.New(invoker, hash)}
|
||||
}
|
||||
|
||||
// New creates an instance of Token for contract with the given hash
|
||||
// using the given Actor.
|
||||
func New(actor Actor, hash util.Uint160) *Token {
|
||||
return &Token{*NewReader(actor, hash), actor}
|
||||
}
|
||||
|
||||
// BalanceOf returns the token balance of the given account.
|
||||
func (t *TokenReader) BalanceOf(account util.Uint160) (*big.Int, error) {
|
||||
return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", account))
|
||||
return &Token{*NewReader(actor, hash), hash, actor}
|
||||
}
|
||||
|
||||
// Transfer creates and sends a transaction that performs a `transfer` method
|
||||
|
|
|
@ -61,3 +61,12 @@ func (b *Base) Symbol() (string, error) {
|
|||
func (b *Base) TotalSupply() (*big.Int, error) {
|
||||
return unwrap.BigInt(b.invoker.Call(b.hash, "totalSupply"))
|
||||
}
|
||||
|
||||
// BalanceOf returns the token balance of the given account. For NEP-17 that's
|
||||
// the token balance with decimals (1 TOK with 2 decimals will lead to 100
|
||||
// returned from this method). For non-divisible NEP-11 that's the number of
|
||||
// NFTs owned by the account, for divisible NEP-11 that's the sum of the parts
|
||||
// of all NFTs owned by the account.
|
||||
func (b *Base) BalanceOf(account util.Uint160) (*big.Int, error) {
|
||||
return unwrap.BigInt(b.invoker.Call(b.hash, "balanceOf", account))
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ func TestBaseErrors(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
_, err = base.TotalSupply()
|
||||
require.Error(t, err)
|
||||
_, err = base.BalanceOf(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
|
||||
ti.err = nil
|
||||
ti.res = &result.Invoke{
|
||||
|
@ -43,6 +45,8 @@ func TestBaseErrors(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
_, err = base.TotalSupply()
|
||||
require.Error(t, err)
|
||||
_, err = base.BalanceOf(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
|
||||
ti.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
|
@ -53,6 +57,8 @@ func TestBaseErrors(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
_, err = base.TotalSupply()
|
||||
require.Error(t, err)
|
||||
_, err = base.BalanceOf(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBaseDecimals(t *testing.T) {
|
||||
|
@ -135,3 +141,27 @@ func TestBaseTotalSupply(t *testing.T) {
|
|||
_, err = base.TotalSupply()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBaseBalanceOf(t *testing.T) {
|
||||
ti := new(testInv)
|
||||
base := New(ti, util.Uint160{1, 2, 3})
|
||||
|
||||
ti.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}
|
||||
bal, err := base.BalanceOf(util.Uint160{1, 2, 3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(100500), bal)
|
||||
|
||||
ti.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = base.BalanceOf(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
|
47
pkg/rpcclient/neptoken/info.go
Normal file
47
pkg/rpcclient/neptoken/info.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package neptoken
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
// InfoClient is a set of RPC methods required to get all of the NEP-11/NEP-17
|
||||
// token data.
|
||||
type InfoClient interface {
|
||||
invoker.RPCInvoke
|
||||
|
||||
GetContractStateByHash(hash util.Uint160) (*state.Contract, error)
|
||||
}
|
||||
|
||||
// Info allows to get basic token info using RPC client.
|
||||
func Info(c InfoClient, hash util.Uint160) (*wallet.Token, error) {
|
||||
cs, err := c.GetContractStateByHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var standard string
|
||||
for _, st := range cs.Manifest.SupportedStandards {
|
||||
if st == manifest.NEP17StandardName || st == manifest.NEP11StandardName {
|
||||
standard = st
|
||||
break
|
||||
}
|
||||
}
|
||||
if standard == "" {
|
||||
return nil, fmt.Errorf("contract %s is not NEP-11/NEP17", hash.StringLE())
|
||||
}
|
||||
b := New(invoker.New(c, nil), hash)
|
||||
symbol, err := b.Symbol()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decimals, err := b.Decimals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wallet.NewToken(hash, cs.Manifest.Name, symbol, int64(decimals), standard), nil
|
||||
}
|
147
pkg/rpcclient/neptoken/info_test.go
Normal file
147
pkg/rpcclient/neptoken/info_test.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package neptoken
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"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/smartcontract"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type rpcClient struct {
|
||||
cnt int
|
||||
cserr error
|
||||
cs *state.Contract
|
||||
inverrs []error
|
||||
invs []*result.Invoke
|
||||
}
|
||||
|
||||
func (r *rpcClient) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *rpcClient) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
e, i := r.inverrs[r.cnt], r.invs[r.cnt]
|
||||
r.cnt = (r.cnt + 1) % len(r.invs)
|
||||
return i, e
|
||||
}
|
||||
func (r *rpcClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *rpcClient) TerminateSession(sessionID uuid.UUID) (bool, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *rpcClient) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *rpcClient) GetContractStateByHash(hash util.Uint160) (*state.Contract, error) {
|
||||
return r.cs, r.cserr
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
c := &rpcClient{}
|
||||
hash := util.Uint160{1, 2, 3}
|
||||
|
||||
// Error on contract state.
|
||||
c.cserr = errors.New("")
|
||||
_, err := Info(c, hash)
|
||||
require.Error(t, err)
|
||||
|
||||
// Error on missing standard.
|
||||
c.cserr = nil
|
||||
c.cs = &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
Manifest: manifest.Manifest{
|
||||
Name: "Vasiliy",
|
||||
SupportedStandards: []string{"RFC 1149"},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = Info(c, hash)
|
||||
require.Error(t, err)
|
||||
|
||||
// Error on Symbol()
|
||||
c.cs = &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
Manifest: manifest.Manifest{
|
||||
Name: "Übertoken",
|
||||
SupportedStandards: []string{"NEP-17"},
|
||||
},
|
||||
},
|
||||
}
|
||||
c.inverrs = []error{errors.New(""), nil}
|
||||
c.invs = []*result.Invoke{nil, nil}
|
||||
_, err = Info(c, hash)
|
||||
require.Error(t, err)
|
||||
|
||||
// Error on Decimals()
|
||||
c.cnt = 0
|
||||
c.inverrs[0], c.inverrs[1] = c.inverrs[1], c.inverrs[0]
|
||||
c.invs[0] = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make("UBT"),
|
||||
},
|
||||
}
|
||||
_, err = Info(c, hash)
|
||||
require.Error(t, err)
|
||||
|
||||
// OK
|
||||
c.cnt = 0
|
||||
c.inverrs[1] = nil
|
||||
c.invs[1] = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(8),
|
||||
},
|
||||
}
|
||||
ti, err := Info(c, hash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &wallet.Token{
|
||||
Name: "Übertoken",
|
||||
Hash: hash,
|
||||
Decimals: 8,
|
||||
Symbol: "UBT",
|
||||
Standard: "NEP-17",
|
||||
}, ti)
|
||||
|
||||
// NEP-11
|
||||
c.cs = &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
Manifest: manifest.Manifest{
|
||||
Name: "NFTizer",
|
||||
SupportedStandards: []string{"NEP-11"},
|
||||
},
|
||||
},
|
||||
}
|
||||
c.cnt = 0
|
||||
c.inverrs[1] = nil
|
||||
c.invs[0] = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make("NZ"),
|
||||
},
|
||||
}
|
||||
c.invs[1] = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(0),
|
||||
},
|
||||
}
|
||||
ti, err = Info(c, hash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &wallet.Token{
|
||||
Name: "NFTizer",
|
||||
Hash: hash,
|
||||
Decimals: 0,
|
||||
Symbol: "NZ",
|
||||
Standard: "NEP-11",
|
||||
}, ti)
|
||||
}
|
|
@ -36,6 +36,7 @@ import (
|
|||
"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/neptoken"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/oracle"
|
||||
|
@ -83,7 +84,7 @@ func TestClient_NEP17(t *testing.T) {
|
|||
require.Equal(t, "RUB", sym)
|
||||
})
|
||||
t.Run("TokenInfo", func(t *testing.T) {
|
||||
tok, err := c.NEP17TokenInfo(h)
|
||||
tok, err := neptoken.Info(c, h)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, h, tok.Hash)
|
||||
require.Equal(t, "Rubl", tok.Name)
|
||||
|
@ -1377,7 +1378,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
require.Equal(t, "NNS", sym)
|
||||
})
|
||||
t.Run("TokenInfo", func(t *testing.T) {
|
||||
tok, err := c.NEP11TokenInfo(h)
|
||||
tok, err := neptoken.Info(c, h)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &wallet.Token{
|
||||
Name: "NameService",
|
||||
|
@ -1463,7 +1464,7 @@ func TestClient_NEP11_D(t *testing.T) {
|
|||
require.Equal(t, "NFSO", sym)
|
||||
})
|
||||
t.Run("TokenInfo", func(t *testing.T) {
|
||||
tok, err := c.NEP11TokenInfo(nfsoHash)
|
||||
tok, err := neptoken.Info(c, nfsoHash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &wallet.Token{
|
||||
Name: "NeoFS Object NFT",
|
||||
|
|
Loading…
Reference in a new issue