Merge pull request #2642 from nspcc-dev/rpcclient-nep17
NEP-17 RPC client
This commit is contained in:
commit
db12341755
10 changed files with 496 additions and 36 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
111
pkg/rpcclient/nep17/nep17.go
Normal file
111
pkg/rpcclient/nep17/nep17.go
Normal file
|
@ -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)
|
||||
}
|
106
pkg/rpcclient/nep17/nep17_test.go
Normal file
106
pkg/rpcclient/nep17/nep17_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
63
pkg/rpcclient/neptoken/base.go
Normal file
63
pkg/rpcclient/neptoken/base.go
Normal file
|
@ -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"))
|
||||
}
|
137
pkg/rpcclient/neptoken/base_test.go
Normal file
137
pkg/rpcclient/neptoken/base_test.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue