From fdf73597af0571ce503f1053aae80153715c3131 Mon Sep 17 00:00:00 2001
From: Anna Shaleva <shaleva.ann@nspcc.ru>
Date: Mon, 24 Aug 2020 19:00:16 +0300
Subject: [PATCH 1/3] core: return error for unknown storage configuration

---
 pkg/core/storage/store.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go
index a01725453..6c77b425d 100644
--- a/pkg/core/storage/store.go
+++ b/pkg/core/storage/store.go
@@ -3,6 +3,7 @@ package storage
 import (
 	"encoding/binary"
 	"errors"
+	"fmt"
 )
 
 // KeyPrefix constants.
@@ -91,6 +92,8 @@ func NewStore(cfg DBConfiguration) (Store, error) {
 		store, err = NewBoltDBStore(cfg.BoltDBOptions)
 	case "badgerdb":
 		store, err = NewBadgerDBStore(cfg.BadgerDBOptions)
+	default:
+		return nil, fmt.Errorf("unknown storage: %s", cfg.Type)
 	}
 	return store, err
 }

From d6e7d9c281e822b64a3e50387dd8daad2ac10e93 Mon Sep 17 00:00:00 2001
From: Anna Shaleva <shaleva.ann@nspcc.ru>
Date: Tue, 25 Aug 2020 15:41:50 +0300
Subject: [PATCH 2/3] cli: invoke configureAddresses with pointer

Otherwise configuration remains untouched outside of the method scope.
---
 cli/server/server.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cli/server/server.go b/cli/server/server.go
index cc83178f3..3f7170a8b 100644
--- a/cli/server/server.go
+++ b/cli/server/server.go
@@ -147,7 +147,7 @@ func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *m
 	if err != nil {
 		return nil, nil, nil, cli.NewExitError(err, 1)
 	}
-	configureAddresses(cfg.ApplicationConfiguration)
+	configureAddresses(&cfg.ApplicationConfiguration)
 	prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log)
 	pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log)
 
@@ -392,7 +392,7 @@ Main:
 // In case RPC or Prometheus or Pprof Address provided each of them will use it.
 // In case global Address (of the node) provided and RPC/Prometheus/Pprof don't have configured addresses they will
 // use global one. So Node and RPC and Prometheus and Pprof will run on one address.
