From c2b54596468bf128700dc8e7f478eb77a7cd5861 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 28 May 2021 18:05:19 +0300 Subject: [PATCH] cli: add `wallet candidate getstate` --- cli/candidate_test.go | 26 ++++++++++++++-- cli/wallet/validator.go | 66 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/cli/candidate_test.go b/cli/candidate_test.go index d0ca3e46a..91ba7cb50 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/hex" "math/big" + "strconv" "testing" "github.com/stretchr/testify/require" @@ -48,7 +49,7 @@ func TestRegisterCandidate(t *testing.T) { "--wallet", validatorWallet, "--address", validatorPriv.Address(), "--candidate", hex.EncodeToString(validatorPriv.PublicKey().Bytes())) - e.checkTxPersisted(t) + _, index := e.checkTxPersisted(t) vs, err = e.Chain.GetEnrollments() require.Equal(t, 1, len(vs)) @@ -56,18 +57,36 @@ func TestRegisterCandidate(t *testing.T) { b, _ := e.Chain.GetGoverningTokenBalance(validatorPriv.GetScriptHash()) require.Equal(t, b, vs[0].Votes) + // check state + e.Run(t, "neo-go", "wallet", "candidate", "getstate", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--address", validatorPriv.Address()) + e.checkNextLine(t, "^\\s*Voted:\\s+"+validatorPriv.Address()) + e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$") + e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) + e.checkEOF(t) + // unvote e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "vote", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorPriv.Address()) - e.checkTxPersisted(t) + _, index = e.checkTxPersisted(t) vs, err = e.Chain.GetEnrollments() require.Equal(t, 1, len(vs)) require.Equal(t, validatorPriv.PublicKey(), vs[0].Key) require.Equal(t, big.NewInt(0), vs[0].Votes) + + // check state + e.Run(t, "neo-go", "wallet", "candidate", "getstate", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--address", validatorPriv.Address()) + e.checkNextLine(t, "^\\s*Voted:\\s+"+"null") // no vote. + e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$") + e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) + e.checkEOF(t) }) // missing address @@ -84,4 +103,7 @@ func TestRegisterCandidate(t *testing.T) { vs, err = e.Chain.GetEnrollments() require.Equal(t, 0, len(vs)) + + // getstate: missing address + e.RunWithError(t, "neo-go", "wallet", "candidate", "getstate") } diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 1e2475400..c94a9c648 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -7,11 +7,14 @@ import ( "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/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/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpc/client" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -71,6 +74,18 @@ func newValidatorCommands() []cli.Command { }, }, options.RPC...), }, + { + Name: "getstate", + Usage: "print NEO holder account state", + UsageText: "getstate -a ", + Action: getAccountState, + Flags: append([]cli.Flag{ + flags.AddressFlag{ + Name: "address, a", + Usage: "Address to get state of", + }, + }, options.RPC...), + }, } } @@ -204,3 +219,54 @@ func getDecryptedAccount(ctx *cli.Context, wall *wallet.Wallet, addr util.Uint16 } return acc, nil } + +func getAccountState(ctx *cli.Context) error { + addrFlag := ctx.Generic("address").(*flags.Address) + if !addrFlag.IsSet { + return cli.NewExitError("address was not provided", 1) + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + c, exitErr := options.GetRPCClient(gctx, ctx) + if exitErr != nil { + 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) + } + res, err := c.InvokeFunction(neoHash, "getAccountState", []smartcontract.Parameter{ + { + Type: smartcontract.Hash160Type, + Value: addrFlag.Uint160(), + }, + }, nil) + if err != nil { + return cli.NewExitError(err, 1) + } + if res.State != "HALT" { + return cli.NewExitError(fmt.Errorf("invocation failed: %s", res.FaultException), 1) + } + if len(res.Stack) == 0 { + return cli.NewExitError("result stack is empty", 1) + } + st := new(state.NEOBalanceState) + err = st.FromStackItem(res.Stack[0]) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to convert account state from stackitem: %w", err), 1) + } + dec, err := c.NEP17Decimals(neoHash) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1) + } + voted := "null" + if st.VoteTo != nil { + voted = address.Uint160ToString(st.VoteTo.GetScriptHash()) + } + fmt.Fprintf(ctx.App.Writer, "\tVoted: %s\n", voted) + fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", fixedn.ToString(&st.Balance, int(dec))) + fmt.Fprintf(ctx.App.Writer, "\tBlock: %d\n", st.BalanceHeight) + return nil +}