diff --git a/cli/query/query.go b/cli/query/query.go index 226585d8f..2d390e42f 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -19,7 +19,9 @@ import ( "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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -289,29 +291,21 @@ func queryVoter(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("failed to get NEO contract hash: %w", err), 1) } - res, err := c.InvokeFunction(neoHash, "getAccountState", []smartcontract.Parameter{ - { - Type: smartcontract.Hash160Type, - Value: addr, - }, - }, nil) + inv := invoker.New(c, nil) + neoToken := nep17.NewReader(inv, neoHash) + + itm, err := unwrap.Item(inv.Call(neoHash, "getAccountState", addr)) if err != nil { return cli.NewExitError(err, 1) } - if res.State != "HALT" { - return cli.NewExitError(fmt.Errorf("invocation failed: %s", res.FaultException), 1) - } - if len(res.Stack) == 0 { - return cli.NewExitError("result stack is empty", 1) - } st := new(state.NEOBalance) - if _, ok := res.Stack[0].(stackitem.Null); !ok { - err = st.FromStackItem(res.Stack[0]) + if _, ok := itm.(stackitem.Null); !ok { + err = st.FromStackItem(itm) if err != nil { return cli.NewExitError(fmt.Errorf("failed to convert account state from stackitem: %w", err), 1) } } - dec, err := c.NEP17Decimals(neoHash) + dec, err := neoToken.Decimals() if err != nil { return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1) } diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 43ff8d418..f7596945b 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -17,6 +17,8 @@ import ( "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/nep17" "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" @@ -286,7 +288,8 @@ func getNativeNEP17Symbol(c *rpcclient.Client, name string) (string, util.Uint16 if err != nil { return "", util.Uint160{}, fmt.Errorf("failed to get native %s hash: %w", name, err) } - symbol, err := c.NEP17Symbol(h) + nepTok := nep17.NewReader(invoker.New(c, nil), h) + symbol, err := nepTok.Symbol() if err != nil { return "", util.Uint160{}, fmt.Errorf("failed to get native %s symbol: %w", name, err) } diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index e7ea77d51..21a8d514b 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math/big" "os" "strings" @@ -17,6 +18,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -329,11 +332,16 @@ func claimGas(ctx *cli.Context) error { return cli.NewExitError(err, 1) } + act, err := actor.NewSimple(c, acc) + if err != nil { + return cli.NewExitError(err, 1) + } neoContractHash, err := c.GetNativeContractHash(nativenames.Neo) if err != nil { return cli.NewExitError(err, 1) } - hash, err := c.TransferNEP17(acc, scriptHash, neoContractHash, 0, 0, nil, nil) + neoToken := nep17.New(act, neoContractHash) + hash, _, err := neoToken.Transfer(scriptHash, scriptHash, big.NewInt(0), nil) if err != nil { return cli.NewExitError(err, 1) } diff --git a/pkg/rpcclient/nep17.go b/pkg/rpcclient/nep17.go index 329f88fca..8e86e3f43 100644 --- a/pkg/rpcclient/nep17.go +++ b/pkg/rpcclient/nep17.go @@ -27,21 +27,35 @@ type SignerAccount struct { } // NEP17Decimals invokes `decimals` NEP-17 method on the specified contract. +// +// Deprecated: please use nep17 package, this method will be removed in future +// versions. func (c *Client) NEP17Decimals(tokenHash util.Uint160) (int64, error) { return c.nepDecimals(tokenHash) } // NEP17Symbol invokes `symbol` NEP-17 method on the specified contract. +// +// Deprecated: please use nep17 package, this method will be removed in future +// versions. func (c *Client) NEP17Symbol(tokenHash util.Uint160) (string, error) { return c.nepSymbol(tokenHash) } // NEP17TotalSupply invokes `totalSupply` NEP-17 method on the specified contract. +// +// Deprecated: please use nep17 package, this method will be removed in future +// versions. This method is also wrong since tokens can return values overflowing +// int64. func (c *Client) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) { return c.nepTotalSupply(tokenHash) } // NEP17BalanceOf invokes `balanceOf` NEP-17 method on the specified contract. +// +// Deprecated: please use nep17 package, this method will be removed in future +// versions. This method is also wrong since tokens can return values overflowing +// int64. func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) { return c.nepBalanceOf(tokenHash, acc, nil) } @@ -55,6 +69,9 @@ func (c *Client) NEP17TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) { // method of the given contract (token) to move the specified amount of NEP-17 assets // (in FixedN format using contract's number of decimals) to the given account and // returns it. The returned transaction is not signed. +// +// Deprecated: please use nep17 package, this method will be removed in future +// versions. func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64, data interface{}, cosigners []SignerAccount) (*transaction.Transaction, error) { return c.CreateNEP17MultiTransferTx(acc, gas, []TransferTarget{ @@ -140,6 +157,9 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, // specifies a set of the transaction cosigners (may be nil or may include sender) // with a proper scope and the accounts to cosign the transaction. If cosigning is // impossible (e.g. due to locked cosigner's account) an error is returned. +// +// Deprecated: please use nep17 package, this method will be removed in future +// versions. func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64, data interface{}, cosigners []SignerAccount) (util.Uint256, error) { tx, err := c.CreateNEP17TransferTx(acc, to, token, amount, gas, data, cosigners) diff --git a/pkg/rpcclient/nep17/nep17.go b/pkg/rpcclient/nep17/nep17.go new file mode 100644 index 000000000..d5f3998bd --- /dev/null +++ b/pkg/rpcclient/nep17/nep17.go @@ -0,0 +1,111 @@ +/* +Package nep17 contains RPC wrappers to work with NEP-17 contracts. + +Safe methods are encapsulated into TokenReader structure while Token provides +various methods to perform the only NEP-17 state-changing call, Transfer. +*/ +package nep17 + +import ( + "math/big" + + "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" +) + +// Invoker is used by TokenReader to call various safe methods. +type Invoker interface { + neptoken.Invoker +} + +// Actor is used by Token to create and send transactions. +type Actor interface { + Invoker + + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// TokenReader represents safe (read-only) methods of NEP-17 token. It can be +// 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 + + actor Actor +} + +// TransferEvent represents a Transfer event as defined in the NEP-17 standard. +type TransferEvent struct { + From util.Uint160 + To util.Uint160 + Amount *big.Int +} + +// 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} +} + +// 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)) +} + +// Transfer creates and sends a transaction that performs a `transfer` method +// call using the given parameters and checks for this call result, failing the +// transaction if it's not true. The returned values are transaction hash, its +// ValidUntilBlock value and an error if any. +func (t *Token) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error) { + script, err := t.transferScript(from, to, amount, data) + if err != nil { + return util.Uint256{}, 0, err + } + return t.actor.SendRun(script) +} + +// TransferTransaction creates a transaction that performs a `transfer` method +// call using the given parameters and checks for this call result, failing the +// transaction if it's not true. This transaction is signed, but not sent to the +// network, instead it's returned to the caller. +func (t *Token) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) { + script, err := t.transferScript(from, to, amount, data) + if err != nil { + return nil, err + } + return t.actor.MakeRun(script) +} + +// TransferUnsigned creates a transaction that performs a `transfer` method +// call using the given parameters and checks for this call result, failing the +// transaction if it's not true. This transaction is not signed and just returned +// to the caller. +func (t *Token) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) { + script, err := t.transferScript(from, to, amount, data) + if err != nil { + return nil, err + } + return t.actor.MakeUnsignedRun(script, nil) +} + +func (t *Token) transferScript(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(t.hash, "transfer", from, to, amount, data) +} diff --git a/pkg/rpcclient/nep17/nep17_test.go b/pkg/rpcclient/nep17/nep17_test.go new file mode 100644 index 000000000..2bcf45d53 --- /dev/null +++ b/pkg/rpcclient/nep17/nep17_test.go @@ -0,0 +1,106 @@ +package nep17 + +import ( + "errors" + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +type testAct struct { + err error + res *result.Invoke + tx *transaction.Transaction + txh util.Uint256 + vub uint32 +} + +func (t *testAct) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) { + return t.res, t.err +} +func (t *testAct) MakeRun(script []byte) (*transaction.Transaction, error) { + return t.tx, t.err +} +func (t *testAct) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) { + return t.tx, t.err +} +func (t *testAct) SendRun(script []byte) (util.Uint256, uint32, error) { + return t.txh, t.vub, t.err +} + +func TestReaderBalanceOf(t *testing.T) { + ta := new(testAct) + tr := NewReader(ta, util.Uint160{1, 2, 3}) + + ta.err = errors.New("") + _, err := tr.BalanceOf(util.Uint160{3, 2, 1}) + require.Error(t, err) + + ta.err = nil + ta.res = &result.Invoke{ + State: "HALT", + Stack: []stackitem.Item{ + stackitem.Make(100500), + }, + } + bal, err := tr.BalanceOf(util.Uint160{3, 2, 1}) + require.NoError(t, err) + require.Equal(t, big.NewInt(100500), bal) + + ta.res = &result.Invoke{ + State: "HALT", + Stack: []stackitem.Item{ + stackitem.Make([]stackitem.Item{}), + }, + } + _, err = tr.BalanceOf(util.Uint160{3, 2, 1}) + require.Error(t, err) +} + +func TestTokenTransfer(t *testing.T) { + ta := new(testAct) + tok := New(ta, util.Uint160{1, 2, 3}) + + ta.err = errors.New("") + _, _, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil) + require.Error(t, err) + + ta.err = nil + ta.txh = util.Uint256{1, 2, 3} + ta.vub = 42 + h, vub, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil) + require.NoError(t, err) + require.Equal(t, ta.txh, h) + require.Equal(t, ta.vub, vub) + + _, _, err = tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap()) + require.Error(t, err) +} + +func TestTokenTransferTransaction(t *testing.T) { + ta := new(testAct) + tok := New(ta, util.Uint160{1, 2, 3}) + + for _, fun := range []func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error){ + tok.TransferTransaction, + tok.TransferUnsigned, + } { + ta.err = errors.New("") + _, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil) + require.Error(t, err) + + ta.err = nil + ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42} + tx, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil) + require.NoError(t, err) + require.Equal(t, ta.tx, tx) + + _, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap()) + require.Error(t, err) + } +} diff --git a/pkg/rpcclient/neptoken/base.go b/pkg/rpcclient/neptoken/base.go new file mode 100644 index 000000000..63e258d6b --- /dev/null +++ b/pkg/rpcclient/neptoken/base.go @@ -0,0 +1,63 @@ +/* +Package neptoken contains RPC wrapper for common NEP-11 and NEP-17 methods. + +All of these methods are safe, read-only. +*/ +package neptoken + +import ( + "math/big" + + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +const ( + // MaxValidDecimals is the maximum value 'decimals' contract method can + // return to be considered as valid. It's log10(2^256), higher values + // don't make any sense on a VM with 256-bit integers. This restriction + // is not imposed by NEP-17 or NEP-11, but we do it as a sanity check + // anyway (and return plain int as a result). + MaxValidDecimals = 77 +) + +// Invoker is used by Base to call various methods. +type Invoker interface { + Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) +} + +// Base is a reader interface for common NEP-11 and NEP-17 methods built +// on top of Invoker. +type Base struct { + invoker Invoker + hash util.Uint160 +} + +// New creates an instance of Base for contract with the given hash using the +// given invoker. +func New(invoker Invoker, hash util.Uint160) *Base { + return &Base{invoker, hash} +} + +// Decimals implements `decimals` NEP-17 or NEP-11 method and returns the number +// of decimals used by token. For non-divisible NEP-11 tokens this method always +// returns zero. Values less than 0 or more than MaxValidDecimals are considered +// to be invalid (with an appropriate error) even if returned by the contract. +func (b *Base) Decimals() (int, error) { + r, err := b.invoker.Call(b.hash, "decimals") + dec, err := unwrap.LimitedInt64(r, err, 0, MaxValidDecimals) + return int(dec), err +} + +// Symbol implements `symbol` NEP-17 or NEP-11 method and returns a short token +// identifier (like "NEO" or "GAS"). +func (b *Base) Symbol() (string, error) { + return unwrap.PrintableASCIIString(b.invoker.Call(b.hash, "symbol")) +} + +// TotalSupply returns the total token supply currently available (the amount +// of minted tokens). +func (b *Base) TotalSupply() (*big.Int, error) { + return unwrap.BigInt(b.invoker.Call(b.hash, "totalSupply")) +} diff --git a/pkg/rpcclient/neptoken/base_test.go b/pkg/rpcclient/neptoken/base_test.go new file mode 100644 index 000000000..b251de5f0 --- /dev/null +++ b/pkg/rpcclient/neptoken/base_test.go @@ -0,0 +1,137 @@ +package neptoken + +import ( + "errors" + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +type testInv struct { + err error + res *result.Invoke +} + +func (t *testInv) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) { + return t.res, t.err +} + +func TestBaseErrors(t *testing.T) { + ti := new(testInv) + base := New(ti, util.Uint160{1, 2, 3}) + + ti.err = errors.New("") + _, err := base.Decimals() + require.Error(t, err) + _, err = base.Symbol() + require.Error(t, err) + _, err = base.TotalSupply() + require.Error(t, err) + + ti.err = nil + ti.res = &result.Invoke{ + State: "FAULT", + FaultException: "bad thing happened", + } + _, err = base.Decimals() + require.Error(t, err) + _, err = base.Symbol() + require.Error(t, err) + _, err = base.TotalSupply() + require.Error(t, err) + + ti.res = &result.Invoke{ + State: "HALT", + } + _, err = base.Decimals() + require.Error(t, err) + _, err = base.Symbol() + require.Error(t, err) + _, err = base.TotalSupply() + require.Error(t, err) +} + +func TestBaseDecimals(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(0), + }, + } + dec, err := base.Decimals() + require.NoError(t, err) + require.Equal(t, 0, dec) + + ti.res = &result.Invoke{ + State: "HALT", + Stack: []stackitem.Item{ + stackitem.Make(-1), + }, + } + _, err = base.Decimals() + require.Error(t, err) + + ti.res = &result.Invoke{ + State: "HALT", + Stack: []stackitem.Item{ + stackitem.Make(100500), + }, + } + _, err = base.Decimals() + require.Error(t, err) +} + +func TestBaseSymbol(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("SYM"), + }, + } + sym, err := base.Symbol() + require.NoError(t, err) + require.Equal(t, "SYM", sym) + + ti.res = &result.Invoke{ + State: "HALT", + Stack: []stackitem.Item{ + stackitem.Make("\xff"), + }, + } + _, err = base.Symbol() + require.Error(t, err) +} + +func TestBaseTotalSupply(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), + }, + } + ts, err := base.TotalSupply() + require.NoError(t, err) + require.Equal(t, big.NewInt(100500), ts) + + ti.res = &result.Invoke{ + State: "HALT", + Stack: []stackitem.Item{ + stackitem.Make([]stackitem.Item{}), + }, + } + _, err = base.TotalSupply() + require.Error(t, err) +} diff --git a/pkg/rpcclient/unwrap/unwrap.go b/pkg/rpcclient/unwrap/unwrap.go index 25d7961d5..208b041c1 100644 --- a/pkg/rpcclient/unwrap/unwrap.go +++ b/pkg/rpcclient/unwrap/unwrap.go @@ -26,7 +26,7 @@ import ( // BigInt expects correct execution (HALT state) with a single stack item // returned. A big.Int is extracted from this item and returned. func BigInt(r *result.Invoke, err error) (*big.Int, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func BigInt(r *result.Invoke, err error) (*big.Int, error) { // Bool expects correct execution (HALT state) with a single stack item // returned. A bool is extracted from this item and returned. func Bool(r *result.Invoke, err error) (bool, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return false, err } @@ -46,7 +46,7 @@ func Bool(r *result.Invoke, err error) (bool, error) { // Int64 expects correct execution (HALT state) with a single stack item // returned. An int64 is extracted from this item and returned. func Int64(r *result.Invoke, err error) (int64, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return 0, err } @@ -80,7 +80,7 @@ func LimitedInt64(r *result.Invoke, err error, min int64, max int64) (int64, err // Bytes expects correct execution (HALT state) with a single stack item // returned. A slice of bytes is extracted from this item and returned. func Bytes(r *result.Invoke, err error) ([]byte, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return nil, err } @@ -142,7 +142,7 @@ func Uint256(r *result.Invoke, err error) (util.Uint256, error) { // item returned. If this item is an iterator it's returned to the caller along // with the session ID. func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return uuid.UUID{}, result.Iterator{}, err } @@ -161,7 +161,7 @@ func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, e // be used for structures as well since they're also represented as slices of // stack items (the number of them and their types are structure-specific). func Array(r *result.Invoke, err error) ([]stackitem.Item, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return nil, err } @@ -193,7 +193,7 @@ func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) { // Map expects correct execution (HALT state) with a single stack item // returned. A stackitem.Map is extracted from this item and returned. func Map(r *result.Invoke, err error) (*stackitem.Map, error) { - itm, err := getSingleItem(r, err) + itm, err := Item(r, err) if err != nil { return nil, err } @@ -213,7 +213,9 @@ func checkResOK(r *result.Invoke, err error) error { return nil } -func getSingleItem(r *result.Invoke, err error) (stackitem.Item, error) { +// Item returns a stack item from the result if execution was successful (HALT +// state) and if it's the only element on the result stack. +func Item(r *result.Invoke, err error) (stackitem.Item, error) { err = checkResOK(r, err) if err != nil { return nil, err diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 354bf38d8..ef11a0f7b 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -29,6 +29,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/network" "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/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" @@ -54,19 +57,20 @@ func TestClient_NEP17(t *testing.T) { h, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) + rub := nep17.NewReader(invoker.New(c, nil), h) t.Run("Decimals", func(t *testing.T) { - d, err := c.NEP17Decimals(h) + d, err := rub.Decimals() require.NoError(t, err) require.EqualValues(t, 2, d) }) t.Run("TotalSupply", func(t *testing.T) { - s, err := c.NEP17TotalSupply(h) + s, err := rub.TotalSupply() require.NoError(t, err) - require.EqualValues(t, 1_000_000, s) + require.EqualValues(t, big.NewInt(1_000_000), s) }) t.Run("Symbol", func(t *testing.T) { - sym, err := c.NEP17Symbol(h) + sym, err := rub.Symbol() require.NoError(t, err) require.Equal(t, "RUB", sym) }) @@ -80,9 +84,9 @@ func TestClient_NEP17(t *testing.T) { }) t.Run("BalanceOf", func(t *testing.T) { acc := testchain.PrivateKeyByID(0).GetScriptHash() - b, err := c.NEP17BalanceOf(h, acc) + b, err := rub.BalanceOf(acc) require.NoError(t, err) - require.EqualValues(t, 877, b) + require.EqualValues(t, big.NewInt(877), b) }) } @@ -721,12 +725,16 @@ func TestCreateNEP17TransferTx(t *testing.T) { priv := testchain.PrivateKeyByID(0) acc := wallet.NewAccountFromPrivateKey(priv) + addr := priv.PublicKey().GetScriptHash() gasContractHash, err := c.GetNativeContractHash(nativenames.Gas) require.NoError(t, err) t.Run("default scope", func(t *testing.T) { - tx, err := c.CreateNEP17TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0, nil, nil) + act, err := actor.NewSimple(c, acc) + require.NoError(t, err) + gas := nep17.New(act, gasContractHash) + tx, err := gas.TransferUnsigned(addr, util.Uint160{}, big.NewInt(1000), nil) require.NoError(t, err) require.NoError(t, acc.SignTx(testchain.Network(), tx)) require.NoError(t, chain.VerifyTx(tx)) @@ -735,23 +743,31 @@ func TestCreateNEP17TransferTx(t *testing.T) { require.NoError(t, ic.VM.Run()) }) t.Run("none scope", func(t *testing.T) { - _, err := c.CreateNEP17TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0, nil, []rpcclient.SignerAccount{{ + act, err := actor.New(c, []actor.SignerAccount{{ Signer: transaction.Signer{ - Account: priv.PublicKey().GetScriptHash(), + Account: addr, Scopes: transaction.None, }, + Account: acc, }}) + require.NoError(t, err) + gas := nep17.New(act, gasContractHash) + _, err = gas.TransferUnsigned(addr, util.Uint160{}, big.NewInt(1000), nil) require.Error(t, err) }) t.Run("customcontracts scope", func(t *testing.T) { - tx, err := c.CreateNEP17TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0, nil, []rpcclient.SignerAccount{{ + act, err := actor.New(c, []actor.SignerAccount{{ Signer: transaction.Signer{ Account: priv.PublicKey().GetScriptHash(), Scopes: transaction.CustomContracts, AllowedContracts: []util.Uint160{gasContractHash}, }, + Account: acc, }}) require.NoError(t, err) + gas := nep17.New(act, gasContractHash) + tx, err := gas.TransferUnsigned(addr, util.Uint160{}, big.NewInt(1000), nil) + require.NoError(t, err) require.NoError(t, acc.SignTx(testchain.Network(), tx)) require.NoError(t, chain.VerifyTx(tx)) ic := chain.GetTestVM(trigger.Application, tx, nil)