-func configureAddresses(cfg config.ApplicationConfiguration) {
+func configureAddresses(cfg *config.ApplicationConfiguration) {
 	if cfg.Address != "" {
 		if cfg.RPC.Address == "" {
 			cfg.RPC.Address = cfg.Address

From cab767dafe2d8a3a2675c4ec038f69f4e39593f3 Mon Sep 17 00:00:00 2001
From: Anna Shaleva <shaleva.ann@nspcc.ru>
Date: Fri, 21 Aug 2020 21:36:08 +0300
Subject: [PATCH 3/3] cli: add tests to cli packages

Closes #1336
---
 cli/flags/address_test.go                |  99 ++++++++++
 cli/flags/fixed8_test.go                 |  66 +++++++
 cli/flags/util_test.go                   |  17 ++
 cli/options/options_test.go              |  76 ++++++++
 cli/server/dump_test.go                  |  29 +++
 cli/server/server_test.go                | 231 +++++++++++++++++++++++
 cli/smartcontract/smart_contract_test.go | 123 ++++++++++++
 7 files changed, 641 insertions(+)
 create mode 100644 cli/flags/address_test.go
 create mode 100644 cli/flags/fixed8_test.go
 create mode 100644 cli/flags/util_test.go
 create mode 100644 cli/options/options_test.go
 create mode 100644 cli/server/dump_test.go
 create mode 100644 cli/server/server_test.go
 create mode 100644 cli/smartcontract/smart_contract_test.go

diff --git a/cli/flags/address_test.go b/cli/flags/address_test.go
new file mode 100644
index 000000000..a5019216d
--- /dev/null
+++ b/cli/flags/address_test.go
@@ -0,0 +1,99 @@
+package flags
+
+import (
+	"flag"
+	"io/ioutil"
+	"testing"
+
+	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
+	"github.com/nspcc-dev/neo-go/pkg/util"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAddress_String(t *testing.T) {
+	value := util.Uint160{1, 2, 3}
+	addr := Address{
+		IsSet: true,
+		Value: value,
+	}
+
+	require.Equal(t, address.Uint160ToString(value), addr.String())
+}
+
+func TestAddress_Set(t *testing.T) {
+	value := util.Uint160{1, 2, 3}
+	addr := Address{}
+
+	t.Run("bad address", func(t *testing.T) {
+		require.Error(t, addr.Set("not an address"))
+	})
+
+	t.Run("positive", func(t *testing.T) {
+		require.NoError(t, addr.Set(address.Uint160ToString(value)))
+		require.Equal(t, true, addr.IsSet)
+		require.Equal(t, value, addr.Value)
+	})
+}
+
+func TestAddress_Uint160(t *testing.T) {
+	value := util.Uint160{4, 5, 6}
+	addr := Address{}
+
+	t.Run("not set", func(t *testing.T) {
+		require.Panics(t, func() { addr.Uint160() })
+	})
+
+	t.Run("success", func(t *testing.T) {
+		addr.IsSet = true
+		addr.Value = value
+		require.Equal(t, value, addr.Uint160())
+	})
+}
+
+func TestAddressFlag_IsSet(t *testing.T) {
+	flag := AddressFlag{}
+
+	t.Run("not set", func(t *testing.T) {
+		require.False(t, flag.IsSet())
+	})
+
+	t.Run("set", func(t *testing.T) {
+		flag.Value.IsSet = true
+		require.True(t, flag.IsSet())
+	})
+}
+
+func TestAddressFlag_String(t *testing.T) {
+	flag := AddressFlag{
+		Name:  "myFlag",
+		Usage: "Address to pass",
+		Value: Address{},
+	}
+
+	require.Equal(t, "--myFlag value\tAddress to pass", flag.String())
+}
+
+func TestAddress_getNameHelp(t *testing.T) {
+	require.Equal(t, "-f value", getNameHelp("f"))
+	require.Equal(t, "--flag value", getNameHelp("flag"))
+}
+
+func TestAddressFlag_GetName(t *testing.T) {
+	flag := AddressFlag{
+		Name: "my flag",
+	}
+
+	require.Equal(t, "my flag", flag.GetName())
+}
+
+func TestAddress(t *testing.T) {
+	f := flag.NewFlagSet("", flag.ContinueOnError)
+	f.SetOutput(ioutil.Discard) // don't pollute test output
+	addr := AddressFlag{Name: "addr, a"}
+	addr.Apply(f)
+	require.NoError(t, f.Parse([]string{"--addr", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"}))
+	require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String())
+	require.NoError(t, f.Parse([]string{"-a", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"}))
+	require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String())
+	require.Error(t, f.Parse([]string{"--addr", "kek"}))
+}
diff --git a/cli/flags/fixed8_test.go b/cli/flags/fixed8_test.go
new file mode 100644
index 000000000..62004b1c4
--- /dev/null
+++ b/cli/flags/fixed8_test.go
@@ -0,0 +1,66 @@
+package flags
+
+import (
+	"flag"
+	"io/ioutil"
+	"testing"
+
+	"github.com/nspcc-dev/neo-go/pkg/util"
+	"github.com/stretchr/testify/require"
+)
+
+func TestFixed8_String(t *testing.T) {
+	value := util.Fixed8(123)
+	f := Fixed8{
+		Value: value,
+	}
+
+	require.Equal(t, "0.00000123", f.String())
+}
+
+func TestFixed8_Set(t *testing.T) {
+	value := util.Fixed8(123)
+	f := Fixed8{}
+
+	require.Error(t, f.Set("not-a-fixed8"))
+
+	require.NoError(t, f.Set("0.00000123"))
+	require.Equal(t, value, f.Value)
+}
+
+func TestFixed8_Fixed8(t *testing.T) {
+	f := Fixed8{
+		Value: util.Fixed8(123),
+	}
+
+	require.Equal(t, util.Fixed8(123), f.Fixed8())
+}
+
+func TestFixed8Flag_String(t *testing.T) {
+	flag := Fixed8Flag{
+		Name:  "myFlag",
+		Usage: "Gas amount",
+	}
+
+	require.Equal(t, "--myFlag value\tGas amount", flag.String())
+}
+
+func TestFixed8Flag_GetName(t *testing.T) {
+	flag := Fixed8Flag{
+		Name: "myFlag",
+	}
+
+	require.Equal(t, "myFlag", flag.GetName())
+}
+
+func TestFixed8(t *testing.T) {
+	f := flag.NewFlagSet("", flag.ContinueOnError)
+	f.SetOutput(ioutil.Discard) // don't pollute test output
+	gas := Fixed8Flag{Name: "gas, g"}
+	gas.Apply(f)
+	require.NoError(t, f.Parse([]string{"--gas", "0.123"}))
+	require.Equal(t, "0.123", f.Lookup("g").Value.String())
+	require.NoError(t, f.Parse([]string{"-g", "0.456"}))
+	require.Equal(t, "0.456", f.Lookup("g").Value.String())
+	require.Error(t, f.Parse([]string{"--gas", "kek"}))
+}
diff --git a/cli/flags/util_test.go b/cli/flags/util_test.go
new file mode 100644
index 000000000..fb47b697f
--- /dev/null
+++ b/cli/flags/util_test.go
@@ -0,0 +1,17 @@
+package flags
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestEachName(t *testing.T) {
+	expected := "*one*two*three"
+	actual := ""
+
+	eachName(" one,two ,three", func(s string) {
+		actual += "*" + s
+	})
+	require.Equal(t, expected, actual)
+}
diff --git a/cli/options/options_test.go b/cli/options/options_test.go
new file mode 100644
index 000000000..bd92c6416
--- /dev/null
+++ b/cli/options/options_test.go
@@ -0,0 +1,76 @@
+package options
+
+import (
+	"flag"
+	"testing"
+	"time"
+
+	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
+	"github.com/stretchr/testify/require"
+	"github.com/urfave/cli"
+)
+
+func TestGetNetwork(t *testing.T) {
+	t.Run("privnet", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		require.Equal(t, netmode.PrivNet, GetNetwork(ctx))
+	})
+
+	t.Run("testnet", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.Bool("testnet", true, "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		require.Equal(t, netmode.TestNet, GetNetwork(ctx))
+	})
+
+	t.Run("mainnet", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.Bool("mainnet", true, "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		require.Equal(t, netmode.MainNet, GetNetwork(ctx))
+	})
+}
+
+func TestGetTimeoutContext(t *testing.T) {
+	t.Run("default", func(t *testing.T) {
+		start := time.Now()
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		actualCtx, _ := GetTimeoutContext(ctx)
+		end := time.Now()
+		dl, _ := actualCtx.Deadline()
+		require.True(t, start.Before(dl) && dl.Before(end.Add(DefaultTimeout)))
+	})
+
+	t.Run("set", func(t *testing.T) {
+		start := time.Now()
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.Duration("timeout", time.Duration(20), "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		actualCtx, _ := GetTimeoutContext(ctx)
+		end := time.Now()
+		dl, _ := actualCtx.Deadline()
+		require.True(t, start.Before(dl) && dl.Before(end.Add(time.Nanosecond*20)))
+	})
+}
+
+func TestGetRPCClient(t *testing.T) {
+	t.Run("no endpoint", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		gctx, _ := GetTimeoutContext(ctx)
+		_, ec := GetRPCClient(gctx, ctx)
+		require.Equal(t, 1, ec.ExitCode())
+	})
+
+	t.Run("success", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.String(RPCEndpointFlag, "http://localhost:50333", "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		gctx, _ := GetTimeoutContext(ctx)
+		_, ec := GetRPCClient(gctx, ctx)
+		require.Nil(t, ec)
+	})
+
+}
diff --git a/cli/server/dump_test.go b/cli/server/dump_test.go
new file mode 100644
index 000000000..d118125c7
--- /dev/null
+++ b/cli/server/dump_test.go
@@ -0,0 +1,29 @@
+package server
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetPath(t *testing.T) {
+	testPath, err := ioutil.TempDir("./", "")
+	require.NoError(t, err)
+	defer func() {
+		err := os.RemoveAll(testPath)
+		require.NoError(t, err)
+	}()
+	path, err := getPath(testPath, 123)
+	require.NoError(t, err)
+	require.Equal(t, testPath+"/BlockStorage_100000/dump-block-1000.json", path)
+
+	path, err = getPath(testPath, 1230)
+	require.NoError(t, err)
+	require.Equal(t, testPath+"/BlockStorage_100000/dump-block-2000.json", path)
+
+	path, err = getPath(testPath, 123000)
+	require.NoError(t, err)
+	require.Equal(t, testPath+"/BlockStorage_200000/dump-block-123000.json", path)
+}
diff --git a/cli/server/server_test.go b/cli/server/server_test.go
new file mode 100644
index 000000000..dbbfe44c9
--- /dev/null
+++ b/cli/server/server_test.go
@@ -0,0 +1,231 @@
+package server
+
+import (
+	"flag"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/nspcc-dev/neo-go/pkg/config"
+	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
+	"github.com/nspcc-dev/neo-go/pkg/core/storage"
+	"github.com/nspcc-dev/neo-go/pkg/network/metrics"
+	"github.com/nspcc-dev/neo-go/pkg/rpc"
+	"github.com/stretchr/testify/require"
+	"github.com/urfave/cli"
+	"go.uber.org/zap"
+)
+
+func TestGetConfigFromContext(t *testing.T) {
+	set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+	set.String("config-path", "../../config", "")
+	set.Bool("testnet", true, "")
+	ctx := cli.NewContext(cli.NewApp(), set, nil)
+	cfg, err := getConfigFromContext(ctx)
+	require.NoError(t, err)
+	require.Equal(t, netmode.TestNet, cfg.ProtocolConfiguration.Magic)
+}
+
+func TestHandleLoggingParams(t *testing.T) {
+	testLog, err := ioutil.TempFile("./", "*.log")
+	require.NoError(t, err)
+	defer func() {
+		require.NoError(t, os.Remove(testLog.Name()))
+	}()
+
+	t.Run("default", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		cfg := config.ApplicationConfiguration{
+			LogPath: testLog.Name(),
+		}
+		logger, err := handleLoggingParams(ctx, cfg)
+		require.NoError(t, err)
+		require.True(t, logger.Core().Enabled(zap.InfoLevel))
+		require.False(t, logger.Core().Enabled(zap.DebugLevel))
+	})
+
+	t.Run("debug", func(t *testing.T) {
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.Bool("debug", true, "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		cfg := config.ApplicationConfiguration{
+			LogPath: testLog.Name(),
+		}
+		logger, err := handleLoggingParams(ctx, cfg)
+		require.NoError(t, err)
+		require.True(t, logger.Core().Enabled(zap.InfoLevel))
+		require.True(t, logger.Core().Enabled(zap.DebugLevel))
+	})
+}
+
+func TestInitBCWithMetrics(t *testing.T) {
+	d, err := ioutil.TempDir("./", "")
+	require.NoError(t, err)
+	os.Chdir(d)
+	defer func() {
+		os.Chdir("..")
+		os.RemoveAll(d)
+	}()
+
+	set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+	set.String("config-path", "../../../config", "")
+	set.Bool("testnet", true, "")
+	set.Bool("debug", true, "")
+	ctx := cli.NewContext(cli.NewApp(), set, nil)
+	cfg, err := getConfigFromContext(ctx)
+	require.NoError(t, err)
+	logger, err := handleLoggingParams(ctx, cfg.ApplicationConfiguration)
+	chain, prometheus, pprof, err := initBCWithMetrics(cfg, logger)
+	require.NoError(t, err)
+	defer chain.Close()
+	defer prometheus.ShutDown()
+	defer pprof.ShutDown()
+	require.Equal(t, netmode.TestNet, chain.GetConfig().Magic)
+}
+
+func TestDumpDB(t *testing.T) {
+	t.Run("too low chain", func(t *testing.T) {
+		d, err := ioutil.TempDir("./", "")
+		require.NoError(t, err)
+		os.Chdir(d)
+		defer func() {
+			os.Chdir("..")
+			os.RemoveAll(d)
+		}()
+		testDump := "file.acc"
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.String("config-path", "../../../config", "")
+		set.Bool("privnet", true, "")
+		set.Bool("debug", true, "")
+		set.Int("start", 0, "")
+		set.Int("count", 5, "")
+		set.String("out", testDump, "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		err = dumpDB(ctx)
+		require.Error(t, err)
+	})
+
+	t.Run("positive", func(t *testing.T) {
+		d, err := ioutil.TempDir("./", "")
+		require.NoError(t, err)
+		os.Chdir(d)
+		defer func() {
+			os.Chdir("..")
+			os.RemoveAll(d)
+		}()
+		testDump := "file.acc"
+		set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+		set.String("config-path", "../../../config", "")
+		set.Bool("privnet", true, "")
+		set.Bool("debug", true, "")
+		set.Int("start", 0, "")
+		set.Int("count", 1, "")
+		set.String("out", testDump, "")
+		ctx := cli.NewContext(cli.NewApp(), set, nil)
+		err = dumpDB(ctx)
+		require.NoError(t, err)
+	})
+}
+
+func TestRestoreDB(t *testing.T) {
+	d, err := ioutil.TempDir("./", "")
+	require.NoError(t, err)
+	testDump := "file1.acc"
+	saveDump := "file2.acc"
+	os.Chdir(d)
+	defer func() {
+		os.Chdir("..")
+		os.RemoveAll(d)
+	}()
+
+	//dump first
+	set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+	set.String("config-path", "../../../config", "")
+	set.Bool("privnet", true, "")
+	set.Bool("debug", true, "")
+	set.Int("start", 0, "")
+	set.Int("count", 1, "")
+	set.String("out", testDump, "")
+	ctx := cli.NewContext(cli.NewApp(), set, nil)
+	err = dumpDB(ctx)
+	require.NoError(t, err)
+
+	// and then restore
+	set.String("in", testDump, "")
+	set.Int("skip", 0, "")
+	set.String("dump", saveDump, "")
+	require.NoError(t, restoreDB(ctx))
+}
+
+func TestConfigureAddresses(t *testing.T) {
+	defaultAddress := "http://127.0.0.1:10333"
+	customAddress := "http://127.0.0.1:10334"
+
+	t.Run("default addresses", func(t *testing.T) {
+		cfg := &config.ApplicationConfiguration{
+			Address: defaultAddress,
+		}
+		configureAddresses(cfg)
+		require.Equal(t, defaultAddress, cfg.RPC.Address)
+		require.Equal(t, defaultAddress, cfg.Prometheus.Address)
+		require.Equal(t, defaultAddress, cfg.Pprof.Address)
+	})
+
+	t.Run("custom RPC address", func(t *testing.T) {
+		cfg := &config.ApplicationConfiguration{
+			Address: defaultAddress,
+			RPC: rpc.Config{
+				Address: customAddress,
+			},
+		}
+		configureAddresses(cfg)
+		require.Equal(t, cfg.RPC.Address, customAddress)
+		require.Equal(t, cfg.Prometheus.Address, defaultAddress)
+		require.Equal(t, cfg.Pprof.Address, defaultAddress)
+	})
+
+	t.Run("custom Pprof address", func(t *testing.T) {
+		cfg := &config.ApplicationConfiguration{
+			Address: defaultAddress,
+			Pprof: metrics.Config{
+				Address: customAddress,
+			},
+		}
+		configureAddresses(cfg)
+		require.Equal(t, cfg.RPC.Address, defaultAddress)
+		require.Equal(t, cfg.Prometheus.Address, defaultAddress)
+		require.Equal(t, cfg.Pprof.Address, customAddress)
+	})
+
+	t.Run("custom Prometheus address", func(t *testing.T) {
+		cfg := &config.ApplicationConfiguration{
+			Address: defaultAddress,
+			Prometheus: metrics.Config{
+				Address: customAddress,
+			},
+		}
+		configureAddresses(cfg)
+		require.Equal(t, cfg.RPC.Address, defaultAddress)
+		require.Equal(t, cfg.Prometheus.Address, customAddress)
+		require.Equal(t, cfg.Pprof.Address, defaultAddress)
+	})
+}
+
+func TestInitBlockChain(t *testing.T) {
+	t.Run("bad storage", func(t *testing.T) {
+		_, err := initBlockChain(config.Config{}, nil)
+		require.Error(t, err)
+	})
+
+	t.Run("empty logger", func(t *testing.T) {
+		_, err := initBlockChain(config.Config{
+			ApplicationConfiguration: config.ApplicationConfiguration{
+				DBConfiguration: storage.DBConfiguration{
+					Type: "inmemory",
+				},
+			},
+		}, nil)
+		require.Error(t, err)
+	})
+}
diff --git a/cli/smartcontract/smart_contract_test.go b/cli/smartcontract/smart_contract_test.go
new file mode 100644
index 000000000..7ed50ae0e
--- /dev/null
+++ b/cli/smartcontract/smart_contract_test.go
@@ -0,0 +1,123 @@
+package smartcontract
+
+import (
+	"flag"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
+	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
+	"github.com/nspcc-dev/neo-go/pkg/util"
+	"github.com/stretchr/testify/require"
+	"github.com/urfave/cli"
+)
+
+func TestInitSmartContract(t *testing.T) {
+	d, err := ioutil.TempDir("./", "")
+	require.NoError(t, err)
+	os.Chdir(d)
+	defer func() {
+		os.Chdir("..")
+		os.RemoveAll(d)
+	}()
+	contractName := "testContract"
+
+	set := flag.NewFlagSet("flagSet", flag.ExitOnError)
+	set.String("name", contractName, "")
+	ctx := cli.NewContext(cli.NewApp(), set, nil)
+	require.NoError(t, initSmartContract(ctx))
+	dirInfo, err := os.Stat(contractName)
+	require.NoError(t, err)
+	require.True(t, dirInfo.IsDir())
+	files, err := ioutil.ReadDir(contractName)
+	require.NoError(t, err)
+	require.Equal(t, 2, len(files))
+	require.Equal(t, "main.go", files[0].Name())
+	require.Equal(t, "neo-go.yml", files[1].Name())
+	main, err := ioutil.ReadFile(contractName + "/" + files[0].Name())
+	require.NoError(t, err)
+	require.Equal(t,
+		`package `+contractName+`
+
+import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
+
+var notificationName string
+
+// init initializes notificationName before calling any other smart-contract method
+func init() {
+	notificationName = "Hello world!"
+}
+
+// RuntimeNotify sends runtime notification with "Hello world!" name
+func RuntimeNotify(args []interface{}) {
+    runtime.Notify(notificationName, args)
+}`, string(main))
+
+	manifest, err := ioutil.ReadFile(contractName + "/" + files[1].Name())
+	require.NoError(t, err)
+	require.Equal(t,
+		`hasstorage: false
+ispayable: false
+supportedstandards: []
+events:
+- name: Hello world!
+  parameters:
+  - name: args
+    type: Array
+`, string(manifest))
+}
+
+func TestGetFeatures(t *testing.T) {
+	cfg := ProjectConfig{
+		IsPayable:  true,
+		HasStorage: true,
+	}
+	f := cfg.GetFeatures()
+	require.Equal(t, smartcontract.IsPayable|smartcontract.HasStorage, f)
+}
+
+func TestParseCosigner(t *testing.T) {
+	acc := util.Uint160{1, 3, 5, 7}
+	testCases := map[string]transaction.Signer{
+		acc.StringLE(): {
+			Account: acc,
+			Scopes:  transaction.Global,
+		},
+		"0x" + acc.StringLE(): {
+			Account: acc,
+			Scopes:  transaction.Global,
+		},
+		acc.StringLE() + ":Global": {
+			Account: acc,
+			Scopes:  transaction.Global,
+		},
+		acc.StringLE() + ":CalledByEntry": {
+			Account: acc,
+			Scopes:  transaction.CalledByEntry,
+		},
+		acc.StringLE() + ":FeeOnly": {
+			Account: acc,
+			Scopes:  transaction.FeeOnly,
+		},
+		acc.StringLE() + ":CalledByEntry,CustomContracts": {
+			Account: acc,
+			Scopes:  transaction.CalledByEntry | transaction.CustomContracts,
+		},
+	}
+	for s, expected := range testCases {
+		actual, err := parseCosigner(s)
+		require.NoError(t, err)
+		require.Equal(t, expected, actual)
+	}
+	errorCases := []string{
+		acc.StringLE() + "0",
+		acc.StringLE() + ":Unknown",
+		acc.StringLE() + ":Global,CustomContracts",
+		acc.StringLE() + ":Global,FeeOnly",
+	}
+	for _, s := range errorCases {
+		_, err := parseCosigner(s)
+		require.Error(t, err)
+	}
+}