mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 19:29:39 +00:00
rpcclient: introduce NEO wrapper
Notice that int64 types are used for gas per block or registration price because the price has to fit into the system fee limitation and gas per block value can't be more than 10 GAS. We use int64 for votes as well in other types since NEO is limited to 100M.
This commit is contained in:
parent
79051f21c1
commit
f011b3c3dd
8 changed files with 1201 additions and 156 deletions
|
@ -13,18 +13,15 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"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/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/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/rpcclient/neo"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
@ -287,23 +284,14 @@ func queryVoter(ctx *cli.Context) error {
|
|||
return exitErr
|
||||
}
|
||||
|
||||
neoHash, err := c.GetNativeContractHash(nativenames.Neo)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to get NEO contract hash: %w", err), 1)
|
||||
}
|
||||
inv := invoker.New(c, nil)
|
||||
neoToken := nep17.NewReader(inv, neoHash)
|
||||
neoToken := neo.NewReader(invoker.New(c, nil))
|
||||
|
||||
itm, err := unwrap.Item(inv.Call(neoHash, "getAccountState", addr))
|
||||
st, err := neoToken.GetAccountState(addr)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
st := new(state.NEOBalance)
|
||||
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)
|
||||
}
|
||||
if st == nil {
|
||||
st = new(state.NEOBalance)
|
||||
}
|
||||
dec, err := neoToken.Decimals()
|
||||
if err != nil {
|
||||
|
|
|
@ -12,14 +12,13 @@ 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/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"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/neo"
|
||||
"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"
|
||||
|
@ -248,12 +247,15 @@ func getNEP17Balance(ctx *cli.Context) error {
|
|||
continue
|
||||
}
|
||||
if gasSymbol != name {
|
||||
neoSymbol, h, err = getNativeNEP17Symbol(c, nativenames.Neo)
|
||||
n := neo.NewReader(invoker.New(c, nil))
|
||||
neoSymbol, err = n.Symbol()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if neoSymbol != name {
|
||||
continue
|
||||
} else {
|
||||
h = neo.Hash
|
||||
}
|
||||
} else {
|
||||
h = gas.Hash
|
||||
|
@ -287,19 +289,6 @@ func printAssetBalance(ctx *cli.Context, asset util.Uint160, tokenName, tokenSym
|
|||
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balance.LastUpdated)
|
||||
}
|
||||
|
||||
func getNativeNEP17Symbol(c *rpcclient.Client, name string) (string, util.Uint160, error) {
|
||||
h, err := c.GetNativeContractHash(name)
|
||||
if err != nil {
|
||||
return "", util.Uint160{}, fmt.Errorf("failed to get native %s hash: %w", name, err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
return symbol, h, nil
|
||||
}
|
||||
|
||||
func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string, standard string) (*wallet.Token, error) {
|
||||
return getMatchingTokenAux(ctx, func(i int) *wallet.Token {
|
||||
return w.Extra.Tokens[i]
|
||||
|
|
|
@ -7,15 +7,12 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"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/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
@ -78,24 +75,18 @@ func newValidatorCommands() []cli.Command {
|
|||
}
|
||||
|
||||
func handleRegister(ctx *cli.Context) error {
|
||||
return handleCandidate(ctx, true)
|
||||
return handleNeoAction(ctx, func(contract *neo.Contract, _ util.Uint160, acc *wallet.Account) (*transaction.Transaction, error) {
|
||||
return contract.RegisterCandidateUnsigned(acc.PrivateKey().PublicKey())
|
||||
})
|
||||
}
|
||||
|
||||
func handleUnregister(ctx *cli.Context) error {
|
||||
return handleCandidate(ctx, false)
|
||||
return handleNeoAction(ctx, func(contract *neo.Contract, _ util.Uint160, acc *wallet.Account) (*transaction.Transaction, error) {
|
||||
return contract.UnregisterCandidateUnsigned(acc.PrivateKey().PublicKey())
|
||||
})
|
||||
}
|
||||
|
||||
func handleCandidate(ctx *cli.Context, register bool) error {
|
||||
const (
|
||||
regMethod = "registerCandidate"
|
||||
unregMethod = "unregisterCandidate"
|
||||
)
|
||||
var (
|
||||
err error
|
||||
script []byte
|
||||
sysGas int64
|
||||
)
|
||||
|
||||
func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *wallet.Account) (*transaction.Transaction, error)) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -121,124 +112,42 @@ func handleCandidate(ctx *cli.Context, register bool) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
act, err := actor.NewSimple(c, acc)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("RPC actor issue: %w", err), 1)
|
||||
}
|
||||
|
||||
gas := flags.Fixed8FromContext(ctx, "gas")
|
||||
neoContractHash, err := c.GetNativeContractHash(nativenames.Neo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unregScript, err := smartcontract.CreateCallWithAssertScript(neoContractHash, unregMethod, acc.PrivateKey().PublicKey().Bytes())
|
||||
contract := neo.New(act)
|
||||
tx, err := mkTx(contract, addr, acc)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if !register {
|
||||
script = unregScript
|
||||
} else {
|
||||
script, err = smartcontract.CreateCallWithAssertScript(neoContractHash, regMethod, acc.PrivateKey().PublicKey().Bytes())
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
// Registration price is normally much bigger than MaxGasInvoke, so to
|
||||
// determine proper amount of GAS we _always_ run unreg script and then
|
||||
// add registration price to it if needed.
|
||||
r, err := act.Run(unregScript)
|
||||
tx.NetworkFee += int64(gas)
|
||||
res, _, err := act.SignAndSend(tx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Run failure: %w", err), 1)
|
||||
}
|
||||
sysGas = r.GasConsumed
|
||||
if register {
|
||||
// Deregistration will fail, so there is no point in checking State.
|
||||
regPrice, err := c.GetCandidateRegisterPrice()
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
sysGas += regPrice
|
||||
} else if r.State != vmstate.Halt.String() {
|
||||
return cli.NewExitError(fmt.Errorf("unregister transaction failed: %s", r.FaultException), 1)
|
||||
}
|
||||
res, _, err := act.SendUncheckedRun(script, sysGas, nil, func(t *transaction.Transaction) error {
|
||||
t.NetworkFee += int64(gas)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to push transaction: %w", err), 1)
|
||||
return cli.NewExitError(fmt.Errorf("failed to sign/send transaction: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleVote(ctx *cli.Context) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
wall, pass, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
if !addrFlag.IsSet {
|
||||
return cli.NewExitError("address was not provided", 1)
|
||||
}
|
||||
addr := addrFlag.Uint160()
|
||||
acc, err := getDecryptedAccount(wall, addr, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
var pub *keys.PublicKey
|
||||
pubStr := ctx.String("candidate")
|
||||
if pubStr != "" {
|
||||
pub, err = keys.NewPublicKeyFromString(pubStr)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid public key: '%s'", pubStr), 1)
|
||||
return handleNeoAction(ctx, func(contract *neo.Contract, addr util.Uint160, acc *wallet.Account) (*transaction.Transaction, error) {
|
||||
var (
|
||||
err error
|
||||
pub *keys.PublicKey
|
||||
)
|
||||
pubStr := ctx.String("candidate")
|
||||
if pubStr != "" {
|
||||
pub, err = keys.NewPublicKeyFromString(pubStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key: '%s'", pubStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
act, err := actor.NewSimple(c, acc)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("RPC actor issue: %w", err), 1)
|
||||
}
|
||||
|
||||
var pubArg interface{}
|
||||
if pub != nil {
|
||||
pubArg = pub.Bytes()
|
||||
}
|
||||
|
||||
gas := flags.Fixed8FromContext(ctx, "gas")
|
||||
neoContractHash, err := c.GetNativeContractHash(nativenames.Neo)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
script, err := smartcontract.CreateCallWithAssertScript(neoContractHash, "vote", addr.BytesBE(), pubArg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
res, _, err := act.SendTunedRun(script, nil, func(r *result.Invoke, t *transaction.Transaction) error {
|
||||
if r.State != vmstate.Halt.String() {
|
||||
return fmt.Errorf("invocation failed: %s", r.FaultException)
|
||||
}
|
||||
t.NetworkFee += int64(gas)
|
||||
return nil
|
||||
return contract.VoteUnsigned(addr, pub)
|
||||
})
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to push invocation transaction: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDecryptedAccount tries to unlock the specified account. If password is nil, it will be requested via terminal.
|
||||
|
|
|
@ -15,11 +15,10 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"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/rpcclient/neo"
|
||||
"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"
|
||||
|
@ -336,11 +335,7 @@ func claimGas(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
neoContractHash, err := c.GetNativeContractHash(nativenames.Neo)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
neoToken := nep17.New(act, neoContractHash)
|
||||
neoToken := neo.New(act)
|
||||
hash, _, err := neoToken.Transfer(scriptHash, scriptHash, big.NewInt(0), nil)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
|
|
|
@ -36,11 +36,15 @@ func (c *Client) GetNNSPrice(nnsHash util.Uint160) (int64, error) {
|
|||
}
|
||||
|
||||
// GetGasPerBlock invokes `getGasPerBlock` method on a native NEO contract.
|
||||
//
|
||||
// Deprecated: please use neo subpackage. This method will be removed in future releases.
|
||||
func (c *Client) GetGasPerBlock() (int64, error) {
|
||||
return c.getFromNEO("getGasPerBlock")
|
||||
}
|
||||
|
||||
// GetCandidateRegisterPrice invokes `getRegisterPrice` method on native NEO contract.
|
||||
//
|
||||
// Deprecated: please use neo subpackage. This method will be removed in future releases.
|
||||
func (c *Client) GetCandidateRegisterPrice() (int64, error) {
|
||||
return c.getFromNEO("getRegisterPrice")
|
||||
}
|
||||
|
|
462
pkg/rpcclient/neo/neo.go
Normal file
462
pkg/rpcclient/neo/neo.go
Normal file
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
Package neo provides an RPC-based wrapper for the NEOToken contract.
|
||||
|
||||
Safe methods are encapsulated into ContractReader structure while Contract provides
|
||||
various methods to perform state-changing calls.
|
||||
*/
|
||||
package neo
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"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/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"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/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
const (
|
||||
setGasMethod = "setGasPerBlock"
|
||||
setRegMethod = "setRegisterPrice"
|
||||
)
|
||||
|
||||
// Invoker is used by ContractReader to perform read-only calls.
|
||||
type Invoker interface {
|
||||
nep17.Invoker
|
||||
|
||||
CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error)
|
||||
TerminateSession(sessionID uuid.UUID) error
|
||||
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
|
||||
}
|
||||
|
||||
// Actor is used by Contract to create and send transactions.
|
||||
type Actor interface {
|
||||
nep17.Actor
|
||||
Invoker
|
||||
|
||||
Run(script []byte) (*result.Invoke, error)
|
||||
MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error)
|
||||
MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error)
|
||||
Sign(tx *transaction.Transaction) error
|
||||
SignAndSend(tx *transaction.Transaction) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// ContractReader represents safe (read-only) methods of NEO. It can be
|
||||
// used to query various data.
|
||||
type ContractReader struct {
|
||||
nep17.TokenReader
|
||||
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
// Contract provides full NEO interface, both safe and state-changing methods.
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
nep17.Token
|
||||
|
||||
actor Actor
|
||||
}
|
||||
|
||||
// CandidateStateEvent represents a CandidateStateChanged NEO event.
|
||||
type CandidateStateEvent struct {
|
||||
Key *keys.PublicKey
|
||||
Registered bool
|
||||
Votes *big.Int
|
||||
}
|
||||
|
||||
// VoteEvent represents a Vote NEO event.
|
||||
type VoteEvent struct {
|
||||
Account util.Uint160
|
||||
From *keys.PublicKey
|
||||
To *keys.PublicKey
|
||||
Amount *big.Int
|
||||
}
|
||||
|
||||
// ValidatorIterator is used for iterating over GetAllCandidates results.
|
||||
type ValidatorIterator struct {
|
||||
client Invoker
|
||||
session uuid.UUID
|
||||
iterator result.Iterator
|
||||
}
|
||||
|
||||
// Hash stores the hash of the native NEOToken contract.
|
||||
var Hash = state.CreateNativeContractHash(nativenames.Neo)
|
||||
|
||||
// NewReader creates an instance of ContractReader to get data from the NEO
|
||||
// contract.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
return &ContractReader{*nep17.NewReader(invoker, Hash), invoker}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract to perform state-changing actions in the
|
||||
// NEO contract.
|
||||
func New(actor Actor) *Contract {
|
||||
return &Contract{*NewReader(actor), *nep17.New(actor, Hash), actor}
|
||||
}
|
||||
|
||||
// GetAccountState returns current NEO balance state for the account which
|
||||
// includes balance and voting data. It can return nil balance with no error
|
||||
// if the account given has no NEO.
|
||||
func (c *ContractReader) GetAccountState(account util.Uint160) (*state.NEOBalance, error) {
|
||||
itm, err := unwrap.Item(c.invoker.Call(Hash, "getAccountState", account))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := itm.(stackitem.Null); ok {
|
||||
return nil, nil
|
||||
}
|
||||
res := new(state.NEOBalance)
|
||||
err = res.FromStackItem(itm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetAllCandidates returns an iterator that allows to retrieve all registered
|
||||
// validators from it. It depends on the server to provide proper session-based
|
||||
// iterator, but can also work with expanded one.
|
||||
func (c *ContractReader) GetAllCandidates() (*ValidatorIterator, error) {
|
||||
sess, iter, err := unwrap.SessionIterator(c.invoker.Call(Hash, "getAllCandidates"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ValidatorIterator{
|
||||
client: c.invoker,
|
||||
iterator: iter,
|
||||
session: sess,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAllCandidatesExpanded is similar to GetAllCandidates (uses the same NEO
|
||||
// method), but can be useful if the server used doesn't support sessions and
|
||||
// doesn't expand iterators. It creates a script that will get num of result
|
||||
// items from the iterator right in the VM and return them to you. It's only
|
||||
// limited by VM stack and GAS available for RPC invocations.
|
||||
func (c *ContractReader) GetAllCandidatesExpanded(num int) ([]result.Validator, error) {
|
||||
arr, err := unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "getAllCandidates", num))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return itemsToValidators(arr)
|
||||
}
|
||||
|
||||
// Next returns the next set of elements from the iterator (up to num of them).
|
||||
// It can return less than num elements in case iterator doesn't have that many
|
||||
// or zero elements if the iterator has no more elements or the session is
|
||||
// expired.
|
||||
func (v *ValidatorIterator) Next(num int) ([]result.Validator, error) {
|
||||
items, err := v.client.TraverseIterator(v.session, &v.iterator, num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return itemsToValidators(items)
|
||||
}
|
||||
|
||||
// Terminate closes the iterator session used by ValidatorIterator (if it's
|
||||
// session-based).
|
||||
func (v *ValidatorIterator) Terminate() error {
|
||||
if v.iterator.ID == nil {
|
||||
return nil
|
||||
}
|
||||
return v.client.TerminateSession(v.session)
|
||||
}
|
||||
|
||||
// GetCandidates returns the list of validators with their vote count. This
|
||||
// method is mostly useful for historic invocations because the RPC protocol
|
||||
// provides direct getcandidates call that returns more data and works faster.
|
||||
// The contract only returns up to 256 candidates in response to this method, so
|
||||
// if there are more of them on the network you will get a truncated result, use
|
||||
// GetAllCandidates to solve this problem.
|
||||
func (c *ContractReader) GetCandidates() ([]result.Validator, error) {
|
||||
arr, err := unwrap.Array(c.invoker.Call(Hash, "getCandidates"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return itemsToValidators(arr)
|
||||
}
|
||||
|
||||
func itemsToValidators(arr []stackitem.Item) ([]result.Validator, error) {
|
||||
res := make([]result.Validator, len(arr))
|
||||
for i, itm := range arr {
|
||||
str, ok := itm.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("item #%d is not a structure", i)
|
||||
}
|
||||
if len(str) != 2 {
|
||||
return nil, fmt.Errorf("item #%d has wrong length", i)
|
||||
}
|
||||
b, err := str[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("item #%d has wrong key: %w", i, err)
|
||||
}
|
||||
k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("item #%d has wrong key: %w", i, err)
|
||||
}
|
||||
votes, err := str[1].TryInteger()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("item #%d has wrong votes: %w", i, err)
|
||||
}
|
||||
if !votes.IsInt64() {
|
||||
return nil, fmt.Errorf("item #%d has too big number of votes", i)
|
||||
}
|
||||
res[i].PublicKey = *k
|
||||
res[i].Votes = votes.Int64()
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetCommittee returns the list of committee member public keys. This
|
||||
// method is mostly useful for historic invocations because the RPC protocol
|
||||
// provides direct getcommittee call that works faster.
|
||||
func (c *ContractReader) GetCommittee() (keys.PublicKeys, error) {
|
||||
return unwrap.ArrayOfPublicKeys(c.invoker.Call(Hash, "getCommittee"))
|
||||
}
|
||||
|
||||
// GetNextBlockValidators returns the list of validator keys that will sign the
|
||||
// next block. This method is mostly useful for historic invocations because the
|
||||
// RPC protocol provides direct getnextblockvalidators call that provides more
|
||||
// data and works faster.
|
||||
func (c *ContractReader) GetNextBlockValidators() (keys.PublicKeys, error) {
|
||||
return unwrap.ArrayOfPublicKeys(c.invoker.Call(Hash, "getNextBlockValidators"))
|
||||
}
|
||||
|
||||
// GetGasPerBlock returns the amount of GAS generated in each block.
|
||||
func (c *ContractReader) GetGasPerBlock() (int64, error) {
|
||||
return unwrap.Int64(c.invoker.Call(Hash, "getGasPerBlock"))
|
||||
}
|
||||
|
||||
// GetRegisterPrice returns the price of candidate key registration.
|
||||
func (c *ContractReader) GetRegisterPrice() (int64, error) {
|
||||
return unwrap.Int64(c.invoker.Call(Hash, "getRegisterPrice"))
|
||||
}
|
||||
|
||||
// UnclaimedGas allows to calculate the amount of GAS that will be generated if
|
||||
// any NEO state change ("claim") is to happen for the given account at the given
|
||||
// block number. This method is mostly useful for historic invocations because
|
||||
// the RPC protocol provides direct getunclaimedgas method that works faster.
|
||||
func (c *ContractReader) UnclaimedGas(account util.Uint160, end uint32) (*big.Int, error) {
|
||||
return unwrap.BigInt(c.invoker.Call(Hash, "unclaimedGas", account, end))
|
||||
}
|
||||
|
||||
// RegisterCandidate creates and sends a transaction that adds the given key to
|
||||
// the list of candidates that can be voted for. The return result from the
|
||||
// "registerCandidate" method is checked to be true, so transaction fails (with
|
||||
// FAULT state) if not successful. Notice that for this call to work it must be
|
||||
// witnessed by the simple account derived from the given key, so use an
|
||||
// appropriate Actor. The returned values are transaction hash, its
|
||||
// ValidUntilBlock value and an error if any.
|
||||
//
|
||||
// Notice that unlike for all other methods the script for this one is not
|
||||
// test-executed in its final form because most networks have registration price
|
||||
// set to be much higher than typical RPC server allows to spend during
|
||||
// test-execution. This adds some risk that it might fail on-chain, but in
|
||||
// practice it's not likely to happen if signers are set up correctly.
|
||||
func (c *Contract) RegisterCandidate(k *keys.PublicKey) (util.Uint256, uint32, error) {
|
||||
tx, err := c.RegisterCandidateUnsigned(k)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SignAndSend(tx)
|
||||
}
|
||||
|
||||
// RegisterCandidateTransaction creates a transaction that adds the given key to
|
||||
// the list of candidates that can be voted for. The return result from the
|
||||
// "registerCandidate" method is checked to be true, so transaction fails (with
|
||||
// FAULT state) if not successful. Notice that for this call to work it must be
|
||||
// witnessed by the simple account derived from the given key, so use an
|
||||
// appropriate Actor. The transaction is signed, but not sent to the network,
|
||||
// instead it's returned to the caller.
|
||||
//
|
||||
// Notice that unlike for all other methods the script for this one is not
|
||||
// test-executed in its final form because most networks have registration price
|
||||
// set to be much higher than typical RPC server allows to spend during
|
||||
// test-execution. This adds some risk that it might fail on-chain, but in
|
||||
// practice it's not likely to happen if signers are set up correctly.
|
||||
func (c *Contract) RegisterCandidateTransaction(k *keys.PublicKey) (*transaction.Transaction, error) {
|
||||
tx, err := c.RegisterCandidateUnsigned(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.actor.Sign(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// RegisterCandidateUnsigned creates a transaction that adds the given key to
|
||||
// the list of candidates that can be voted for. The return result from the
|
||||
// "registerCandidate" method is checked to be true, so transaction fails (with
|
||||
// FAULT state) if not successful. Notice that for this call to work it must be
|
||||
// witnessed by the simple account derived from the given key, so use an
|
||||
// appropriate Actor. The transaction is not signed and just returned to the
|
||||
// caller.
|
||||
//
|
||||
// Notice that unlike for all other methods the script for this one is not
|
||||
// test-executed in its final form because most networks have registration price
|
||||
// set to be much higher than typical RPC server allows to spend during
|
||||
// test-execution. This adds some risk that it might fail on-chain, but in
|
||||
// practice it's not likely to happen if signers are set up correctly.
|
||||
func (c *Contract) RegisterCandidateUnsigned(k *keys.PublicKey) (*transaction.Transaction, error) {
|
||||
// It's an unregister script intentionally.
|
||||
r, err := c.actor.Run(regScript(true, k))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regPrice, err := c.GetRegisterPrice()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedUncheckedRun(regScript(false, k), r.GasConsumed+regPrice, nil)
|
||||
}
|
||||
|
||||
// UnregisterCandidate creates and sends a transaction that removes the key from
|
||||
// the list of candidates that can be voted for. The return result from the
|
||||
// "unregisterCandidate" method is checked to be true, so transaction fails (with
|
||||
// FAULT state) if not successful. Notice that for this call to work it must be
|
||||
// witnessed by the simple account derived from the given key, so use an
|
||||
// appropriate Actor. The returned values are transaction hash, its
|
||||
// ValidUntilBlock value and an error if any.
|
||||
func (c *Contract) UnregisterCandidate(k *keys.PublicKey) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendRun(regScript(true, k))
|
||||
}
|
||||
|
||||
// UnregisterCandidateTransaction creates a transaction that removes the key from
|
||||
// the list of candidates that can be voted for. The return result from the
|
||||
// "unregisterCandidate" method is checked to be true, so transaction fails (with
|
||||
// FAULT state) if not successful. Notice that for this call to work it must be
|
||||
// witnessed by the simple account derived from the given key, so use an
|
||||
// appropriate Actor. The transaction is signed, but not sent to the network,
|
||||
// instead it's returned to the caller.
|
||||
func (c *Contract) UnregisterCandidateTransaction(k *keys.PublicKey) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeRun(regScript(true, k))
|
||||
}
|
||||
|
||||
// UnregisterCandidateUnsigned creates a transaction that removes the key from
|
||||
// the list of candidates that can be voted for. The return result from the
|
||||
// "unregisterCandidate" method is checked to be true, so transaction fails (with
|
||||
// FAULT state) if not successful. Notice that for this call to work it must be
|
||||
// witnessed by the simple account derived from the given key, so use an
|
||||
// appropriate Actor. The transaction is not signed and just returned to the
|
||||
// caller.
|
||||
func (c *Contract) UnregisterCandidateUnsigned(k *keys.PublicKey) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedRun(regScript(true, k), nil)
|
||||
}
|
||||
|
||||
func regScript(unreg bool, k *keys.PublicKey) []byte {
|
||||
var method = "registerCandidate"
|
||||
|
||||
if unreg {
|
||||
method = "unregisterCandidate"
|
||||
}
|
||||
|
||||
// We know parameters exactly (unlike with nep17.Transfer), so this can't fail.
|
||||
script, _ := smartcontract.CreateCallWithAssertScript(Hash, method, k.Bytes())
|
||||
return script
|
||||
}
|
||||
|
||||
// Vote creates and sends a transaction that casts a vote from the given account
|
||||
// to the given key which can be nil (in which case any previous vote is removed).
|
||||
// The return result from the "vote" method is checked to be true, so transaction
|
||||
// fails (with FAULT state) if voting is not successful. The returned values are
|
||||
// transaction hash, its ValidUntilBlock value and an error if any.
|
||||
func (c *Contract) Vote(account util.Uint160, voteTo *keys.PublicKey) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendRun(voteScript(account, voteTo))
|
||||
}
|
||||
|
||||
// VoteTransaction creates a transaction that casts a vote from the given account
|
||||
// to the given key which can be nil (in which case any previous vote is removed).
|
||||
// The return result from the "vote" method is checked to be true, so transaction
|
||||
// fails (with FAULT state) if voting is not successful. The transaction is signed,
|
||||
// but not sent to the network, instead it's returned to the caller.
|
||||
func (c *Contract) VoteTransaction(account util.Uint160, voteTo *keys.PublicKey) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeRun(voteScript(account, voteTo))
|
||||
}
|
||||
|
||||
// VoteUnsigned creates a transaction that casts a vote from the given account
|
||||
// to the given key which can be nil (in which case any previous vote is removed).
|
||||
// The return result from the "vote" method is checked to be true, so transaction
|
||||
// fails (with FAULT state) if voting is not successful. The transaction is not
|
||||
// signed and just returned to the caller.
|
||||
func (c *Contract) VoteUnsigned(account util.Uint160, voteTo *keys.PublicKey) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedRun(voteScript(account, voteTo), nil)
|
||||
}
|
||||
|
||||
func voteScript(account util.Uint160, voteTo *keys.PublicKey) []byte {
|
||||
var param interface{}
|
||||
|
||||
if voteTo != nil {
|
||||
param = voteTo.Bytes()
|
||||
}
|
||||
// We know parameters exactly (unlike with nep17.Transfer), so this can't fail.
|
||||
script, _ := smartcontract.CreateCallWithAssertScript(Hash, "vote", account, param)
|
||||
return script
|
||||
}
|
||||
|
||||
// SetGasPerBlock creates and sends a transaction that sets the new amount of
|
||||
// GAS to be generated in each block. The action is successful when transaction
|
||||
// ends in HALT state. Notice that this setting can be changed only by the
|
||||
// network's committee, so use an appropriate Actor. The returned values are
|
||||
// transaction hash, its ValidUntilBlock value and an error if any.
|
||||
func (c *Contract) SetGasPerBlock(gas int64) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(Hash, setGasMethod, gas)
|
||||
}
|
||||
|
||||
// SetGasPerBlockTransaction creates a transaction that sets the new amount of
|
||||
// GAS to be generated in each block. The action is successful when transaction
|
||||
// ends in HALT state. Notice that this setting can be changed only by the
|
||||
// network's committee, so use an appropriate Actor. The transaction is signed,
|
||||
// but not sent to the network, instead it's returned to the caller.
|
||||
func (c *Contract) SetGasPerBlockTransaction(gas int64) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(Hash, setGasMethod, gas)
|
||||
}
|
||||
|
||||
// SetGasPerBlockUnsigned creates a transaction that sets the new amount of
|
||||
// GAS to be generated in each block. The action is successful when transaction
|
||||
// ends in HALT state. Notice that this setting can be changed only by the
|
||||
// network's committee, so use an appropriate Actor. The transaction is not
|
||||
// signed and just returned to the caller.
|
||||
func (c *Contract) SetGasPerBlockUnsigned(gas int64) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(Hash, setGasMethod, nil, gas)
|
||||
}
|
||||
|
||||
// SetRegisterPrice creates and sends a transaction that sets the new candidate
|
||||
// registration price (in GAS). The action is successful when transaction
|
||||
// ends in HALT state. Notice that this setting can be changed only by the
|
||||
// network's committee, so use an appropriate Actor. The returned values are
|
||||
// transaction hash, its ValidUntilBlock value and an error if any.
|
||||
func (c *Contract) SetRegisterPrice(price int64) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(Hash, setRegMethod, price)
|
||||
}
|
||||
|
||||
// SetRegisterPriceTransaction creates a transaction that sets the new candidate
|
||||
// registration price (in GAS). The action is successful when transaction
|
||||
// ends in HALT state. Notice that this setting can be changed only by the
|
||||
// network's committee, so use an appropriate Actor. The transaction is signed,
|
||||
// but not sent to the network, instead it's returned to the caller.
|
||||
func (c *Contract) SetRegisterPriceTransaction(price int64) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(Hash, setRegMethod, price)
|
||||
}
|
||||
|
||||
// SetRegisterPriceUnsigned creates a transaction that sets the new candidate
|
||||
// registration price (in GAS). The action is successful when transaction
|
||||
// ends in HALT state. Notice that this setting can be changed only by the
|
||||
// network's committee, so use an appropriate Actor. The transaction is not
|
||||
// signed and just returned to the caller.
|
||||
func (c *Contract) SetRegisterPriceUnsigned(price int64) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(Hash, setRegMethod, nil, price)
|
||||
}
|
568
pkg/rpcclient/neo/neo_test.go
Normal file
568
pkg/rpcclient/neo/neo_test.go
Normal file
|
@ -0,0 +1,568 @@
|
|||
package neo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"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/crypto/keys"
|
||||
"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
|
||||
ser error
|
||||
res *result.Invoke
|
||||
rre *result.Invoke
|
||||
rer error
|
||||
tx *transaction.Transaction
|
||||
txh util.Uint256
|
||||
vub uint32
|
||||
inv *result.Invoke
|
||||
}
|
||||
|
||||
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 (t *testAct) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) {
|
||||
return t.txh, t.vub, t.err
|
||||
}
|
||||
func (t *testAct) Run(script []byte) (*result.Invoke, error) {
|
||||
return t.rre, t.rer
|
||||
}
|
||||
func (t *testAct) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) Sign(tx *transaction.Transaction) error {
|
||||
return t.ser
|
||||
}
|
||||
func (t *testAct) SignAndSend(tx *transaction.Transaction) (util.Uint256, uint32, error) {
|
||||
return t.txh, t.vub, t.err
|
||||
}
|
||||
func (t *testAct) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) {
|
||||
return t.inv, t.err
|
||||
}
|
||||
func (t *testAct) TerminateSession(sessionID uuid.UUID) error {
|
||||
return t.err
|
||||
}
|
||||
func (t *testAct) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) {
|
||||
return t.res.Stack, t.err
|
||||
}
|
||||
|
||||
func TestGetAccountState(t *testing.T) {
|
||||
ta := &testAct{}
|
||||
neo := NewReader(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := neo.GetAccountState(util.Uint160{})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetAccountState(util.Uint160{})
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Null{},
|
||||
},
|
||||
}
|
||||
st, err := neo.GetAccountState(util.Uint160{})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, st)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(100500),
|
||||
stackitem.Make(42),
|
||||
stackitem.Null{},
|
||||
}),
|
||||
},
|
||||
}
|
||||
st, err = neo.GetAccountState(util.Uint160{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &state.NEOBalance{
|
||||
NEP17Balance: state.NEP17Balance{
|
||||
Balance: *big.NewInt(100500),
|
||||
},
|
||||
BalanceHeight: 42,
|
||||
}, st)
|
||||
}
|
||||
|
||||
func TestGetAllCandidates(t *testing.T) {
|
||||
ta := &testAct{}
|
||||
neo := NewReader(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := neo.GetAllCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
iid := uuid.New()
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
ID: &iid,
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetAllCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
// Session-based iterator.
|
||||
sid := uuid.New()
|
||||
ta.res = &result.Invoke{
|
||||
Session: sid,
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
ID: &iid,
|
||||
}),
|
||||
},
|
||||
}
|
||||
iter, err := neo.GetAllCandidates()
|
||||
require.NoError(t, err)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
ta.res = &result.Invoke{
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(k.PublicKey().Bytes()),
|
||||
stackitem.Make(100500),
|
||||
}),
|
||||
},
|
||||
}
|
||||
vals, err := iter.Next(10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(vals))
|
||||
require.Equal(t, result.Validator{
|
||||
PublicKey: *k.PublicKey(),
|
||||
Votes: 100500,
|
||||
}, vals[0])
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err = iter.Next(1)
|
||||
require.Error(t, err)
|
||||
|
||||
err = iter.Terminate()
|
||||
require.Error(t, err)
|
||||
|
||||
// Value-based iterator.
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.NewInterop(result.Iterator{
|
||||
Values: []stackitem.Item{
|
||||
stackitem.Make(k.PublicKey().Bytes()),
|
||||
stackitem.Make(100500),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
iter, err = neo.GetAllCandidates()
|
||||
require.NoError(t, err)
|
||||
|
||||
ta.err = errors.New("")
|
||||
err = iter.Terminate()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetCandidates(t *testing.T) {
|
||||
ta := &testAct{}
|
||||
neo := NewReader(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
cands, err := neo.GetCandidates()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(cands))
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{stackitem.Make(42)},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Null{},
|
||||
stackitem.Null{},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make("some"),
|
||||
stackitem.Null{},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(k.PublicKey().Bytes()),
|
||||
stackitem.Null{},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(k.PublicKey().Bytes()),
|
||||
stackitem.Make("canbeabigint"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
_, err = neo.GetCandidates()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetKeys(t *testing.T) {
|
||||
ta := &testAct{}
|
||||
neo := NewReader(ta)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, m := range []func() (keys.PublicKeys, error){neo.GetCommittee, neo.GetNextBlockValidators} {
|
||||
ta.err = errors.New("")
|
||||
_, err := m()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{stackitem.Make(k.PublicKey().Bytes())}),
|
||||
},
|
||||
}
|
||||
ks, err := m()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ks)
|
||||
require.Equal(t, 1, len(ks))
|
||||
require.Equal(t, k.PublicKey(), ks[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInts(t *testing.T) {
|
||||
ta := &testAct{}
|
||||
neo := NewReader(ta)
|
||||
|
||||
meth := []func() (int64, error){
|
||||
neo.GetGasPerBlock,
|
||||
neo.GetRegisterPrice,
|
||||
}
|
||||
|
||||
ta.err = errors.New("")
|
||||
for _, m := range meth {
|
||||
_, err := m()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
},
|
||||
}
|
||||
for _, m := range meth {
|
||||
val, err := m()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(42), val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnclaimedGas(t *testing.T) {
|
||||
ta := &testAct{}
|
||||
neo := NewReader(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := neo.UnclaimedGas(util.Uint160{}, 100500)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = neo.UnclaimedGas(util.Uint160{}, 100500)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
},
|
||||
}
|
||||
val, err := neo.UnclaimedGas(util.Uint160{}, 100500)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(42), val)
|
||||
}
|
||||
|
||||
func TestIntSetters(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
neo := New(ta)
|
||||
|
||||
meth := []func(int64) (util.Uint256, uint32, error){
|
||||
neo.SetGasPerBlock,
|
||||
neo.SetRegisterPrice,
|
||||
}
|
||||
|
||||
ta.err = errors.New("")
|
||||
for _, m := range meth {
|
||||
_, _, err := m(42)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
for _, m := range meth {
|
||||
h, vub, err := m(100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntTransactions(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
neo := New(ta)
|
||||
|
||||
for _, fun := range []func(int64) (*transaction.Transaction, error){
|
||||
neo.SetGasPerBlockTransaction,
|
||||
neo.SetGasPerBlockUnsigned,
|
||||
neo.SetRegisterPriceTransaction,
|
||||
neo.SetRegisterPriceUnsigned,
|
||||
} {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(1)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := fun(1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVote(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
neo := New(ta)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err = neo.Vote(util.Uint160{}, nil)
|
||||
require.Error(t, err)
|
||||
_, _, err = neo.Vote(util.Uint160{}, k.PublicKey())
|
||||
require.Error(t, err)
|
||||
_, err = neo.VoteTransaction(util.Uint160{}, nil)
|
||||
require.Error(t, err)
|
||||
_, err = neo.VoteTransaction(util.Uint160{}, k.PublicKey())
|
||||
require.Error(t, err)
|
||||
_, err = neo.VoteUnsigned(util.Uint160{}, nil)
|
||||
require.Error(t, err)
|
||||
_, err = neo.VoteUnsigned(util.Uint160{}, k.PublicKey())
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
|
||||
h, vub, err := neo.Vote(util.Uint160{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
h, vub, err = neo.Vote(util.Uint160{}, k.PublicKey())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := neo.VoteTransaction(util.Uint160{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
tx, err = neo.VoteUnsigned(util.Uint160{}, k.PublicKey())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
}
|
||||
|
||||
func TestRegisterCandidate(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
neo := New(ta)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pk := k.PublicKey()
|
||||
|
||||
ta.rer = errors.New("")
|
||||
_, _, err = neo.RegisterCandidate(pk)
|
||||
require.Error(t, err)
|
||||
_, err = neo.RegisterCandidateTransaction(pk)
|
||||
require.Error(t, err)
|
||||
_, err = neo.RegisterCandidateUnsigned(pk)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.rer = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
ta.rre = &result.Invoke{
|
||||
GasConsumed: 100500,
|
||||
}
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
},
|
||||
}
|
||||
|
||||
h, vub, err := neo.RegisterCandidate(pk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := neo.RegisterCandidateTransaction(pk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
tx, err = neo.RegisterCandidateUnsigned(pk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
ta.ser = errors.New("")
|
||||
_, err = neo.RegisterCandidateTransaction(pk)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err = neo.RegisterCandidateUnsigned(pk)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUnregisterCandidate(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
neo := New(ta)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pk := k.PublicKey()
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err = neo.UnregisterCandidate(pk)
|
||||
require.Error(t, err)
|
||||
_, err = neo.UnregisterCandidateTransaction(pk)
|
||||
require.Error(t, err)
|
||||
_, err = neo.UnregisterCandidateUnsigned(pk)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
|
||||
h, vub, err := neo.UnregisterCandidate(pk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := neo.UnregisterCandidateTransaction(pk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
tx, err = neo.UnregisterCandidateUnsigned(pk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
}
|
|
@ -33,6 +33,7 @@ import (
|
|||
"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/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"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/rpcclient/oracle"
|
||||
|
@ -291,6 +292,135 @@ func TestClientManagementContract(t *testing.T) {
|
|||
require.Equal(t, 1, len(appLog.Executions[0].Events))
|
||||
}
|
||||
|
||||
func TestClientNEOContract(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
defer rpcSrv.Shutdown()
|
||||
|
||||
c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
neoR := neo.NewReader(invoker.New(c, nil))
|
||||
|
||||
sym, err := neoR.Symbol()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "NEO", sym)
|
||||
|
||||
dec, err := neoR.Decimals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, dec)
|
||||
|
||||
ts, err := neoR.TotalSupply()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(1_0000_0000), ts)
|
||||
|
||||
comm, err := neoR.GetCommittee()
|
||||
require.NoError(t, err)
|
||||
commScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(comm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testchain.CommitteeScriptHash(), hash.Hash160(commScript))
|
||||
|
||||
vals, err := neoR.GetNextBlockValidators()
|
||||
require.NoError(t, err)
|
||||
valsScript, err := smartcontract.CreateDefaultMultiSigRedeemScript(vals)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testchain.MultisigScriptHash(), hash.Hash160(valsScript))
|
||||
|
||||
gpb, err := neoR.GetGasPerBlock()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(5_0000_0000), gpb)
|
||||
|
||||
regP, err := neoR.GetRegisterPrice()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1000_0000_0000), regP)
|
||||
|
||||
acc0 := testchain.PrivateKey(0).PublicKey().GetScriptHash()
|
||||
uncl, err := neoR.UnclaimedGas(acc0, 100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(48000), uncl)
|
||||
|
||||
accState, err := neoR.GetAccountState(acc0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(1000), &accState.Balance)
|
||||
require.Equal(t, uint32(4), accState.BalanceHeight)
|
||||
|
||||
cands, err := neoR.GetCandidates()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(cands)) // No registrations.
|
||||
|
||||
cands, err = neoR.GetAllCandidatesExpanded(100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(cands)) // No registrations.
|
||||
|
||||
iter, err := neoR.GetAllCandidates()
|
||||
require.NoError(t, err)
|
||||
cands, err = iter.Next(10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(cands)) // No registrations.
|
||||
require.NoError(t, iter.Terminate())
|
||||
|
||||
act, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: testchain.CommitteeScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: &wallet.Account{
|
||||
Address: testchain.CommitteeAddress(),
|
||||
Contract: &wallet.Contract{
|
||||
Script: testchain.CommitteeVerificationScript(),
|
||||
},
|
||||
},
|
||||
}})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
neoC := neo.New(act)
|
||||
|
||||
txgpb, err := neoC.SetGasPerBlockUnsigned(10 * 1_0000_0000)
|
||||
require.NoError(t, err)
|
||||
txregp, err := neoC.SetRegisterPriceUnsigned(1_0000)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tx := range []*transaction.Transaction{txgpb, txregp} {
|
||||
tx.Scripts[0].InvocationScript = testchain.SignCommittee(tx)
|
||||
}
|
||||
|
||||
bl := testchain.NewBlock(t, chain, 1, 0, txgpb, txregp)
|
||||
_, err = c.SubmitBlock(*bl)
|
||||
require.NoError(t, err)
|
||||
|
||||
gpb, err = neoR.GetGasPerBlock()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10_0000_0000), gpb)
|
||||
|
||||
regP, err = neoR.GetRegisterPrice()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10000), regP)
|
||||
|
||||
act0, err := actor.NewSimple(c, wallet.NewAccountFromPrivateKey(testchain.PrivateKey(0)))
|
||||
require.NoError(t, err)
|
||||
neo0 := neo.New(act0)
|
||||
|
||||
txreg, err := neo0.RegisterCandidateTransaction(testchain.PrivateKey(0).PublicKey())
|
||||
require.NoError(t, err)
|
||||
bl = testchain.NewBlock(t, chain, 1, 0, txreg)
|
||||
_, err = c.SubmitBlock(*bl)
|
||||
require.NoError(t, err)
|
||||
|
||||
txvote, err := neo0.VoteTransaction(acc0, testchain.PrivateKey(0).PublicKey())
|
||||
require.NoError(t, err)
|
||||
bl = testchain.NewBlock(t, chain, 1, 0, txvote)
|
||||
_, err = c.SubmitBlock(*bl)
|
||||
require.NoError(t, err)
|
||||
|
||||
txunreg, err := neo0.UnregisterCandidateTransaction(testchain.PrivateKey(0).PublicKey())
|
||||
require.NoError(t, err)
|
||||
bl = testchain.NewBlock(t, chain, 1, 0, txunreg)
|
||||
_, err = c.SubmitBlock(*bl)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
|
|
Loading…
Reference in a new issue