mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
commit
8cd4948e06
14 changed files with 1394 additions and 178 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)
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -44,7 +46,12 @@ func (r *RPCClient) GetVersion() (*result.Version, error) {
|
|||
func (r *RPCClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
||||
return r.hash, r.err
|
||||
}
|
||||
|
||||
func (r *RPCClient) TerminateSession(sessionID uuid.UUID) (bool, error) {
|
||||
return false, nil // Just a stub, unused by actor.
|
||||
}
|
||||
func (r *RPCClient) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
|
||||
return nil, nil // Just a stub, unused by actor.
|
||||
}
|
||||
func testRPCAndAccount(t *testing.T) (*RPCClient, *wallet.Account) {
|
||||
client := &RPCClient{
|
||||
version: &result.Version{
|
||||
|
|
|
@ -1,17 +1,35 @@
|
|||
package invoker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// DefaultIteratorResultItems is the default number of results to
|
||||
// request from the iterator. Typically it's the same as server's
|
||||
// MaxIteratorResultItems, but different servers can have different
|
||||
// settings.
|
||||
const DefaultIteratorResultItems = 100
|
||||
|
||||
// RPCSessions is a set of RPC methods needed to retrieve values from the
|
||||
// session-based iterators.
|
||||
type RPCSessions interface {
|
||||
TerminateSession(sessionID uuid.UUID) (bool, error)
|
||||
TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error)
|
||||
}
|
||||
|
||||
// RPCInvoke is a set of RPC methods needed to execute things at the current
|
||||
// blockchain height.
|
||||
type RPCInvoke interface {
|
||||
RPCSessions
|
||||
|
||||
InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||
|
@ -20,6 +38,8 @@ type RPCInvoke interface {
|
|||
// RPCInvokeHistoric is a set of RPC methods needed to execute things at some
|
||||
// fixed point in blockchain's life.
|
||||
type RPCInvokeHistoric interface {
|
||||
RPCSessions
|
||||
|
||||
InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
|
@ -117,6 +137,14 @@ func (h *historicConverter) InvokeContractVerify(contract util.Uint160, params [
|
|||
panic("uninitialized historicConverter")
|
||||
}
|
||||
|
||||
func (h *historicConverter) TerminateSession(sessionID uuid.UUID) (bool, error) {
|
||||
return h.client.TerminateSession(sessionID)
|
||||
}
|
||||
|
||||
func (h *historicConverter) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
|
||||
return h.client.TraverseIterator(sessionID, iteratorID, maxItemsCount)
|
||||
}
|
||||
|
||||
// Call invokes a method of the contract with the given parameters (and
|
||||
// Invoker-specific list of signers) and returns the result as is.
|
||||
func (v *Invoker) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||
|
@ -157,3 +185,48 @@ func (v *Invoker) Verify(contract util.Uint160, witnesses []transaction.Witness,
|
|||
func (v *Invoker) Run(script []byte) (*result.Invoke, error) {
|
||||
return v.client.InvokeScript(script, v.signers)
|
||||
}
|
||||
|
||||
// TerminateSession closes the given session, returning an error if anything
|
||||
// goes wrong.
|
||||
func (v *Invoker) TerminateSession(sessionID uuid.UUID) error {
|
||||
return termSession(v.client, sessionID)
|
||||
}
|
||||
|
||||
func termSession(rpc RPCSessions, sessionID uuid.UUID) error {
|
||||
r, err := rpc.TerminateSession(sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r {
|
||||
return errors.New("terminatesession returned false")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TraverseIterator allows to retrieve the next batch of items from the given
|
||||
// iterator in the given session (previously returned from Call or Run). It works
|
||||
// both with session-backed iterators and expanded ones (which one you have
|
||||
// depends on the RPC server). It can change the state of the iterator in the
|
||||
// process. If num <= 0 then DefaultIteratorResultItems number of elements is
|
||||
// requested. If result contains no elements, then either Iterator has no
|
||||
// elements or session was expired and terminated by the server.
|
||||
func (v *Invoker) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) {
|
||||
return iterateNext(v.client, sessionID, iterator, num)
|
||||
}
|
||||
|
||||
func iterateNext(rpc RPCSessions, sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) {
|
||||
if num <= 0 {
|
||||
num = DefaultIteratorResultItems
|
||||
}
|
||||
|
||||
if iterator.ID != nil {
|
||||
return rpc.TraverseIterator(sessionID, *iterator.ID, num)
|
||||
}
|
||||
if num > len(iterator.Values) {
|
||||
num = len(iterator.Values)
|
||||
}
|
||||
items := iterator.Values[:num]
|
||||
iterator.Values = iterator.Values[num:]
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
package invoker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type rpcInv struct {
|
||||
resInv *result.Invoke
|
||||
resTrm bool
|
||||
resItm []stackitem.Item
|
||||
err error
|
||||
}
|
||||
|
||||
|
@ -51,10 +56,16 @@ func (r *rpcInv) InvokeScriptAtHeight(height uint32, script []byte, signers []tr
|
|||
func (r *rpcInv) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) TerminateSession(sessionID uuid.UUID) (bool, error) {
|
||||
return r.resTrm, r.err
|
||||
}
|
||||
func (r *rpcInv) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
|
||||
return r.resItm, r.err
|
||||
}
|
||||
|
||||
func TestInvoker(t *testing.T) {
|
||||
resExp := &result.Invoke{State: "HALT"}
|
||||
ri := &rpcInv{resExp, nil}
|
||||
ri := &rpcInv{resExp, true, nil, nil}
|
||||
|
||||
testInv := func(t *testing.T, inv *Invoker) {
|
||||
res, err := inv.Call(util.Uint160{}, "method")
|
||||
|
@ -112,4 +123,50 @@ func TestInvoker(t *testing.T) {
|
|||
require.Panics(t, func() { _, _ = inv.Verify(util.Uint160{}, nil, "param") })
|
||||
require.Panics(t, func() { _, _ = inv.Run([]byte{1}) })
|
||||
})
|
||||
t.Run("terminate session", func(t *testing.T) {
|
||||
for _, inv := range []*Invoker{New(ri, nil), NewHistoricAtBlock(util.Uint256{}, ri, nil)} {
|
||||
ri.err = errors.New("")
|
||||
require.Error(t, inv.TerminateSession(uuid.UUID{}))
|
||||
ri.err = nil
|
||||
ri.resTrm = false
|
||||
require.Error(t, inv.TerminateSession(uuid.UUID{}))
|
||||
ri.resTrm = true
|
||||
require.NoError(t, inv.TerminateSession(uuid.UUID{}))
|
||||
}
|
||||
})
|
||||
t.Run("traverse iterator", func(t *testing.T) {
|
||||
for _, inv := range []*Invoker{New(ri, nil), NewHistoricAtBlock(util.Uint256{}, ri, nil)} {
|
||||
res, err := inv.TraverseIterator(uuid.UUID{}, &result.Iterator{
|
||||
Values: []stackitem.Item{stackitem.Make(42)},
|
||||
}, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res)
|
||||
|
||||
res, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{
|
||||
Values: []stackitem.Item{stackitem.Make(42)},
|
||||
}, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res)
|
||||
|
||||
res, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{
|
||||
Values: []stackitem.Item{stackitem.Make(42)},
|
||||
}, 2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res)
|
||||
|
||||
ri.err = errors.New("")
|
||||
_, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{
|
||||
ID: &uuid.UUID{},
|
||||
}, 2)
|
||||
require.Error(t, err)
|
||||
|
||||
ri.err = nil
|
||||
ri.resItm = []stackitem.Item{stackitem.Make(42)}
|
||||
res, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{
|
||||
ID: &uuid.UUID{},
|
||||
}, 2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -7,9 +7,6 @@ various methods to perform the only RoleManagement state-changing call.
|
|||
package rolemgmt
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -78,22 +75,7 @@ func New(actor Actor) *Contract {
|
|||
// given role at the given height. The list can be empty if no keys are
|
||||
// configured for this role/height.
|
||||
func (c *ContractReader) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.PublicKeys, error) {
|
||||
arr, err := unwrap.Array(c.invoker.Call(Hash, "getDesignatedByRole", int64(role), index))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pks := make(keys.PublicKeys, len(arr))
|
||||
for i, item := range arr {
|
||||
val, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
|
||||
}
|
||||
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pks, nil
|
||||
return unwrap.ArrayOfPublicKeys(c.invoker.Call(Hash, "getDesignatedByRole", int64(role), index))
|
||||
}
|
||||
|
||||
// DesignateAsRole creates and sends a transaction that sets the keys used for
|
||||
|
|
|
@ -11,12 +11,14 @@ contract-specific packages.
|
|||
package unwrap
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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"
|
||||
|
@ -153,6 +155,9 @@ func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, e
|
|||
if !ok {
|
||||
return uuid.UUID{}, result.Iterator{}, errors.New("the item is InteropInterface, but not an Iterator")
|
||||
}
|
||||
if (r.Session == uuid.UUID{}) && iter.ID != nil {
|
||||
return uuid.UUID{}, result.Iterator{}, errors.New("server returned iterator ID, but no session ID")
|
||||
}
|
||||
return r.Session, iter, nil
|
||||
}
|
||||
|
||||
|
@ -190,6 +195,27 @@ func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
// ArrayOfPublicKeys checks the result for correct state (HALT) and then
|
||||
// extracts a slice of public keys from the returned stack item.
|
||||
func ArrayOfPublicKeys(r *result.Invoke, err error) (keys.PublicKeys, error) {
|
||||
arr, err := Array(r, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pks := make(keys.PublicKeys, len(arr))
|
||||
for i, item := range arr {
|
||||
val, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
|
||||
}
|
||||
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("array element #%d in not a key: %w", i, err)
|
||||
}
|
||||
}
|
||||
return pks, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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"
|
||||
|
@ -52,6 +53,9 @@ func TestStdErrors(t *testing.T) {
|
|||
func(r *result.Invoke, err error) (interface{}, error) {
|
||||
return ArrayOfBytes(r, err)
|
||||
},
|
||||
func(r *result.Invoke, err error) (interface{}, error) {
|
||||
return ArrayOfPublicKeys(r, err)
|
||||
},
|
||||
func(r *result.Invoke, err error) (interface{}, error) {
|
||||
return Map(r, err)
|
||||
},
|
||||
|
@ -193,8 +197,11 @@ func TestSessionIterator(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
|
||||
iid := uuid.New()
|
||||
sid := uuid.New()
|
||||
iter := result.Iterator{ID: &iid}
|
||||
_, _, err = SessionIterator(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.NewInterop(iter)}}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
sid := uuid.New()
|
||||
rs, ri, err := SessionIterator(&result.Invoke{Session: sid, State: "HALT", Stack: []stackitem.Item{stackitem.NewInterop(iter)}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sid, rs)
|
||||
|
@ -224,6 +231,25 @@ func TestArrayOfBytes(t *testing.T) {
|
|||
require.Equal(t, []byte("some"), a[0])
|
||||
}
|
||||
|
||||
func TestArrayOfPublicKeys(t *testing.T) {
|
||||
_, err := ArrayOfPublicKeys(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = ArrayOfPublicKeys(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = ArrayOfPublicKeys(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]byte("some"))})}}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pks, err := ArrayOfPublicKeys(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(k.PublicKey().Bytes())})}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pks))
|
||||
require.Equal(t, k.PublicKey(), pks[0])
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
_, err := Map(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
|
||||
require.Error(t, err)
|
||||
|
|
|
@ -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