Skip to content

Commit

Permalink
Merge pull request #2667 from nspcc-dev/rpc-nep-token-info
Browse files Browse the repository at this point in the history
Drop NEPXXTokenInfo from the RPC client
  • Loading branch information
roman-khimov committed Aug 30, 2022
2 parents 673c895 + 1da4b33 commit 314cd33
Show file tree
Hide file tree
Showing 14 changed files with 493 additions and 234 deletions.
74 changes: 51 additions & 23 deletions cli/nep11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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")
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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",
Expand All @@ -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")
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions cli/nep17_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
139 changes: 54 additions & 85 deletions cli/wallet/nep11.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import (
"encoding/hex"
"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/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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 k != 0 {
fmt.Fprintln(ctx.App.Writer)
if name == "" || tokenFound {
return nil
}
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)
}
if err != nil {
continue
// 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)
}
amountStr := fixedn.ToString(amount, int(token.Decimals))
return nil
})
}

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)
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
}
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 {
Expand Down
Loading

0 comments on commit 314cd33

Please sign in to comment.