From a3f32bf3061bd7c25397ebdac10ccaeaa8371aa2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 26 Aug 2022 21:52:19 +0300 Subject: [PATCH 1/6] neptoken: move BalanceOf implementation to Base from nep11/nep17 It's the same, even though standards define parameter name in a bit different way. --- pkg/rpcclient/nep11/base.go | 6 ------ pkg/rpcclient/nep17/nep17.go | 18 +++++------------ pkg/rpcclient/neptoken/base.go | 9 +++++++++ pkg/rpcclient/neptoken/base_test.go | 30 +++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/pkg/rpcclient/nep11/base.go b/pkg/rpcclient/nep11/base.go index e9932579a..8aaf06a89 100644 --- a/pkg/rpcclient/nep11/base.go +++ b/pkg/rpcclient/nep11/base.go @@ -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 diff --git a/pkg/rpcclient/nep17/nep17.go b/pkg/rpcclient/nep17/nep17.go index e19f4ec7f..d6c263747 100644 --- a/pkg/rpcclient/nep17/nep17.go +++ b/pkg/rpcclient/nep17/nep17.go @@ -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 diff --git a/pkg/rpcclient/neptoken/base.go b/pkg/rpcclient/neptoken/base.go index 63e258d6b..610e2be8e 100644 --- a/pkg/rpcclient/neptoken/base.go +++ b/pkg/rpcclient/neptoken/base.go @@ -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)) +} diff --git a/pkg/rpcclient/neptoken/base_test.go b/pkg/rpcclient/neptoken/base_test.go index b251de5f0..7f7320d7c 100644 --- a/pkg/rpcclient/neptoken/base_test.go +++ b/pkg/rpcclient/neptoken/base_test.go @@ -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) +} From ed6ed61712f15fbd20e252c4fb36448b4a4f31d5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 26 Aug 2022 22:56:18 +0300 Subject: [PATCH 2/6] neptoken: add Info to replace old NEPXXTokenInfo methods I'm still not sure it's good to have this exposed from neptoken at all, but let's try it this way. --- cli/wallet/nep11.go | 2 +- cli/wallet/nep17.go | 30 +++--- pkg/rpcclient/nep11.go | 3 + pkg/rpcclient/nep17.go | 3 + pkg/rpcclient/neptoken/info.go | 47 +++++++++ pkg/rpcclient/neptoken/info_test.go | 147 ++++++++++++++++++++++++++++ pkg/services/rpcsrv/client_test.go | 7 +- 7 files changed, 222 insertions(+), 17 deletions(-) create mode 100644 pkg/rpcclient/neptoken/info.go create mode 100644 pkg/rpcclient/neptoken/info_test.go diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index 044619b51..152c1979b 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -206,7 +206,7 @@ func getNEP11Balance(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1) } - token, err = c.NEP11TokenInfo(tokenHash) + token, err = getTokenWithStandard(c, tokenHash, manifest.NEP11StandardName) if err != nil { return cli.NewExitError(err.Error(), 1) } diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index c31dc8c51..54f81f379 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -24,6 +24,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/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" @@ -214,7 +215,7 @@ func getNEP17Balance(ctx *cli.Context) error { asset := balances.Balances[i].Asset token, err := getMatchingToken(ctx, wall, asset.StringLE(), manifest.NEP17StandardName) if err != nil { - token, err = c.NEP17TokenInfo(asset) + token, err = getTokenWithStandard(c, asset, manifest.NEP17StandardName) } if err == nil { if name != "" && !(token.Name == name || token.Symbol == name || token.Address() == name || token.Hash.StringLE() == name) { @@ -268,7 +269,7 @@ func getNEP17Balance(ctx *cli.Context) error { } } } - token, err = c.NEP17TokenInfo(h) + token, err = getTokenWithStandard(c, h, manifest.NEP17StandardName) if err != nil { continue } @@ -309,7 +310,7 @@ 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) + t, _ := getTokenWithStandard(c, bs.Balances[i].Asset, standard) return t } return getMatchingTokenAux(ctx, get, len(bs.Balances), name, standard) @@ -319,7 +320,7 @@ func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint16 return nil, fmt.Errorf("valid token adress or hash in LE should be specified for %s RPC-node request: %s", standard, err.Error()) } get := func(i int) *wallet.Token { - t, _ := c.NEP11TokenInfo(tokenHash) + t, _ := getTokenWithStandard(c, tokenHash, standard) return t } return getMatchingTokenAux(ctx, get, 1, name, standard) @@ -383,15 +384,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 +397,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) diff --git a/pkg/rpcclient/nep11.go b/pkg/rpcclient/nep11.go index 111f38ee3..1c16990e9 100644 --- a/pkg/rpcclient/nep11.go +++ b/pkg/rpcclient/nep11.go @@ -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) } diff --git a/pkg/rpcclient/nep17.go b/pkg/rpcclient/nep17.go index 3c2be2679..e1b988976 100644 --- a/pkg/rpcclient/nep17.go +++ b/pkg/rpcclient/nep17.go @@ -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) } diff --git a/pkg/rpcclient/neptoken/info.go b/pkg/rpcclient/neptoken/info.go new file mode 100644 index 000000000..d6dfed2ad --- /dev/null +++ b/pkg/rpcclient/neptoken/info.go @@ -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 +} diff --git a/pkg/rpcclient/neptoken/info_test.go b/pkg/rpcclient/neptoken/info_test.go new file mode 100644 index 000000000..452af9220 --- /dev/null +++ b/pkg/rpcclient/neptoken/info_test.go @@ -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) +} diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index c977681e7..d616f5aee 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -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/oracle" "github.com/nspcc-dev/neo-go/pkg/rpcclient/policy" @@ -82,7 +83,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) @@ -1259,7 +1260,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", @@ -1345,7 +1346,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", From e28bf55ebb53a3baabb8d4acba132d637bc5a8be Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 26 Aug 2022 23:12:22 +0300 Subject: [PATCH 3/6] cli/wallet: search for NEP-11 token name in balances In the same way we do for NEP-17 tokens. This code predates "getnep11balances" call, so this wasn't possible back then, but now we can improve the situation (allow specifying names/symbols instead of hashes only). --- cli/wallet/nep17.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 54f81f379..5ebb80cc0 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -315,15 +315,15 @@ func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint16 } 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, _ := getTokenWithStandard(c, tokenHash, standard) + t, _ := getTokenWithStandard(c, bs.Balances[i].Asset, standard) return t } - 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) } From 5f1fe725048be4e99286df2b5a721e28d3c84dce Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 29 Aug 2022 15:48:48 +0300 Subject: [PATCH 4/6] cli/wallet: use token data from getnepXXbalances We have this data available since 0.99.1 while all public networks require at least 0.99.2 for compatibility and NeoFS setups use 0.99.2+ too. This data can simplify account handling considerably making additional requests unneccessary in many cases. --- cli/wallet/nep17.go | 53 ++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 5ebb80cc0..3ef164bbe 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -210,28 +210,12 @@ func getNEP17Balance(ctx *cli.Context) error { 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 = getTokenWithStandard(c, asset, manifest.NEP17StandardName) + token := tokenFromNEP17Balance(&balances.Balances[i]) + if name != "" && !(token.Name == name || token.Symbol == name || token.Address() == name || token.Hash.StringLE() == name) { + continue } - 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]) + printAssetBalance(ctx, balances.Balances[i]) + tokenFound = true } if name == "" || tokenFound { continue @@ -274,22 +258,25 @@ func getNEP17Balance(ctx *cli.Context) error { continue } } - printAssetBalance(ctx, token.Hash, token.Name, token.Symbol, int(token.Decimals), result.NEP17Balance{ + printAssetBalance(ctx, result.NEP17Balance{ Asset: token.Hash, Amount: "0", + Decimals: int(token.Decimals), LastUpdated: 0, + Name: token.Name, + Symbol: token.Symbol, }) } 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()) +func printAssetBalance(ctx *cli.Context, balance result.NEP17Balance) { + fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", balance.Symbol, balance.Name, balance.Asset.StringLE()) amount := balance.Amount - if tokenDecimals != 0 { + if balance.Decimals != 0 { b, ok := new(big.Int).SetString(amount, 10) if ok { - amount = fixedn.ToString(b, tokenDecimals) + amount = fixedn.ToString(b, balance.Decimals) } } fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", amount) @@ -302,6 +289,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: @@ -310,8 +305,7 @@ func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint16 return nil, err } get := func(i int) *wallet.Token { - t, _ := getTokenWithStandard(c, bs.Balances[i].Asset, standard) - return t + return tokenFromNEP17Balance(&bs.Balances[i]) } return getMatchingTokenAux(ctx, get, len(bs.Balances), name, standard) case manifest.NEP11StandardName: @@ -320,8 +314,7 @@ func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint16 return nil, err } get := func(i int) *wallet.Token { - t, _ := getTokenWithStandard(c, bs.Balances[i].Asset, standard) - return t + return tokenFromNEP11Balance(&bs.Balances[i]) } return getMatchingTokenAux(ctx, get, len(bs.Balances), name, standard) default: From 7cfcf072b832ff4c848c0a107efc82f38ebfdf83 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 29 Aug 2022 18:43:25 +0300 Subject: [PATCH 5/6] cli/wallet: make NEP-17 token selection logic a bit more robust 1. In the single token mode compare known hashes instead of names, names can be misleading. 2. Hardcode NEO/GAS, they are special (if not overrided by the wallet data). --- cli/nep17_test.go | 1 + cli/wallet/nep17.go | 115 +++++++++++++++++++++++++------------------- docs/cli.md | 7 +-- 3 files changed, 70 insertions(+), 53 deletions(-) diff --git a/cli/nep17_test.go b/cli/nep17_test.go index 2d00dfbfb..b318616d1 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -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) { diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 3ef164bbe..891bd0adc 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -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,7 +21,6 @@ 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" @@ -100,8 +100,21 @@ func newNEP17Commands() []cli.Command { Name: "balance", Usage: "get address balance", UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint [--timeout