Merge pull request #1378 from nspcc-dev/tests/cli
Implement tests for CLI
This commit is contained in:
commit
1608fbff87
32 changed files with 1169 additions and 147 deletions
65
cli/candidate_test.go
Normal file
65
cli/candidate_test.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Register standby validator and vote for it.
|
||||
// We don't create a new account here, because chain will
|
||||
// stop working after validator will change.
|
||||
func TestRegisterCandidate(t *testing.T) {
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "multitransfer",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--from", validatorAddr,
|
||||
"neo:"+validatorPriv.Address()+":10",
|
||||
"gas:"+validatorPriv.Address()+":100")
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "register",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--address", validatorPriv.Address())
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
vs, err := e.Chain.GetEnrollments()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(vs))
|
||||
require.Equal(t, validatorPriv.PublicKey(), vs[0].Key)
|
||||
require.Equal(t, big.NewInt(0), vs[0].Votes)
|
||||
|
||||
t.Run("Vote", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "vote",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--address", validatorPriv.Address(),
|
||||
"--candidate", hex.EncodeToString(validatorPriv.PublicKey().Bytes()))
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
vs, err = e.Chain.GetEnrollments()
|
||||
require.Equal(t, 1, len(vs))
|
||||
require.Equal(t, validatorPriv.PublicKey(), vs[0].Key)
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(validatorPriv.GetScriptHash())
|
||||
require.Equal(t, b, vs[0].Votes)
|
||||
})
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "unregister",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--address", validatorPriv.Address())
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
vs, err = e.Chain.GetEnrollments()
|
||||
require.Equal(t, 0, len(vs))
|
||||
}
|
208
cli/executor_test.go
Normal file
208
cli/executor_test.go
Normal file
|
@ -0,0 +1,208 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"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/network"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/server"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
const (
|
||||
validatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY"
|
||||
validatorAddr = "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK"
|
||||
|
||||
validatorWallet = "testdata/wallet1_solo.json"
|
||||
)
|
||||
|
||||
var (
|
||||
validatorHash, _ = address.StringToUint160(validatorAddr)
|
||||
validatorPriv, _ = keys.NewPrivateKeyFromWIF(validatorWIF)
|
||||
)
|
||||
|
||||
// executor represents context for a test instance.
|
||||
// It can be safely used in multiple tests, but not in parallel.
|
||||
type executor struct {
|
||||
// CLI is a cli application to test.
|
||||
CLI *cli.App
|
||||
// Chain is a blockchain instance (can be empty).
|
||||
Chain *core.Blockchain
|
||||
// RPC is an RPC server to query (can be empty).
|
||||
RPC *server.Server
|
||||
// NetSrv is a network server (can be empty).
|
||||
NetSrv *network.Server
|
||||
// Out contains command output.
|
||||
Out *bytes.Buffer
|
||||
// Err contains command errors.
|
||||
Err *bytes.Buffer
|
||||
// In contains command input.
|
||||
In *bytes.Buffer
|
||||
}
|
||||
|
||||
func newTestChain(t *testing.T) (*core.Blockchain, *server.Server, *network.Server) {
|
||||
configPath := "../config/protocol.unit_testnet.single.yml"
|
||||
cfg, err := config.LoadFile(configPath)
|
||||
require.NoError(t, err, "could not load config")
|
||||
|
||||
memoryStore := storage.NewMemoryStore()
|
||||
logger := zaptest.NewLogger(t)
|
||||
chain, err := core.NewBlockchain(memoryStore, cfg.ProtocolConfiguration, logger)
|
||||
require.NoError(t, err, "could not create chain")
|
||||
|
||||
go chain.Run()
|
||||
|
||||
serverConfig := network.NewServerConfig(cfg)
|
||||
netSrv, err := network.NewServer(serverConfig, chain, zap.NewNop())
|
||||
require.NoError(t, err)
|
||||
go netSrv.Start(make(chan error, 1))
|
||||
rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, netSrv, logger)
|
||||
errCh := make(chan error, 2)
|
||||
rpcServer.Start(errCh)
|
||||
|
||||
return chain, &rpcServer, netSrv
|
||||
}
|
||||
|
||||
func newExecutor(t *testing.T, needChain bool) *executor {
|
||||
e := &executor{
|
||||
CLI: newApp(),
|
||||
Out: bytes.NewBuffer(nil),
|
||||
Err: bytes.NewBuffer(nil),
|
||||
In: bytes.NewBuffer(nil),
|
||||
}
|
||||
e.CLI.Writer = e.Out
|
||||
e.CLI.ErrWriter = e.Err
|
||||
rw := bufio.NewReadWriter(bufio.NewReader(e.In), bufio.NewWriter(ioutil.Discard))
|
||||
require.Nil(t, input.Terminal) // check that tests clean up properly
|
||||
input.Terminal = terminal.NewTerminal(rw, "")
|
||||
if needChain {
|
||||
e.Chain, e.RPC, e.NetSrv = newTestChain(t)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *executor) Close(t *testing.T) {
|
||||
input.Terminal = nil
|
||||
if e.RPC != nil {
|
||||
require.NoError(t, e.RPC.Shutdown())
|
||||
}
|
||||
if e.NetSrv != nil {
|
||||
e.NetSrv.Shutdown()
|
||||
}
|
||||
if e.Chain != nil {
|
||||
e.Chain.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// GetTransaction returns tx with hash h after it has persisted.
|
||||
// If it is in mempool, we can just wait for the next block, otherwise
|
||||
// it must be already in chain. 1 second is time per block in a unittest chain.
|
||||
func (e *executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Transaction, uint32) {
|
||||
var tx *transaction.Transaction
|
||||
var height uint32
|
||||
require.Eventually(t, func() bool {
|
||||
var err error
|
||||
tx, height, err = e.Chain.GetTransaction(h)
|
||||
return err == nil && height != 0
|
||||
}, time.Second*2, time.Millisecond*100, "too long time waiting for block")
|
||||
return tx, height
|
||||
}
|
||||
|
||||
func (e *executor) checkNextLine(t *testing.T, expected string) {
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
e.checkLine(t, line, expected)
|
||||
}
|
||||
|
||||
func (e *executor) checkLine(t *testing.T, line, expected string) {
|
||||
require.Regexp(t, expected, line)
|
||||
}
|
||||
|
||||
func (e *executor) checkEOF(t *testing.T) {
|
||||
_, err := e.Out.ReadString('\n')
|
||||
require.True(t, errors.Is(err, io.EOF))
|
||||
}
|
||||
|
||||
func setExitFunc() <-chan int {
|
||||
ch := make(chan int, 1)
|
||||
cli.OsExiter = func(code int) {
|
||||
ch <- code
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func checkExit(t *testing.T, ch <-chan int, code int) {
|
||||
select {
|
||||
case c := <-ch:
|
||||
require.Equal(t, code, c)
|
||||
default:
|
||||
if code != 0 {
|
||||
require.Fail(t, "no exit was called")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RunWithError runs command and checks that is exits with error.
|
||||
func (e *executor) RunWithError(t *testing.T, args ...string) {
|
||||
ch := setExitFunc()
|
||||
require.Error(t, e.run(args...))
|
||||
checkExit(t, ch, 1)
|
||||
}
|
||||
|
||||
// Run runs command and checks that there were no errors.
|
||||
func (e *executor) Run(t *testing.T, args ...string) {
|
||||
ch := setExitFunc()
|
||||
require.NoError(t, e.run(args...))
|
||||
checkExit(t, ch, 0)
|
||||
}
|
||||
func (e *executor) run(args ...string) error {
|
||||
e.Out.Reset()
|
||||
e.Err.Reset()
|
||||
return e.CLI.Run(args)
|
||||
}
|
||||
|
||||
func (e *executor) checkTxPersisted(t *testing.T) (*transaction.Transaction, uint32) {
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
h, err := util.Uint256DecodeStringLE(line)
|
||||
require.NoError(t, err, "can't decode tx hash: %s", line)
|
||||
|
||||
tx, height := e.GetTransaction(t, h)
|
||||
aer, err := e.Chain.GetAppExecResult(tx.Hash())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer.VMState)
|
||||
return tx, height
|
||||
}
|
||||
|
||||
func generateKeys(t *testing.T, n int) ([]*keys.PrivateKey, keys.PublicKeys) {
|
||||
privs := make([]*keys.PrivateKey, n)
|
||||
pubs := make(keys.PublicKeys, n)
|
||||
for i := range privs {
|
||||
var err error
|
||||
privs[i], err = keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubs[i] = privs[i].PublicKey()
|
||||
}
|
||||
return privs, pubs
|
||||
}
|
44
cli/input/input.go
Normal file
44
cli/input/input.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// Terminal is a terminal used for input. If `nil`, stdin is used.
|
||||
var Terminal *terminal.Terminal
|
||||
|
||||
// ReadLine reads line from the input without trailing '\n'
|
||||
func ReadLine(w io.Writer, prompt string) (string, error) {
|
||||
if Terminal != nil {
|
||||
_, err := Terminal.Write([]byte(prompt))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
raw, err := Terminal.ReadLine()
|
||||
return strings.TrimRight(raw, "\n"), err
|
||||
}
|
||||
fmt.Fprint(w, prompt)
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
return buf.ReadString('\n')
|
||||
}
|
||||
|
||||
// ReadPassword reads user password with prompt.
|
||||
func ReadPassword(w io.Writer, prompt string) (string, error) {
|
||||
if Terminal != nil {
|
||||
return Terminal.ReadPassword(prompt)
|
||||
}
|
||||
fmt.Fprint(w, prompt)
|
||||
rawPass, err := terminal.ReadPassword(syscall.Stdin)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
return strings.TrimRight(string(rawPass), "\n"), nil
|
||||
}
|
13
cli/main.go
13
cli/main.go
|
@ -13,6 +13,14 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
ctl := newApp()
|
||||
|
||||
if err := ctl.Run(os.Args); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newApp() *cli.App {
|
||||
ctl := cli.NewApp()
|
||||
ctl.Name = "neo-go"
|
||||
ctl.Version = config.Version
|
||||
|
@ -23,8 +31,5 @@ func main() {
|
|||
ctl.Commands = append(ctl.Commands, wallet.NewCommands()...)
|
||||
ctl.Commands = append(ctl.Commands, vm.NewCommands()...)
|
||||
ctl.Commands = append(ctl.Commands, util.NewCommands()...)
|
||||
|
||||
if err := ctl.Run(os.Args); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ctl
|
||||
}
|
||||
|
|
13
cli/main_test.go
Normal file
13
cli/main_test.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCLIVersion(t *testing.T) {
|
||||
e := newExecutor(t, false)
|
||||
defer e.Close(t)
|
||||
e.Run(t, "neo-go", "--version")
|
||||
e.checkNextLine(t, "^neo-go version")
|
||||
e.checkEOF(t)
|
||||
}
|
85
cli/multisig_test.go
Normal file
85
cli/multisig_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"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/smartcontract"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test signing of multisig transactions.
|
||||
// 1. Transfer funds to a created multisig address.
|
||||
// 2. Transfer from multisig to another account.
|
||||
func TestSignMultisigTx(t *testing.T) {
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
|
||||
privs, pubs := generateKeys(t, 3)
|
||||
script, err := smartcontract.CreateMultiSigRedeemScript(2, pubs)
|
||||
require.NoError(t, err)
|
||||
multisigHash := hash.Hash160(script)
|
||||
multisigAddr := address.Uint160ToString(multisigHash)
|
||||
|
||||
// Create 2 wallets participating in multisig.
|
||||
tmpDir := os.TempDir()
|
||||
wallet1Path := path.Join(tmpDir, "multiWallet1.json")
|
||||
defer os.Remove(wallet1Path)
|
||||
wallet2Path := path.Join(tmpDir, "multiWallet2.json")
|
||||
defer os.Remove(wallet2Path)
|
||||
|
||||
addAccount := func(w string, wif string) {
|
||||
e.In.WriteString("acc\rpass\rpass\r")
|
||||
e.Run(t, "neo-go", "wallet", "init", "--wallet", w)
|
||||
e.Run(t, "neo-go", "wallet", "import-multisig",
|
||||
"--wallet", w,
|
||||
"--wif", wif,
|
||||
"--min", "2",
|
||||
hex.EncodeToString(pubs[0].Bytes()),
|
||||
hex.EncodeToString(pubs[1].Bytes()),
|
||||
hex.EncodeToString(pubs[2].Bytes()))
|
||||
}
|
||||
addAccount(wallet1Path, privs[0].WIF())
|
||||
addAccount(wallet2Path, privs[1].WIF())
|
||||
|
||||
// Transfer funds to the multisig.
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "multitransfer",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--from", validatorAddr,
|
||||
"neo:"+multisigAddr+":4",
|
||||
"gas:"+multisigAddr+":1")
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
// Sign and transfer funds to another account.
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
txPath := path.Join(tmpDir, "multisigtx.json")
|
||||
defer os.Remove(txPath)
|
||||
e.In.WriteString("pass\r")
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "transfer",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", wallet1Path, "--from", multisigAddr,
|
||||
"--to", priv.Address(), "--token", "neo", "--amount", "1",
|
||||
"--out", txPath)
|
||||
|
||||
e.In.WriteString("pass\r")
|
||||
e.Run(t, "neo-go", "wallet", "multisig", "sign",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", wallet2Path, "--addr", multisigAddr,
|
||||
"--in", txPath, "--out", txPath)
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(priv.GetScriptHash())
|
||||
require.Equal(t, big.NewInt(1), b)
|
||||
b, _ = e.Chain.GetGoverningTokenBalance(multisigHash)
|
||||
require.Equal(t, big.NewInt(3), b)
|
||||
}
|
171
cli/nep5_test.go
Normal file
171
cli/nep5_test.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNEP5Balance(t *testing.T) {
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
cmd := []string{
|
||||
"neo-go", "wallet", "nep5", "balance",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--addr", validatorAddr,
|
||||
}
|
||||
t.Run("NEO", func(t *testing.T) {
|
||||
b, index := e.Chain.GetGoverningTokenBalance(validatorHash)
|
||||
checkResult := func(t *testing.T) {
|
||||
e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.GoverningTokenHash().StringLE())
|
||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String())
|
||||
e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||
e.checkEOF(t)
|
||||
}
|
||||
t.Run("Alias", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "neo")...)
|
||||
checkResult(t)
|
||||
})
|
||||
t.Run("Hash", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", e.Chain.GoverningTokenHash().StringLE())...)
|
||||
checkResult(t)
|
||||
})
|
||||
})
|
||||
t.Run("GAS", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "gas")...)
|
||||
e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.UtilityTokenHash().StringLE())
|
||||
b := e.Chain.GetUtilityTokenBalance(validatorHash)
|
||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(b.Int64()).String())
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--token", "kek")...)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func TestNEP5Transfer(t *testing.T) {
|
||||
w, err := wallet.NewWalletFromFile("testdata/testwallet.json")
|
||||
require.NoError(t, err)
|
||||
defer w.Close()
|
||||
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
args := []string{
|
||||
"neo-go", "wallet", "nep5", "transfer",
|
||||
"--unittest",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--from", validatorAddr,
|
||||
"--to", w.Accounts[0].Address,
|
||||
"--token", "neo",
|
||||
"--amount", "1",
|
||||
}
|
||||
|
||||
t.Run("InvalidPassword", func(t *testing.T) {
|
||||
e.In.WriteString("onetwothree\r")
|
||||
e.RunWithError(t, args...)
|
||||
e.In.Reset()
|
||||
})
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, args...)
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
sh, err := address.StringToUint160(w.Accounts[0].Address)
|
||||
require.NoError(t, err)
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(sh)
|
||||
require.Equal(t, big.NewInt(1), b)
|
||||
}
|
||||
|
||||
func TestNEP5MultiTransfer(t *testing.T) {
|
||||
privs, _ := generateKeys(t, 3)
|
||||
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
args := []string{
|
||||
"neo-go", "wallet", "nep5", "multitransfer",
|
||||
"--unittest", "--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--from", validatorAddr,
|
||||
"neo:" + privs[0].Address() + ":42",
|
||||
"GAS:" + privs[1].Address() + ":7",
|
||||
client.NeoContractHash.StringLE() + ":" + privs[2].Address() + ":13",
|
||||
}
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, args...)
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(privs[0].GetScriptHash())
|
||||
require.Equal(t, big.NewInt(42), b)
|
||||
b = e.Chain.GetUtilityTokenBalance(privs[1].GetScriptHash())
|
||||
require.Equal(t, big.NewInt(int64(util.Fixed8FromInt64(7))), b)
|
||||
b, _ = e.Chain.GetGoverningTokenBalance(privs[2].GetScriptHash())
|
||||
require.Equal(t, big.NewInt(13), b)
|
||||
}
|
||||
|
||||
func TestNEP5ImportToken(t *testing.T) {
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
|
||||
tmpDir := os.TempDir()
|
||||
walletPath := path.Join(tmpDir, "walletForImport.json")
|
||||
defer os.Remove(walletPath)
|
||||
|
||||
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "import",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", walletPath,
|
||||
"--token", client.GasContractHash.StringLE())
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "import",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", walletPath,
|
||||
"--token", client.NeoContractHash.StringLE())
|
||||
|
||||
t.Run("Info", func(t *testing.T) {
|
||||
checkGASInfo := func(t *testing.T) {
|
||||
e.checkNextLine(t, "^Name:\\s*GAS")
|
||||
e.checkNextLine(t, "^Symbol:\\s*gas")
|
||||
e.checkNextLine(t, "^Hash:\\s*"+client.GasContractHash.StringLE())
|
||||
e.checkNextLine(t, "^Decimals:\\s*8")
|
||||
e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.GasContractHash))
|
||||
}
|
||||
t.Run("WithToken", func(t *testing.T) {
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "info",
|
||||
"--wallet", walletPath, "--token", client.GasContractHash.StringLE())
|
||||
checkGASInfo(t)
|
||||
})
|
||||
t.Run("NoToken", func(t *testing.T) {
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "info",
|
||||
"--wallet", walletPath)
|
||||
checkGASInfo(t)
|
||||
_, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
e.checkNextLine(t, "^Name:\\s*NEO")
|
||||
e.checkNextLine(t, "^Symbol:\\s*neo")
|
||||
e.checkNextLine(t, "^Hash:\\s*"+client.NeoContractHash.StringLE())
|
||||
e.checkNextLine(t, "^Decimals:\\s*0")
|
||||
e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.NeoContractHash))
|
||||
})
|
||||
t.Run("Remove", func(t *testing.T) {
|
||||
e.In.WriteString("y\r")
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "remove",
|
||||
"--wallet", walletPath, "--token", client.NeoContractHash.StringLE())
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "info",
|
||||
"--wallet", walletPath)
|
||||
checkGASInfo(t)
|
||||
_, err := e.Out.ReadString('\n')
|
||||
require.Equal(t, err, io.EOF)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -37,6 +37,7 @@ var RPC = []cli.Flag{
|
|||
cli.BoolFlag{Name: "privnet, p"},
|
||||
cli.BoolFlag{Name: "mainnet, m"},
|
||||
cli.BoolFlag{Name: "testnet, t"},
|
||||
cli.BoolFlag{Name: "unittest", Hidden: true},
|
||||
}
|
||||
|
||||
var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'")
|
||||
|
@ -51,6 +52,9 @@ func GetNetwork(ctx *cli.Context) netmode.Magic {
|
|||
if ctx.Bool("mainnet") {
|
||||
net = netmode.MainNet
|
||||
}
|
||||
if ctx.Bool("unittest") {
|
||||
net = netmode.UnitTestNet
|
||||
}
|
||||
return net
|
||||
}
|
||||
|
||||
|
|
|
@ -355,11 +355,11 @@ func startServer(ctx *cli.Context) error {
|
|||
errChan := make(chan error)
|
||||
|
||||
go serv.Start(errChan)
|
||||
go rpcServer.Start(errChan)
|
||||
rpcServer.Start(errChan)
|
||||
|
||||
fmt.Println(logo())
|
||||
fmt.Println(serv.UserAgent)
|
||||
fmt.Println()
|
||||
fmt.Fprintln(ctx.App.Writer, logo())
|
||||
fmt.Fprintln(ctx.App.Writer, serv.UserAgent)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
|
||||
var shutdownErr error
|
||||
Main:
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -25,7 +25,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
@ -366,7 +365,7 @@ func initSmartContract(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully initialized smart contract [%s]\n", contractName)
|
||||
fmt.Fprintf(ctx.App.Writer, "Successfully initialized smart contract [%s]\n", contractName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -405,7 +404,7 @@ func contractCompile(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if ctx.Bool("verbose") {
|
||||
fmt.Println(hex.EncodeToString(result))
|
||||
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -495,14 +494,14 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
|
||||
}
|
||||
fmt.Printf("Sent invocation transaction %s\n", txHash.StringLE())
|
||||
fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE())
|
||||
} else {
|
||||
b, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
fmt.Fprintln(ctx.App.Writer, string(b))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -599,7 +598,7 @@ func testInvokeScript(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
fmt.Fprintln(ctx.App.Writer, string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -680,9 +679,8 @@ func getAccFromContext(ctx *cli.Context) (*wallet.Account, error) {
|
|||
return nil, cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)), 1)
|
||||
}
|
||||
|
||||
fmt.Printf("Enter account %s password > ", address.Uint160ToString(addr))
|
||||
rawPass, err := terminal.ReadPassword(syscall.Stdin)
|
||||
fmt.Println()
|
||||
rawPass, err := input.ReadPassword(ctx.App.Writer,
|
||||
fmt.Sprintf("Enter account %s password > ", address.Uint160ToString(addr)))
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -751,7 +749,8 @@ func contractDeploy(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
|
||||
}
|
||||
fmt.Printf("Sent deployment transaction %s for contract %s\n", txHash.StringLE(), nefFile.Header.ScriptHash.StringLE())
|
||||
fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", nefFile.Header.ScriptHash.StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, txHash.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
1
cli/testdata/testwallet.json
vendored
Normal file
1
cli/testdata/testwallet.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":"3.0","accounts":[{"address":"NNuJqXDnRqvwgzhSzhH4jnVFWB1DyZ34EM","key":"6PYT6enT6eh4gu4ew3Mx58pFFDQNhuR1qQPuU594Eo5u4sA2ZvE4MqJV12","label":"kek","contract":{"script":"DCECl3UyEIq6T5RRIXS6z4tNdZPTzQ7NvXyx7FwK05d9UyYLQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false}],"scrypt":{"n":16384,"r":8,"p":8},"extra":{"Tokens":null}}
|
5
cli/testdata/verify.go
vendored
Normal file
5
cli/testdata/verify.go
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
package testdata
|
||||
|
||||
func Verify() bool {
|
||||
return true
|
||||
}
|
1
cli/testdata/verify.manifest.json
vendored
Executable file
1
cli/testdata/verify.manifest.json
vendored
Executable file
|
@ -0,0 +1 @@
|
|||
{"abi":{"hash":"0x8dff9f223e4622961f410c015dd37052a59892bb","methods":[{"name":"verify","offset":0,"parameters":[],"returntype":"Boolean"}],"events":[]},"groups":[],"features":{"payable":true,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"safemethods":[],"extra":null}
|
BIN
cli/testdata/verify.nef
vendored
Executable file
BIN
cli/testdata/verify.nef
vendored
Executable file
Binary file not shown.
72
cli/testdata/wallet1_solo.json
vendored
Normal file
72
cli/testdata/wallet1_solo.json
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"version": "3.0",
|
||||
"accounts": [
|
||||
{
|
||||
"address": "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc",
|
||||
"key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux",
|
||||
"label": "",
|
||||
"contract": {
|
||||
"script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQZVEDXg=",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "parameter0",
|
||||
"type": "Signature"
|
||||
}
|
||||
],
|
||||
"deployed": false
|
||||
},
|
||||
"lock": false,
|
||||
"isdefault": false
|
||||
},
|
||||
{
|
||||
"address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY",
|
||||
"key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux",
|
||||
"label": "",
|
||||
"contract": {
|
||||
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "parameter0",
|
||||
"type": "Signature"
|
||||
},
|
||||
{
|
||||
"name": "parameter1",
|
||||
"type": "Signature"
|
||||
},
|
||||
{
|
||||
"name": "parameter2",
|
||||
"type": "Signature"
|
||||
}
|
||||
],
|
||||
"deployed": false
|
||||
},
|
||||
"lock": false,
|
||||
"isdefault": false
|
||||
},
|
||||
{
|
||||
"address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK",
|
||||
"key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux",
|
||||
"label": "",
|
||||
"contract": {
|
||||
"script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "parameter0",
|
||||
"type": "Signature"
|
||||
}
|
||||
],
|
||||
"deployed": false
|
||||
},
|
||||
"lock": false,
|
||||
"isdefault": false
|
||||
}
|
||||
],
|
||||
"scrypt": {
|
||||
"n": 16384,
|
||||
"r": 8,
|
||||
"p": 8
|
||||
},
|
||||
"extra": {
|
||||
"Tokens": null
|
||||
}
|
||||
}
|
|
@ -33,6 +33,6 @@ func handleParse(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
fmt.Print(res)
|
||||
fmt.Fprint(ctx.App.Writer, res)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func signMultisig(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1)
|
||||
}
|
||||
acc, err := getDecryptedAccount(wall, sh)
|
||||
acc, err := getDecryptedAccount(ctx, wall, sh)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func signMultisig(ctx *cli.Context) error {
|
|||
if !ok {
|
||||
return cli.NewExitError("verifiable item is not a transaction", 1)
|
||||
}
|
||||
printTxInfo(tx)
|
||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
||||
|
||||
priv := acc.PrivateKey()
|
||||
sign := priv.Sign(tx.GetSignedPart())
|
||||
|
@ -86,11 +86,11 @@ func signMultisig(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
fmt.Println(res.StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(tx.Hash().StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,3 @@ func writeParameterContext(c *context.ParameterContext, filename string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTxInfo(t *transaction.Transaction) {
|
||||
fmt.Printf("Hash: %s\n", t.Hash().StringLE())
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ func newNEP5Commands() []cli.Command {
|
|||
{
|
||||
Name: "remove",
|
||||
Usage: "remove NEP5 token from the wallet",
|
||||
UsageText: "remove --wallet <path> <hash-or-name>",
|
||||
UsageText: "remove --wallet <path> --token <hash-or-name>",
|
||||
Action: removeNEP5Token,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
|
@ -162,9 +162,9 @@ func getNEP5Balance(ctx *cli.Context) error {
|
|||
var token *wallet.Token
|
||||
name := ctx.String("token")
|
||||
if name != "" {
|
||||
token, err = getMatchingToken(wall, name)
|
||||
token, err = getMatchingToken(ctx, wall, name)
|
||||
if err != nil {
|
||||
token, err = getMatchingTokenRPC(c, addrHash, name)
|
||||
token, err = getMatchingTokenRPC(ctx, c, addrHash, name)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -181,26 +181,26 @@ func getNEP5Balance(ctx *cli.Context) error {
|
|||
if name != "" && !token.Hash.Equals(asset) {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("TokenHash: %s\n", asset.StringLE())
|
||||
fmt.Printf("\tAmount : %s\n", balances.Balances[i].Amount)
|
||||
fmt.Printf("\tUpdated: %d\n", balances.Balances[i].LastUpdated)
|
||||
fmt.Fprintf(ctx.App.Writer, "TokenHash: %s\n", asset.StringLE())
|
||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", balances.Balances[i].Amount)
|
||||
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balances.Balances[i].LastUpdated)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMatchingToken(w *wallet.Wallet, name string) (*wallet.Token, error) {
|
||||
func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) {
|
||||
switch strings.ToLower(name) {
|
||||
case "neo":
|
||||
return neoToken, nil
|
||||
case "gas":
|
||||
return gasToken, nil
|
||||
}
|
||||
return getMatchingTokenAux(func(i int) *wallet.Token {
|
||||
return getMatchingTokenAux(ctx, func(i int) *wallet.Token {
|
||||
return w.Extra.Tokens[i]
|
||||
}, len(w.Extra.Tokens), name)
|
||||
}
|
||||
|
||||
func getMatchingTokenRPC(c *client.Client, addr util.Uint160, name string) (*wallet.Token, error) {
|
||||
func getMatchingTokenRPC(ctx *cli.Context, c *client.Client, addr util.Uint160, name string) (*wallet.Token, error) {
|
||||
bs, err := c.GetNEP5Balances(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -209,18 +209,18 @@ func getMatchingTokenRPC(c *client.Client, addr util.Uint160, name string) (*wal
|
|||
t, _ := c.NEP5TokenInfo(bs.Balances[i].Asset)
|
||||
return t
|
||||
}
|
||||
return getMatchingTokenAux(get, len(bs.Balances), name)
|
||||
return getMatchingTokenAux(ctx, get, len(bs.Balances), name)
|
||||
}
|
||||
|
||||
func getMatchingTokenAux(get func(i int) *wallet.Token, n int, name string) (*wallet.Token, error) {
|
||||
func getMatchingTokenAux(ctx *cli.Context, get func(i int) *wallet.Token, n int, name string) (*wallet.Token, error) {
|
||||
var token *wallet.Token
|
||||
var count int
|
||||
for i := 0; i < n; i++ {
|
||||
t := get(i)
|
||||
if t != nil && (t.Name == name || t.Symbol == name || t.Address() == name || t.Hash.StringLE() == name) {
|
||||
if count == 1 {
|
||||
printTokenInfo(token)
|
||||
printTokenInfo(t)
|
||||
printTokenInfo(ctx, token)
|
||||
printTokenInfo(ctx, t)
|
||||
return nil, errors.New("multiple matching tokens found")
|
||||
}
|
||||
count++
|
||||
|
@ -247,7 +247,7 @@ func importNEP5Token(ctx *cli.Context) error {
|
|||
|
||||
for _, t := range wall.Extra.Tokens {
|
||||
if t.Hash.Equals(tokenHash) {
|
||||
printTokenInfo(t)
|
||||
printTokenInfo(ctx, t)
|
||||
return cli.NewExitError("token already exists", 1)
|
||||
}
|
||||
}
|
||||
|
@ -269,16 +269,17 @@ func importNEP5Token(ctx *cli.Context) error {
|
|||
if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
printTokenInfo(tok)
|
||||
printTokenInfo(ctx, tok)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTokenInfo(tok *wallet.Token) {
|
||||
fmt.Printf("Name:\t%s\n", tok.Name)
|
||||
fmt.Printf("Symbol:\t%s\n", tok.Symbol)
|
||||
fmt.Printf("Hash:\t%s\n", tok.Hash.StringLE())
|
||||
fmt.Printf("Decimals: %d\n", tok.Decimals)
|
||||
fmt.Printf("Address: %s\n", tok.Address())
|
||||
func printTokenInfo(ctx *cli.Context, tok *wallet.Token) {
|
||||
w := ctx.App.Writer
|
||||
fmt.Fprintf(w, "Name:\t%s\n", tok.Name)
|
||||
fmt.Fprintf(w, "Symbol:\t%s\n", tok.Symbol)
|
||||
fmt.Fprintf(w, "Hash:\t%s\n", tok.Hash.StringLE())
|
||||
fmt.Fprintf(w, "Decimals: %d\n", tok.Decimals)
|
||||
fmt.Fprintf(w, "Address: %s\n", tok.Address())
|
||||
}
|
||||
|
||||
func printNEP5Info(ctx *cli.Context) error {
|
||||
|
@ -289,19 +290,19 @@ func printNEP5Info(ctx *cli.Context) error {
|
|||
defer wall.Close()
|
||||
|
||||
if name := ctx.String("token"); name != "" {
|
||||
token, err := getMatchingToken(wall, name)
|
||||
token, err := getMatchingToken(ctx, wall, name)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
printTokenInfo(token)
|
||||
printTokenInfo(ctx, token)
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, t := range wall.Extra.Tokens {
|
||||
if i > 0 {
|
||||
fmt.Println()
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
}
|
||||
printTokenInfo(t)
|
||||
printTokenInfo(ctx, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -313,16 +314,12 @@ func removeNEP5Token(ctx *cli.Context) error {
|
|||
}
|
||||
defer wall.Close()
|
||||
|
||||
name := ctx.Args().First()
|
||||
if name == "" {
|
||||
return cli.NewExitError("token must be specified", 1)
|
||||
}
|
||||
token, err := getMatchingToken(wall, name)
|
||||
token, err := getMatchingToken(ctx, wall, ctx.String("token"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if !ctx.Bool("force") {
|
||||
if ok := askForConsent(); !ok {
|
||||
if ok := askForConsent(ctx.App.Writer); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -343,7 +340,7 @@ func multiTransferNEP5(ctx *cli.Context) error {
|
|||
|
||||
fromFlag := ctx.Generic("from").(*flags.Address)
|
||||
from := fromFlag.Uint160()
|
||||
acc, err := getDecryptedAccount(wall, from)
|
||||
acc, err := getDecryptedAccount(ctx, wall, from)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -369,10 +366,10 @@ func multiTransferNEP5(ctx *cli.Context) error {
|
|||
}
|
||||
token, ok := cache[ss[0]]
|
||||
if !ok {
|
||||
token, err = getMatchingToken(wall, ss[0])
|
||||
token, err = getMatchingToken(ctx, wall, ss[0])
|
||||
if err != nil {
|
||||
fmt.Println("Can't find matching token in the wallet. Querying RPC-node for balances.")
|
||||
token, err = getMatchingTokenRPC(c, from, ctx.String("token"))
|
||||
fmt.Fprintln(ctx.App.ErrWriter, "Can't find matching token in the wallet. Querying RPC-node for balances.")
|
||||
token, err = getMatchingTokenRPC(ctx, c, from, ss[0])
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -406,7 +403,7 @@ func transferNEP5(ctx *cli.Context) error {
|
|||
|
||||
fromFlag := ctx.Generic("from").(*flags.Address)
|
||||
from := fromFlag.Uint160()
|
||||
acc, err := getDecryptedAccount(wall, from)
|
||||
acc, err := getDecryptedAccount(ctx, wall, from)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -421,10 +418,10 @@ func transferNEP5(ctx *cli.Context) error {
|
|||
|
||||
toFlag := ctx.Generic("to").(*flags.Address)
|
||||
to := toFlag.Uint160()
|
||||
token, err := getMatchingToken(wall, ctx.String("token"))
|
||||
token, err := getMatchingToken(ctx, wall, ctx.String("token"))
|
||||
if err != nil {
|
||||
fmt.Println("Can't find matching token in the wallet. Querying RPC-node for balances.")
|
||||
token, err = getMatchingTokenRPC(c, from, ctx.String("token"))
|
||||
fmt.Fprintln(ctx.App.ErrWriter, "Can't find matching token in the wallet. Querying RPC-node for balances.")
|
||||
token, err = getMatchingTokenRPC(ctx, c, from, ctx.String("token"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -470,10 +467,10 @@ func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
fmt.Println(res.StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(tx.Hash().StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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/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/io"
|
||||
|
@ -80,10 +82,11 @@ func handleCandidate(ctx *cli.Context, method string) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
addr := addrFlag.Uint160()
|
||||
acc, err := getDecryptedAccount(wall, addr)
|
||||
acc, err := getDecryptedAccount(ctx, wall, addr)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -100,7 +103,10 @@ func handleCandidate(ctx *cli.Context, method string) error {
|
|||
w := io.NewBufBinWriter()
|
||||
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes())
|
||||
emit.Opcode(w.BinWriter, opcode.ASSERT)
|
||||
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas))
|
||||
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
})
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
} else if err = acc.SignTx(tx); err != nil {
|
||||
|
@ -111,7 +117,7 @@ func handleCandidate(ctx *cli.Context, method string) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
fmt.Println(res.StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -120,10 +126,11 @@ func handleVote(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
addr := addrFlag.Uint160()
|
||||
acc, err := getDecryptedAccount(wall, addr)
|
||||
acc, err := getDecryptedAccount(ctx, wall, addr)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -155,7 +162,10 @@ func handleVote(ctx *cli.Context) error {
|
|||
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg)
|
||||
emit.Opcode(w.BinWriter, opcode.ASSERT)
|
||||
|
||||
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas))
|
||||
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
})
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -168,17 +178,18 @@ func handleVote(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
fmt.Println(res.StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDecryptedAccount(wall *wallet.Wallet, addr util.Uint160) (*wallet.Account, error) {
|
||||
func getDecryptedAccount(ctx *cli.Context, wall *wallet.Wallet, addr util.Uint160) (*wallet.Account, error) {
|
||||
acc := wall.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addr))
|
||||
}
|
||||
|
||||
if pass, err := readPassword("Password > "); err != nil {
|
||||
if pass, err := input.ReadPassword(ctx.App.Writer, "Password > "); err != nil {
|
||||
fmt.Println("ERROR", pass, err)
|
||||
return nil, err
|
||||
} else if err := acc.Decrypt(pass); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"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"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -222,7 +221,7 @@ func claimGas(ctx *cli.Context) error {
|
|||
return cli.NewExitError("address was not provided", 1)
|
||||
}
|
||||
scriptHash := addrFlag.Uint160()
|
||||
acc, err := getDecryptedAccount(wall, scriptHash)
|
||||
acc, err := getDecryptedAccount(ctx, wall, scriptHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -234,18 +233,13 @@ func claimGas(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Temporary.
|
||||
neoHash, err := util.Uint160DecodeStringLE("3b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c")
|
||||
|
||||
hash, err := c.TransferNEP5(acc, scriptHash, client.NeoContractHash, 0, 0)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
hash, err := c.TransferNEP5(acc, scriptHash, neoHash, 0, 0)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
fmt.Println(hash.StringLE())
|
||||
fmt.Fprintln(ctx.App.Writer, hash.StringLE())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -265,7 +259,7 @@ func convertWallet(ctx *cli.Context) error {
|
|||
for _, acc := range wall.Accounts {
|
||||
address.Prefix = address.NEO2Prefix
|
||||
|
||||
pass, err := readPassword(fmt.Sprintf("Enter passphrase for account %s (label '%s') > ", acc.Address, acc.Label))
|
||||
pass, err := input.ReadPassword(ctx.App.Writer, fmt.Sprintf("Enter passphrase for account %s (label '%s') > ", acc.Address, acc.Label))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, -1)
|
||||
} else if err := acc.Decrypt(pass); err != nil {
|
||||
|
@ -347,7 +341,7 @@ loop:
|
|||
|
||||
for _, wif := range wifs {
|
||||
if decrypt {
|
||||
pass, err := readPassword("Enter password > ")
|
||||
pass, err := input.ReadPassword(ctx.App.Writer, "Enter password > ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -360,7 +354,7 @@ loop:
|
|||
wif = pk.WIF()
|
||||
}
|
||||
|
||||
fmt.Println(wif)
|
||||
fmt.Fprintln(ctx.App.Writer, wif)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -389,7 +383,7 @@ func importMultisig(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
acc, err := newAccountFromWIF(ctx.String("wif"))
|
||||
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -413,13 +407,13 @@ func importDeployed(ctx *cli.Context) error {
|
|||
|
||||
defer wall.Close()
|
||||
|
||||
rawHash := strings.TrimPrefix("0x", ctx.String("contract"))
|
||||
rawHash := strings.TrimPrefix(ctx.String("contract"), "0x")
|
||||
h, err := util.Uint160DecodeStringLE(rawHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid contract hash: %w", err), 1)
|
||||
}
|
||||
|
||||
acc, err := newAccountFromWIF(ctx.String("wif"))
|
||||
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -440,7 +434,9 @@ func importDeployed(ctx *cli.Context) error {
|
|||
if md == nil {
|
||||
return cli.NewExitError("contract has no `verify` method", 1)
|
||||
}
|
||||
acc.Address = address.Uint160ToString(cs.ScriptHash())
|
||||
acc.Contract.Script = cs.Script
|
||||
acc.Contract.Parameters = acc.Contract.Parameters[:0]
|
||||
for _, p := range md.Parameters {
|
||||
acc.Contract.Parameters = append(acc.Contract.Parameters, wallet.ContractParam{
|
||||
Name: p.Name,
|
||||
|
@ -463,7 +459,7 @@ func importWallet(ctx *cli.Context) error {
|
|||
}
|
||||
defer wall.Close()
|
||||
|
||||
acc, err := newAccountFromWIF(ctx.String("wif"))
|
||||
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -476,7 +472,9 @@ func importWallet(ctx *cli.Context) error {
|
|||
acc.Contract.Script = ctr
|
||||
}
|
||||
|
||||
acc.Label = ctx.String("name")
|
||||
if acc.Label == "" {
|
||||
acc.Label = ctx.String("name")
|
||||
}
|
||||
if err := addAccountAndSave(wall, acc); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -502,8 +500,8 @@ func removeAccount(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
if !ctx.Bool("force") {
|
||||
fmt.Printf("Account %s will be removed. This action is irreversible.\n", addrArg)
|
||||
if ok := askForConsent(); !ok {
|
||||
fmt.Fprintf(ctx.App.Writer, "Account %s will be removed. This action is irreversible.\n", addrArg)
|
||||
if ok := askForConsent(ctx.App.Writer); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -516,17 +514,15 @@ func removeAccount(ctx *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func askForConsent() bool {
|
||||
fmt.Print("Are you sure? [y/N]: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
func askForConsent(w io.Writer) bool {
|
||||
response, err := input.ReadLine(w, "Are you sure? [y/N]: ")
|
||||
if err == nil {
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
if response == "y" || response == "yes" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
fmt.Println("Cancelled.")
|
||||
fmt.Fprintln(w, "Cancelled.")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -536,7 +532,7 @@ func dumpWallet(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if ctx.Bool("decrypt") {
|
||||
pass, err := readPassword("Enter wallet password > ")
|
||||
pass, err := input.ReadPassword(ctx.App.Writer, "Enter wallet password > ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -548,7 +544,7 @@ func dumpWallet(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
fmtPrintWallet(wall)
|
||||
fmtPrintWallet(ctx.App.Writer, wall)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -571,20 +567,18 @@ func createWallet(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
fmtPrintWallet(wall)
|
||||
fmt.Printf("wallet successfully created, file location is %s\n", wall.Path())
|
||||
fmtPrintWallet(ctx.App.Writer, wall)
|
||||
fmt.Fprintf(ctx.App.Writer, "wallet successfully created, file location is %s\n", wall.Path())
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAccountInfo() (string, string, error) {
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Enter the name of the account > ")
|
||||
rawName, _ := buf.ReadBytes('\n')
|
||||
phrase, err := readPassword("Enter passphrase > ")
|
||||
func readAccountInfo(w io.Writer) (string, string, error) {
|
||||
rawName, _ := input.ReadLine(w, "Enter the name of the account > ")
|
||||
phrase, err := input.ReadPassword(w, "Enter passphrase > ")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
phraseCheck, err := readPassword("Confirm passphrase > ")
|
||||
phraseCheck, err := input.ReadPassword(w, "Confirm passphrase > ")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
@ -598,7 +592,7 @@ func readAccountInfo() (string, string, error) {
|
|||
}
|
||||
|
||||
func createAccount(ctx *cli.Context, wall *wallet.Wallet) error {
|
||||
name, phrase, err := readAccountInfo()
|
||||
name, phrase, err := readAccountInfo(ctx.App.Writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -612,11 +606,11 @@ func openWallet(path string) (*wallet.Wallet, error) {
|
|||
return wallet.NewWalletFromFile(path)
|
||||
}
|
||||
|
||||
func newAccountFromWIF(wif string) (*wallet.Account, error) {
|
||||
func newAccountFromWIF(w io.Writer, wif string) (*wallet.Account, error) {
|
||||
// note: NEP2 strings always have length of 58 even though
|
||||
// base58 strings can have different lengths even if slice lengths are equal
|
||||
if len(wif) == 58 {
|
||||
pass, err := readPassword("Enter password > ")
|
||||
pass, err := input.ReadPassword(w, "Enter password > ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -629,8 +623,8 @@ func newAccountFromWIF(wif string) (*wallet.Account, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println("Provided WIF was unencrypted. Wallet can contain only encrypted keys.")
|
||||
name, pass, err := readAccountInfo()
|
||||
fmt.Fprintln(w, "Provided WIF was unencrypted. Wallet can contain only encrypted keys.")
|
||||
name, pass, err := readAccountInfo(w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -654,19 +648,9 @@ func addAccountAndSave(w *wallet.Wallet, acc *wallet.Account) error {
|
|||
return w.Save()
|
||||
}
|
||||
|
||||
func readPassword(prompt string) (string, error) {
|
||||
fmt.Print(prompt)
|
||||
rawPass, err := terminal.ReadPassword(syscall.Stdin)
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimRight(string(rawPass), "\n"), nil
|
||||
}
|
||||
|
||||
func fmtPrintWallet(wall *wallet.Wallet) {
|
||||
func fmtPrintWallet(w io.Writer, wall *wallet.Wallet) {
|
||||
b, _ := wall.JSON()
|
||||
fmt.Println("")
|
||||
fmt.Println(string(b))
|
||||
fmt.Println("")
|
||||
fmt.Fprintln(w, "")
|
||||
fmt.Fprintln(w, string(b))
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
|
|
268
cli/wallet_test.go
Normal file
268
cli/wallet_test.go
Normal file
|
@ -0,0 +1,268 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"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/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWalletInit(t *testing.T) {
|
||||
tmpDir := os.TempDir()
|
||||
e := newExecutor(t, false)
|
||||
defer e.Close(t)
|
||||
|
||||
walletPath := path.Join(tmpDir, "wallet.json")
|
||||
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||
defer os.Remove(walletPath)
|
||||
|
||||
t.Run("CreateAccount", func(t *testing.T) {
|
||||
e.In.WriteString("testname\r")
|
||||
e.In.WriteString("testpass\r")
|
||||
e.In.WriteString("testpass\r")
|
||||
e.Run(t, "neo-go", "wallet", "create", "--wallet", walletPath)
|
||||
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, w.Accounts, 1)
|
||||
require.Equal(t, w.Accounts[0].Label, "testname")
|
||||
require.NoError(t, w.Accounts[0].Decrypt("testpass"))
|
||||
w.Close()
|
||||
|
||||
t.Run("RemoveAccount", func(t *testing.T) {
|
||||
sh := w.Accounts[0].Contract.ScriptHash()
|
||||
addr := w.Accounts[0].Address
|
||||
e.In.WriteString("y\r")
|
||||
e.Run(t, "neo-go", "wallet", "remove",
|
||||
"--wallet", walletPath, addr)
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, w.GetAccount(sh))
|
||||
w.Close()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Import", func(t *testing.T) {
|
||||
t.Run("WIF", func(t *testing.T) {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
e.In.WriteString("test_account\r")
|
||||
e.In.WriteString("qwerty\r")
|
||||
e.In.WriteString("qwerty\r")
|
||||
e.Run(t, "neo-go", "wallet", "import", "--wallet", walletPath,
|
||||
"--wif", priv.WIF())
|
||||
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
defer w.Close()
|
||||
acc := w.GetAccount(priv.GetScriptHash())
|
||||
require.NotNil(t, acc)
|
||||
require.Equal(t, "test_account", acc.Label)
|
||||
require.NoError(t, acc.Decrypt("qwerty"))
|
||||
|
||||
t.Run("AlreadyExists", func(t *testing.T) {
|
||||
e.In.WriteString("test_account_2\r")
|
||||
e.In.WriteString("qwerty2\r")
|
||||
e.In.WriteString("qwerty2\r")
|
||||
e.RunWithError(t, "neo-go", "wallet", "import",
|
||||
"--wallet", walletPath, "--wif", priv.WIF())
|
||||
})
|
||||
})
|
||||
t.Run("EncryptedWIF", func(t *testing.T) {
|
||||
acc, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, acc.Encrypt("somepass"))
|
||||
|
||||
t.Run("InvalidPassword", func(t *testing.T) {
|
||||
e.In.WriteString("password1\r")
|
||||
e.RunWithError(t, "neo-go", "wallet", "import", "--wallet", walletPath,
|
||||
"--wif", acc.EncryptedWIF)
|
||||
})
|
||||
|
||||
e.In.WriteString("somepass\r")
|
||||
e.Run(t, "neo-go", "wallet", "import", "--wallet", walletPath,
|
||||
"--wif", acc.EncryptedWIF)
|
||||
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
defer w.Close()
|
||||
actual := w.GetAccount(acc.PrivateKey().GetScriptHash())
|
||||
require.NotNil(t, actual)
|
||||
require.NoError(t, actual.Decrypt("somepass"))
|
||||
})
|
||||
t.Run("Multisig", func(t *testing.T) {
|
||||
privs, pubs := generateKeys(t, 4)
|
||||
|
||||
cmd := []string{"neo-go", "wallet", "import-multisig",
|
||||
"--wallet", walletPath,
|
||||
"--wif", privs[0].WIF(),
|
||||
"--min", "2"}
|
||||
t.Run("InvalidPublicKeys", func(t *testing.T) {
|
||||
e.In.WriteString("multiacc\r")
|
||||
e.In.WriteString("multipass\r")
|
||||
e.In.WriteString("multipass\r")
|
||||
defer e.In.Reset()
|
||||
|
||||
e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[1].Bytes()),
|
||||
hex.EncodeToString(pubs[1].Bytes()),
|
||||
hex.EncodeToString(pubs[2].Bytes()),
|
||||
hex.EncodeToString(pubs[3].Bytes()))...)
|
||||
})
|
||||
e.In.WriteString("multiacc\r")
|
||||
e.In.WriteString("multipass\r")
|
||||
e.In.WriteString("multipass\r")
|
||||
e.Run(t, append(cmd, hex.EncodeToString(pubs[0].Bytes()),
|
||||
hex.EncodeToString(pubs[1].Bytes()),
|
||||
hex.EncodeToString(pubs[2].Bytes()),
|
||||
hex.EncodeToString(pubs[3].Bytes()))...)
|
||||
|
||||
script, err := smartcontract.CreateMultiSigRedeemScript(2, pubs)
|
||||
require.NoError(t, err)
|
||||
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
defer w.Close()
|
||||
actual := w.GetAccount(hash.Hash160(script))
|
||||
require.NotNil(t, actual)
|
||||
require.NoError(t, actual.Decrypt("multipass"))
|
||||
require.Equal(t, script, actual.Contract.Script)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalletExport(t *testing.T) {
|
||||
e := newExecutor(t, false)
|
||||
defer e.Close(t)
|
||||
|
||||
t.Run("Encrypted", func(t *testing.T) {
|
||||
e.Run(t, "neo-go", "wallet", "export",
|
||||
"--wallet", validatorWallet, validatorAddr)
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
enc, err := keys.NEP2Encrypt(validatorPriv, "one")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, enc, strings.TrimSpace(line))
|
||||
})
|
||||
t.Run("Decrypted", func(t *testing.T) {
|
||||
t.Run("NoAddress", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "export",
|
||||
"--wallet", validatorWallet, "--decrypt")
|
||||
})
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "export",
|
||||
"--wallet", validatorWallet, "--decrypt", validatorAddr)
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, validatorWIF, strings.TrimSpace(line))
|
||||
})
|
||||
}
|
||||
|
||||
func TestClaimGas(t *testing.T) {
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
|
||||
start := e.Chain.BlockHeight()
|
||||
balanceBefore := e.Chain.GetUtilityTokenBalance(validatorHash)
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "claim",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet,
|
||||
"--address", validatorAddr)
|
||||
tx, end := e.checkTxPersisted(t)
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(validatorHash)
|
||||
cl := e.Chain.CalculateClaimable(b, start, end)
|
||||
require.True(t, cl.Sign() > 0)
|
||||
cl.Sub(cl, big.NewInt(tx.NetworkFee+tx.SystemFee))
|
||||
|
||||
balanceAfter := e.Chain.GetUtilityTokenBalance(validatorHash)
|
||||
require.Equal(t, 0, balanceAfter.Cmp(balanceBefore.Add(balanceBefore, cl)))
|
||||
}
|
||||
|
||||
func TestImportDeployed(t *testing.T) {
|
||||
e := newExecutor(t, true)
|
||||
defer e.Close(t)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "contract", "deploy",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet, "--address", validatorAddr,
|
||||
"--in", "testdata/verify.nef", "--manifest", "testdata/verify.manifest.json")
|
||||
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
|
||||
h, err := util.Uint160DecodeStringLE(line)
|
||||
require.NoError(t, err)
|
||||
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
tmpDir := os.TempDir()
|
||||
walletPath := path.Join(tmpDir, "wallet.json")
|
||||
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||
defer os.Remove(walletPath)
|
||||
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
e.In.WriteString("acc\rpass\rpass\r")
|
||||
e.Run(t, "neo-go", "wallet", "import-deployed",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", walletPath, "--wif", priv.WIF(),
|
||||
"--contract", h.StringLE())
|
||||
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
defer w.Close()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(w.Accounts))
|
||||
contractAddr := w.Accounts[0].Address
|
||||
require.Equal(t, address.Uint160ToString(h), contractAddr)
|
||||
require.True(t, w.Accounts[0].Contract.Deployed)
|
||||
|
||||
t.Run("Sign", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "multitransfer",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet, "--from", validatorAddr,
|
||||
"neo:"+contractAddr+":10",
|
||||
"gas:"+contractAddr+":10")
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
privTo, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
e.In.WriteString("pass\r")
|
||||
e.Run(t, "neo-go", "wallet", "nep5", "transfer",
|
||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", walletPath, "--from", contractAddr,
|
||||
"--to", privTo.Address(), "--token", "neo", "--amount", "1")
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(h)
|
||||
require.Equal(t, big.NewInt(9), b)
|
||||
b, _ = e.Chain.GetGoverningTokenBalance(privTo.GetScriptHash())
|
||||
require.Equal(t, big.NewInt(1), b)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalletDump(t *testing.T) {
|
||||
e := newExecutor(t, false)
|
||||
defer e.Close(t)
|
||||
|
||||
e.Run(t, "neo-go", "wallet", "dump", "--wallet", "testdata/testwallet.json")
|
||||
rawStr := strings.TrimSpace(e.Out.String())
|
||||
w := new(wallet.Wallet)
|
||||
require.NoError(t, json.Unmarshal([]byte(rawStr), w))
|
||||
require.Equal(t, 1, len(w.Accounts))
|
||||
require.Equal(t, "NNuJqXDnRqvwgzhSzhH4jnVFWB1DyZ34EM", w.Accounts[0].Address)
|
||||
}
|
52
config/protocol.unit_testnet.single.yml
Normal file
52
config/protocol.unit_testnet.single.yml
Normal file
|
@ -0,0 +1,52 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 42
|
||||
SecondsPerBlock: 1
|
||||
MemPoolSize: 100
|
||||
StandbyCommittee:
|
||||
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
|
||||
ValidatorsCount: 1
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||
# LogPath: "./log/neogo.log"
|
||||
DBConfiguration:
|
||||
Type: "inmemory" #other options: 'inmemory','redis','boltdb', 'badgerdb'.
|
||||
# DB type options. Uncomment those you need in case you want to switch DB type.
|
||||
# LevelDBOptions:
|
||||
# DataDirectoryPath: "./chains/unit_testnet"
|
||||
# RedisDBOptions:
|
||||
# Addr: "localhost:6379"
|
||||
# Password: ""
|
||||
# DB: 0
|
||||
# BoltDBOptions:
|
||||
# FilePath: "./chains/unit_testnet.bolt"
|
||||
# BadgerDBOptions:
|
||||
# BadgerDir: "./chains/unit_testnet.badger"
|
||||
# Uncomment in order to set up custom address for node.
|
||||
# Address: 127.0.0.1
|
||||
NodePort: 0
|
||||
Relay: true
|
||||
DialTimeout: 3
|
||||
ProtoTickInterval: 2
|
||||
PingInterval: 30
|
||||
PingTimeout: 90
|
||||
MinPeers: 0
|
||||
MaxPeers: 10
|
||||
AttemptConnPeers: 5
|
||||
UnlockWallet:
|
||||
Path: "testdata/wallet1_solo.json"
|
||||
Password: "one"
|
||||
RPC:
|
||||
Address: 127.0.0.1
|
||||
MaxGasInvoke: 10
|
||||
Enabled: true
|
||||
EnableCORSWorkaround: false
|
||||
Port: 0 # let the system choose port dynamically
|
||||
Prometheus:
|
||||
Enabled: false #since it's not useful for unit tests.
|
||||
Port: 2112
|
||||
Pprof:
|
||||
Enabled: false #since it's not useful for unit tests.
|
||||
Port: 2113
|
|
@ -42,6 +42,8 @@ type Service interface {
|
|||
// Start initializes dBFT and starts event loop for consensus service.
|
||||
// It must be called only when sufficient amount of peers are connected.
|
||||
Start()
|
||||
// Shutdown stops dBFT event loop.
|
||||
Shutdown()
|
||||
|
||||
// OnPayload is a callback to notify Service about new received payload.
|
||||
OnPayload(p *Payload)
|
||||
|
@ -73,6 +75,7 @@ type service struct {
|
|||
// started is a flag set with Start method that runs an event handling
|
||||
// goroutine.
|
||||
started *atomic.Bool
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// Config is a configuration for consensus services.
|
||||
|
@ -115,6 +118,7 @@ func NewService(cfg Config) (Service, error) {
|
|||
blockEvents: make(chan *coreb.Block, 1),
|
||||
network: cfg.Chain.GetConfig().Magic,
|
||||
started: atomic.NewBool(false),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
if cfg.Wallet == nil {
|
||||
|
@ -190,9 +194,17 @@ func (s *service) Start() {
|
|||
}
|
||||
}
|
||||
|
||||
// Shutdown implements Service interface.
|
||||
func (s *service) Shutdown() {
|
||||
close(s.quit)
|
||||
}
|
||||
|
||||
func (s *service) eventLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-s.quit:
|
||||
s.dbft.Timer.Stop()
|
||||
return
|
||||
case <-s.dbft.Timer.C():
|
||||
hv := s.dbft.Timer.HV()
|
||||
s.log.Debug("timer fired",
|
||||
|
|
|
@ -1420,7 +1420,9 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
|
|||
|
||||
// GetTestVM returns a VM and a Store setup for a test run of some sort of code.
|
||||
func (bc *Blockchain) GetTestVM(tx *transaction.Transaction) *vm.VM {
|
||||
systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
||||
d := bc.dao.GetWrapped().(*dao.Simple)
|
||||
d.MPT = nil
|
||||
systemInterop := bc.newInteropContext(trigger.Application, d, nil, tx)
|
||||
vm := systemInterop.SpawnVM()
|
||||
vm.SetPriceGetter(getPrice)
|
||||
return vm
|
||||
|
|
|
@ -367,8 +367,10 @@ func (dao *Simple) PutStorageItem(id int32, key []byte, si *state.StorageItem) e
|
|||
return buf.Err
|
||||
}
|
||||
v := buf.Bytes()
|
||||
if err := dao.MPT.Put(stKey[1:], v); err != nil && err != mpt.ErrNotFound {
|
||||
return err
|
||||
if dao.MPT != nil {
|
||||
if err := dao.MPT.Put(stKey[1:], v); err != nil && err != mpt.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return dao.Store.Put(stKey, v)
|
||||
}
|
||||
|
@ -377,8 +379,10 @@ func (dao *Simple) PutStorageItem(id int32, key []byte, si *state.StorageItem) e
|
|||
// given key from the store.
|
||||
func (dao *Simple) DeleteStorageItem(id int32, key []byte) error {
|
||||
stKey := makeStorageItemKey(id, key)
|
||||
if err := dao.MPT.Delete(stKey[1:]); err != nil && err != mpt.ErrNotFound {
|
||||
return err
|
||||
if dao.MPT != nil {
|
||||
if err := dao.MPT.Delete(stKey[1:]); err != nil && err != mpt.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return dao.Store.Delete(stKey)
|
||||
}
|
||||
|
|
|
@ -187,6 +187,9 @@ func (s *Server) Shutdown() {
|
|||
s.log.Info("shutting down server", zap.Int("peers", s.PeerCount()))
|
||||
s.transport.Close()
|
||||
s.discovery.Close()
|
||||
if s.consensusStarted.Load() {
|
||||
s.consensus.Shutdown()
|
||||
}
|
||||
for p := range s.Peers() {
|
||||
p.Disconnect(errServerShutdown)
|
||||
}
|
||||
|
|
|
@ -79,9 +79,11 @@ func (t *TCPTransport) isCloseError(err error) bool {
|
|||
|
||||
// Close implements the Transporter interface.
|
||||
func (t *TCPTransport) Close() {
|
||||
t.lock.Lock()
|
||||
if t.listener != nil {
|
||||
t.listener.Close()
|
||||
}
|
||||
t.lock.Unlock()
|
||||
}
|
||||
|
||||
// Proto implements the Transporter interface.
|
||||
|
|
|
@ -489,7 +489,7 @@ func (c *Client) CalculateValidUntilBlock() (uint32, error) {
|
|||
expiresAt: blockCount + cacheTimeout,
|
||||
}
|
||||
}
|
||||
return blockCount + validatorsCount, nil
|
||||
return blockCount + validatorsCount + 1, nil
|
||||
}
|
||||
|
||||
// AddNetworkFee adds network fee for each witness script and optional extra
|
||||
|
|
|
@ -1327,14 +1327,14 @@ func TestCalculateValidUntilBlock(t *testing.T) {
|
|||
|
||||
validUntilBlock, err := c.CalculateValidUntilBlock()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint32(54), validUntilBlock)
|
||||
assert.Equal(t, uint32(55), validUntilBlock)
|
||||
assert.Equal(t, 1, getBlockCountCalled)
|
||||
assert.Equal(t, 1, getValidatorsCalled)
|
||||
|
||||
// check, whether caching is working
|
||||
validUntilBlock, err = c.CalculateValidUntilBlock()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint32(54), validUntilBlock)
|
||||
assert.Equal(t, uint32(55), validUntilBlock)
|
||||
assert.Equal(t, 2, getBlockCountCalled)
|
||||
assert.Equal(t, 1, getValidatorsCalled)
|
||||
}
|
||||
|
|
|
@ -167,18 +167,32 @@ func (s *Server) Start(errChan chan error) {
|
|||
s.https.Handler = http.HandlerFunc(s.handleHTTPRequest)
|
||||
s.log.Info("starting rpc-server (https)", zap.String("endpoint", s.https.Addr))
|
||||
go func() {
|
||||
err := s.https.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile)
|
||||
ln, err := net.Listen("tcp", s.https.Addr)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
s.https.Addr = ln.Addr().String()
|
||||
err = s.https.ServeTLS(ln, cfg.CertFile, cfg.KeyFile)
|
||||
if err != http.ErrServerClosed {
|
||||
s.log.Error("failed to start TLS RPC server", zap.Error(err))
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
err := s.ListenAndServe()
|
||||
if err != http.ErrServerClosed {
|
||||
s.log.Error("failed to start RPC server", zap.Error(err))
|
||||
ln, err := net.Listen("tcp", s.Addr)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
s.Addr = ln.Addr().String() // set Addr to the actual address
|
||||
go func() {
|
||||
err = s.Serve(ln)
|
||||
if err != http.ErrServerClosed {
|
||||
s.log.Error("failed to start RPC server", zap.Error(err))
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Shutdown overrides the http.Server Shutdown
|
||||
|
|
|
@ -68,7 +68,7 @@ func initClearServerWithInMemoryChain(t *testing.T) (*core.Blockchain, *Server,
|
|||
require.NoError(t, err)
|
||||
rpcServer := New(chain, cfg.ApplicationConfiguration.RPC, server, logger)
|
||||
errCh := make(chan error, 2)
|
||||
go rpcServer.Start(errCh)
|
||||
rpcServer.Start(errCh)
|
||||
|
||||
handler := http.HandlerFunc(rpcServer.handleHTTPRequest)
|
||||
srv := httptest.NewServer(handler)
|
||||
|
|
|
@ -98,6 +98,10 @@ func (a *Account) SignTx(t *transaction.Transaction) error {
|
|||
if a.privateKey == nil {
|
||||
return errors.New("account is not unlocked")
|
||||
}
|
||||
if len(a.Contract.Parameters) == 0 {
|
||||
t.Scripts = append(t.Scripts, transaction.Witness{})
|
||||
return nil
|
||||
}
|
||||
data := t.GetSignedPart()
|
||||
if data == nil {
|
||||
return errors.New("failed to get transaction's signed part")
|
||||
|
|
Loading…
Reference in a new issue