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",