From efb6545bfe91c842adce5a9881c87ca03397ca77 Mon Sep 17 00:00:00 2001
From: Evgenii Stratonikov <evgeniy@nspcc.ru>
Date: Thu, 7 Apr 2022 15:47:13 +0300
Subject: [PATCH] [#722] neofs-adm: Allow to initialize local dump

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
---
 .../internal/modules/morph/balance.go         |  25 +-
 .../internal/modules/morph/container.go       |   4 +-
 cmd/neofs-adm/internal/modules/morph/dump.go  |   5 +-
 cmd/neofs-adm/internal/modules/morph/epoch.go |   3 +-
 .../internal/modules/morph/initialize.go      |  38 +-
 .../modules/morph/initialize_deploy.go        | 128 +++----
 .../internal/modules/morph/initialize_nns.go  |  31 +-
 .../modules/morph/initialize_register.go      |  26 +-
 .../modules/morph/initialize_roles.go         |   3 +-
 .../internal/modules/morph/local_client.go    | 357 ++++++++++++++++++
 .../internal/modules/morph/n3client.go        |   5 -
 cmd/neofs-adm/internal/modules/morph/root.go  |   6 +
 go.mod                                        |   1 +
 go.sum                                        | Bin 98574 -> 98728 bytes
 14 files changed, 498 insertions(+), 134 deletions(-)
 create mode 100644 cmd/neofs-adm/internal/modules/morph/local_client.go

diff --git a/cmd/neofs-adm/internal/modules/morph/balance.go b/cmd/neofs-adm/internal/modules/morph/balance.go
index dcb06c94d..a113758b9 100644
--- a/cmd/neofs-adm/internal/modules/morph/balance.go
+++ b/cmd/neofs-adm/internal/modules/morph/balance.go
@@ -13,7 +13,6 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
 	"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
 	"github.com/nspcc-dev/neo-go/pkg/io"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/vm"
@@ -57,11 +56,19 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
 	}
 
 	ns, err := getNativeHashes(c)
-	if err != nil || ns[nativenames.Gas].Equals(util.Uint160{}) {
-		return errors.New("can't fetch hash of the GAS contract")
+	if err != nil {
+		return fmt.Errorf("can't fetch the list of native contracts: %w", err)
 	}
 
-	gasHash := ns[nativenames.Gas]
+	gasHash, ok := ns[nativenames.Gas]
+	if !ok {
+		return fmt.Errorf("can't find the %s native contract hash", nativenames.Gas)
+	}
+
+	desigHash, ok := ns[nativenames.Designation]
+	if !ok {
+		return fmt.Errorf("can't find the %s native contract hash", nativenames.Designation)
+	}
 
 	if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy {
 		nnsCs, err = c.GetContractStateByID(1)
@@ -75,7 +82,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
 		}
 	}
 
-	irList, err := fetchIRNodes(c, nmHash)
+	irList, err := fetchIRNodes(c, nmHash, desigHash)
 	if err != nil {
 		return err
 	}
@@ -86,7 +93,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
 	printBalances(cmd, "Inner ring nodes balances:", irList)
 
 	if dumpStorage {
-		res, err := c.InvokeFunction(nmHash, "netmap", []smartcontract.Parameter{}, nil)
+		res, err := invokeFunction(c, nmHash, "netmap", []interface{}{}, nil)
 		if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
 			return errors.New("can't fetch the list of storage nodes")
 		}
@@ -170,7 +177,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
 	return nil
 }
 
-func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) {
+func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, error) {
 	var irList []accBalancePair
 
 	if notaryEnabled {
@@ -179,7 +186,7 @@ func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) {
 			return nil, fmt.Errorf("can't get block height: %w", err)
 		}
 
-		arr, err := c.GetDesignatedByRole(noderoles.NeoFSAlphabet, height)
+		arr, err := getDesignatedByRole(c, desigHash, noderoles.NeoFSAlphabet, height)
 		if err != nil {
 			return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
 		}
@@ -189,7 +196,7 @@ func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) {
 			irList[i].scriptHash = arr[i].GetScriptHash()
 		}
 	} else {
-		res, err := c.InvokeFunction(nmHash, "innerRingList", []smartcontract.Parameter{}, nil)
+		res, err := invokeFunction(c, nmHash, "innerRingList", []interface{}{}, nil)
 		if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
 			return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
 		}
diff --git a/cmd/neofs-adm/internal/modules/morph/container.go b/cmd/neofs-adm/internal/modules/morph/container.go
index 04581d6da..336736731 100644
--- a/cmd/neofs-adm/internal/modules/morph/container.go
+++ b/cmd/neofs-adm/internal/modules/morph/container.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
 	"github.com/nspcc-dev/neo-go/pkg/io"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
@@ -49,8 +48,7 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
 		}
 	}
 
-	res, err := c.InvokeFunction(ch, "list",
-		[]smartcontract.Parameter{{Type: smartcontract.StringType, Value: ""}}, nil)
+	res, err := invokeFunction(c, ch, "list", []interface{}{""}, nil)
 	if err != nil {
 		return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
 	}
diff --git a/cmd/neofs-adm/internal/modules/morph/dump.go b/cmd/neofs-adm/internal/modules/morph/dump.go
index 928376350..2f8a44037 100644
--- a/cmd/neofs-adm/internal/modules/morph/dump.go
+++ b/cmd/neofs-adm/internal/modules/morph/dump.go
@@ -8,9 +8,7 @@ import (
 	"fmt"
 	"text/tabwriter"
 
-	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
 	"github.com/nspcc-dev/neo-go/pkg/io"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/vm"
@@ -160,8 +158,7 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
 		return fmt.Errorf("can't get netmap contract hash: %w", err)
 	}
 
-	res, err := c.InvokeFunction(nmHash, "listConfig",
-		[]smartcontract.Parameter{}, []transaction.Signer{{}})
+	res, err := invokeFunction(c, nmHash, "listConfig", nil, nil)
 	if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
 		return errors.New("can't fetch list of network config keys from the netmap contract")
 	}
diff --git a/cmd/neofs-adm/internal/modules/morph/epoch.go b/cmd/neofs-adm/internal/modules/morph/epoch.go
index f8f3d1941..f29cd4eab 100644
--- a/cmd/neofs-adm/internal/modules/morph/epoch.go
+++ b/cmd/neofs-adm/internal/modules/morph/epoch.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/nspcc-dev/neo-go/pkg/io"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/vm"
 	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
@@ -29,7 +28,7 @@ func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("can't get netmap contract hash: %w", err)
 	}
 
-	res, err := wCtx.Client.InvokeFunction(nmHash, "epoch", []smartcontract.Parameter{}, nil)
+	res, err := invokeFunction(wCtx.Client, nmHash, "epoch", nil, nil)
 	if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
 		return errors.New("can't fetch current epoch from the netmap contract")
 	}
diff --git a/cmd/neofs-adm/internal/modules/morph/initialize.go b/cmd/neofs-adm/internal/modules/morph/initialize.go
index 344fe5148..6fed2df98 100644
--- a/cmd/neofs-adm/internal/modules/morph/initialize.go
+++ b/cmd/neofs-adm/internal/modules/morph/initialize.go
@@ -51,6 +51,7 @@ func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return fmt.Errorf("initialization error: %w", err)
 	}
+	defer initCtx.close()
 
 	// 1. Transfer funds to committee accounts.
 	cmd.Println("Stage 1: transfer GAS to alphabet nodes.")
@@ -98,6 +99,16 @@ func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
+func (c *initializeContext) close() {
+	if local, ok := c.Client.(*localClient); ok {
+		err := local.dump()
+		if err != nil {
+			c.Command.PrintErrf("Can't write dump: %v\n", err)
+			os.Exit(1)
+		}
+	}
+}
+
 func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContext, error) {
 	walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
 	wallets, err := openAlphabetWallets(walletDir)
@@ -110,7 +121,18 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
 		return nil, err
 	}
 
-	c, err := getN3Client(v)
+	var c Client
+	if v.GetString(localDumpFlag) != "" {
+		if cmd.Name() != "init" {
+			return nil, errors.New("dump creation is only supported for `init` command")
+		}
+		if v.GetString(endpointFlag) != "" {
+			return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
+		}
+		c, err = newLocalClient(v, wallets)
+	} else {
+		c, err = getN3Client(v)
+	}
 	if err != nil {
 		return nil, fmt.Errorf("can't create N3 client: %w", err)
 	}
@@ -287,6 +309,12 @@ func (c *clientContext) awaitTx(cmd *cobra.Command) error {
 		return nil
 	}
 
+	if local, ok := c.Client.(*localClient); ok {
+		if err := local.putTransactions(); err != nil {
+			return fmt.Errorf("can't persist transactions: %w", err)
+		}
+	}
+
 	cmd.Println("Waiting for transactions to persist...")
 
 	tick := time.NewTicker(c.PollInterval)
@@ -304,8 +332,8 @@ loop:
 		res, err := c.Client.GetApplicationLog(c.Hashes[i], &at)
 		if err == nil {
 			if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vm.HaltState {
-				retErr = fmt.Errorf("tx persisted in %s state: %s",
-					res.Executions[0].VMState, res.Executions[0].FaultException)
+				retErr = fmt.Errorf("tx %d persisted in %s state: %s",
+					i, res.Executions[0].VMState, res.Executions[0].FaultException)
 			}
 			continue loop
 		}
@@ -315,8 +343,8 @@ loop:
 				res, err := c.Client.GetApplicationLog(c.Hashes[i], &at)
 				if err == nil {
 					if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vm.HaltState {
-						retErr = fmt.Errorf("tx persisted in %s state: %s",
-							res.Executions[0].VMState, res.Executions[0].FaultException)
+						retErr = fmt.Errorf("tx %d persisted in %s state: %s",
+							i, res.Executions[0].VMState, res.Executions[0].FaultException)
 					}
 					continue loop
 				}
diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go
index a41029b63..78e5d8a7a 100644
--- a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go
+++ b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go
@@ -125,7 +125,7 @@ func (c *initializeContext) deployNNS(method string) error {
 		mgmtHash = nnsCs.Hash
 	}
 
-	res, err := c.Client.InvokeFunction(mgmtHash, method, params, []transaction.Signer{signer})
+	res, err := invokeFunction(c.Client, mgmtHash, method, params, []transaction.Signer{signer})
 	if err != nil {
 		return fmt.Errorf("can't deploy NNS contract: %w", err)
 	}
@@ -161,7 +161,7 @@ func (c *initializeContext) updateContracts() error {
 	totalGasCost := int64(0)
 	w := io2.NewBufBinWriter()
 
-	var keysParam []smartcontract.Parameter
+	var keysParam []interface{}
 
 	// Update script size for a single-node committee is close to the maximum allowed size of 65535.
 	// Because of this we want to reuse alphabet contract NEF and manifest for different updates.
@@ -184,10 +184,7 @@ func (c *initializeContext) updateContracts() error {
 			return fmt.Errorf("can't resolve hash for contract update: %w", err)
 		}
 
-		keysParam = append(keysParam, smartcontract.Parameter{
-			Type:  smartcontract.PublicKeyType,
-			Value: acc.PrivateKey().PublicKey().Bytes(),
-		})
+		keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
 
 		params := c.getAlphabetDeployItems(i, len(c.Wallets))
 		emit.Array(w.BinWriter, params...)
@@ -243,7 +240,7 @@ func (c *initializeContext) updateContracts() error {
 			Scopes:  transaction.Global,
 		}
 
-		res, err := c.Client.InvokeFunction(invokeHash, method, params, []transaction.Signer{signer})
+		res, err := invokeFunction(c.Client, invokeHash, method, params, []transaction.Signer{signer})
 		if err != nil {
 			return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
 		}
@@ -287,7 +284,7 @@ func (c *initializeContext) deployContracts() error {
 	mgmtHash := c.nativeHash(nativenames.Management)
 	alphaCs := c.getContract(alphabetContract)
 
-	var keysParam []smartcontract.Parameter
+	var keysParam []interface{}
 
 	// alphabet contracts should be deployed by individual nodes to get different hashes.
 	for i, acc := range c.Accounts {
@@ -298,15 +295,12 @@ func (c *initializeContext) deployContracts() error {
 		}
 
 		invokeHash := mgmtHash
-		keysParam = append(keysParam, smartcontract.Parameter{
-			Type:  smartcontract.PublicKeyType,
-			Value: acc.PrivateKey().PublicKey().Bytes(),
-		})
+		keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
 
 		params := getContractDeployParameters(alphaCs.RawNEF, alphaCs.RawManifest,
-			c.getAlphabetDeployParameters(i, len(c.Wallets)))
+			c.getAlphabetDeployItems(i, len(c.Wallets)))
 
-		res, err := c.Client.InvokeFunction(invokeHash, deployMethodName, params, []transaction.Signer{{
+		res, err := invokeFunction(c.Client, invokeHash, deployMethodName, params, []transaction.Signer{{
 			Account: acc.Contract.ScriptHash(),
 			Scopes:  transaction.CalledByEntry,
 		}})
@@ -344,7 +338,7 @@ func (c *initializeContext) deployContracts() error {
 			Scopes:  transaction.Global,
 		}
 
-		res, err := c.Client.InvokeFunction(invokeHash, deployMethodName, params, []transaction.Signer{signer})
+		res, err := invokeFunction(c.Client, invokeHash, deployMethodName, params, []transaction.Signer{signer})
 		if err != nil {
 			return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
 		}
@@ -497,43 +491,29 @@ func readContractsFromArchive(file io.Reader, names []string) (map[string]*contr
 	return m, nil
 }
 
-func getContractDeployParameters(rawNef, rawManif []byte, deployData []smartcontract.Parameter) []smartcontract.Parameter {
-	return []smartcontract.Parameter{
-		{
-			Type:  smartcontract.ByteArrayType,
-			Value: rawNef,
-		},
-		{
-			Type:  smartcontract.ByteArrayType,
-			Value: rawManif,
-		},
-		{
-			Type:  smartcontract.ArrayType,
-			Value: deployData,
-		},
-	}
+func getContractDeployParameters(rawNef, rawManif []byte, deployData []interface{}) []interface{} {
+	return []interface{}{rawNef, rawManif, deployData}
 }
 
-func (c *initializeContext) getContractDeployData(ctrName string, keysParam []smartcontract.Parameter) []smartcontract.Parameter {
-	items := make([]smartcontract.Parameter, 1, 6)
-	items[0] = newContractParameter(smartcontract.BoolType, false) // notaryDisabled is false
+func (c *initializeContext) getContractDeployData(ctrName string, keysParam []interface{}) []interface{} {
+	items := make([]interface{}, 1, 6)
+	items[0] = false // notaryDisabled is false
 
 	switch ctrName {
 	case neofsContract:
 		items = append(items,
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[processingContract].Hash),
-			newContractParameter(smartcontract.ArrayType, keysParam),
-			newContractParameter(smartcontract.ArrayType, smartcontract.Parameter{}))
+			c.Contracts[processingContract].Hash,
+			keysParam,
+			smartcontract.Parameter{})
 	case processingContract:
-		items = append(items, newContractParameter(smartcontract.Hash160Type, c.Contracts[neofsContract].Hash))
+		items = append(items, c.Contracts[neofsContract].Hash)
 		return items[1:] // no notary info
 	case auditContract:
-		items = append(items,
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash))
+		items = append(items, c.Contracts[netmapContract].Hash)
 	case balanceContract:
 		items = append(items,
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash),
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[containerContract].Hash))
+			c.Contracts[netmapContract].Hash,
+			c.Contracts[containerContract].Hash)
 	case containerContract:
 		// In case if NNS is updated multiple times, we can't calculate
 		// it's actual hash based on local data, thus query chain.
@@ -542,43 +522,33 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []sm
 			panic("NNS is not yet deployed")
 		}
 		items = append(items,
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash),
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[balanceContract].Hash),
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[neofsIDContract].Hash),
-			newContractParameter(smartcontract.Hash160Type, nnsCs.Hash),
-			newContractParameter(smartcontract.StringType, "container"))
+			c.Contracts[netmapContract].Hash,
+			c.Contracts[balanceContract].Hash,
+			c.Contracts[neofsIDContract].Hash,
+			nnsCs.Hash,
+			"container")
 	case neofsIDContract:
 		items = append(items,
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash),
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[containerContract].Hash))
+			c.Contracts[netmapContract].Hash,
+			c.Contracts[containerContract].Hash)
 	case netmapContract:
-		configParam := []smartcontract.Parameter{
-			{Type: smartcontract.StringType, Value: netmapEpochKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(epochDurationInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapMaxObjectSizeKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(maxObjectSizeInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapAuditFeeKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(auditFeeInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapContainerFeeKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(containerFeeInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapContainerAliasFeeKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(containerAliasFeeInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapEigenTrustIterationsKey},
-			{Type: smartcontract.IntegerType, Value: int64(defaultEigenTrustIterations)},
-			{Type: smartcontract.StringType, Value: netmapEigenTrustAlphaKey},
-			{Type: smartcontract.StringType, Value: defaultEigenTrustAlpha},
-			{Type: smartcontract.StringType, Value: netmapBasicIncomeRateKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(incomeRateInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapInnerRingCandidateFeeKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(candidateFeeInitFlag)},
-			{Type: smartcontract.StringType, Value: netmapWithdrawFeeKey},
-			{Type: smartcontract.IntegerType, Value: viper.GetInt64(withdrawFeeInitFlag)},
+		configParam := []interface{}{
+			netmapEpochKey, viper.GetInt64(epochDurationInitFlag),
+			netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag),
+			netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag),
+			netmapContainerFeeKey, viper.GetInt64(containerFeeInitFlag),
+			netmapContainerAliasFeeKey, viper.GetInt64(containerAliasFeeInitFlag),
+			netmapEigenTrustIterationsKey, int64(defaultEigenTrustIterations),
+			netmapEigenTrustAlphaKey, defaultEigenTrustAlpha,
+			netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag),
+			netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag),
+			netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag),
 		}
 		items = append(items,
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[balanceContract].Hash),
-			newContractParameter(smartcontract.Hash160Type, c.Contracts[containerContract].Hash),
-			newContractParameter(smartcontract.ArrayType, keysParam),
-			newContractParameter(smartcontract.ArrayType, configParam))
+			c.Contracts[balanceContract].Hash,
+			c.Contracts[containerContract].Hash,
+			keysParam,
+			configParam)
 	case proxyContract:
 		items = nil
 	case reputationContract:
@@ -589,18 +559,6 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []sm
 	return items
 }
 
-func (c *initializeContext) getAlphabetDeployParameters(i, n int) []smartcontract.Parameter {
-	items := c.getAlphabetDeployItems(i, n)
-	return []smartcontract.Parameter{
-		newContractParameter(smartcontract.BoolType, items[0]),
-		newContractParameter(smartcontract.Hash160Type, items[1]),
-		newContractParameter(smartcontract.Hash160Type, items[2]),
-		newContractParameter(smartcontract.StringType, items[3]),
-		newContractParameter(smartcontract.IntegerType, items[4]),
-		newContractParameter(smartcontract.IntegerType, items[5]),
-	}
-}
-
 func (c *initializeContext) getAlphabetDeployItems(i, n int) []interface{} {
 	items := make([]interface{}, 6)
 	items[0] = false
diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_nns.go b/cmd/neofs-adm/internal/modules/morph/initialize_nns.go
index 39ffb4cc4..d59a03077 100644
--- a/cmd/neofs-adm/internal/modules/morph/initialize_nns.go
+++ b/cmd/neofs-adm/internal/modules/morph/initialize_nns.go
@@ -12,7 +12,6 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
 	"github.com/nspcc-dev/neo-go/pkg/io"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/client"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/vm"
@@ -165,8 +164,8 @@ func (c *initializeContext) nnsRegisterDomain(nnsHash, expectedHash util.Uint160
 }
 
 func (c *initializeContext) nnsRootRegistered(nnsHash util.Uint160) (bool, error) {
-	params := []smartcontract.Parameter{{Type: smartcontract.StringType, Value: "name.neofs"}}
-	res, err := c.Client.InvokeFunction(nnsHash, "isAvailable", params, nil)
+	params := []interface{}{"name.neofs"}
+	res, err := invokeFunction(c.Client, nnsHash, "isAvailable", params, nil)
 	if err != nil {
 		return false, err
 	}
@@ -185,16 +184,7 @@ func nnsResolveHash(c Client, nnsHash util.Uint160, domain string) (util.Uint160
 }
 
 func nnsResolve(c Client, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
-	result, err := c.InvokeFunction(nnsHash, "resolve", []smartcontract.Parameter{
-		{
-			Type:  smartcontract.StringType,
-			Value: domain,
-		},
-		{
-			Type:  smartcontract.IntegerType,
-			Value: int64(nns.TXT),
-		},
-	}, nil)
+	result, err := invokeFunction(c, nnsHash, "resolve", []interface{}{domain, int64(nns.TXT)}, nil)
 	if err != nil {
 		return nil, fmt.Errorf("`resolve`: %w", err)
 	}
@@ -244,11 +234,24 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
 	return util.Uint160DecodeStringLE(string(bs))
 }
 
+var errNNSIsAvailableInvalid = errors.New("`isAvailable`: invalid response")
+
 func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
 	switch ct := c.(type) {
 	case *client.Client:
 		return ct.NNSIsAvailable(nnsHash, name)
 	default:
-		panic("unimplemented")
+		res, err := invokeFunction(c, nnsHash, "isAvailable", []interface{}{name}, nil)
+		if err != nil {
+			return false, err
+		}
+		if len(res.Stack) == 0 {
+			return false, errNNSIsAvailableInvalid
+		}
+		b, err := res.Stack[0].TryBool()
+		if err != nil {
+			return b, errNNSIsAvailableInvalid
+		}
+		return b, nil
 	}
 }
diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_register.go b/cmd/neofs-adm/internal/modules/morph/initialize_register.go
index 29d93756d..2480978ff 100644
--- a/cmd/neofs-adm/internal/modules/morph/initialize_register.go
+++ b/cmd/neofs-adm/internal/modules/morph/initialize_register.go
@@ -1,6 +1,7 @@
 package morph
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/nspcc-dev/neo-go/pkg/core/native"
@@ -8,7 +9,6 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/core/state"
 	"github.com/nspcc-dev/neo-go/pkg/io"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/client"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/vm"
@@ -23,7 +23,7 @@ const initialAlphabetNEOAmount = native.NEOTotalSupply
 func (c *initializeContext) registerCandidates() error {
 	neoHash := c.nativeHash(nativenames.Neo)
 
-	res, err := c.Client.InvokeFunction(neoHash, "getCandidates", []smartcontract.Parameter{}, nil)
+	res, err := invokeFunction(c.Client, neoHash, "getCandidates", nil, nil)
 	if err != nil {
 		return err
 	}
@@ -34,7 +34,7 @@ func (c *initializeContext) registerCandidates() error {
 		}
 	}
 
-	regPrice, err := getCandidateRegisterPrice(c.Client)
+	regPrice, err := c.getCandidateRegisterPrice()
 	if err != nil {
 		return fmt.Errorf("can't fetch registration price: %w", err)
 	}
@@ -84,11 +84,25 @@ func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, err
 	return bal < native.NEOTotalSupply, err
 }
 
-func getCandidateRegisterPrice(c Client) (int64, error) {
-	switch ct := c.(type) {
+var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
+
+func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
+	switch ct := c.Client.(type) {
 	case *client.Client:
 		return ct.GetCandidateRegisterPrice()
 	default:
-		panic("unimplemented")
+		neoHash := c.nativeHash(nativenames.Neo)
+		res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
+		if err != nil {
+			return 0, err
+		}
+		if len(res.Stack) == 0 {
+			return 0, errGetPriceInvalid
+		}
+		bi, err := res.Stack[0].TryInteger()
+		if err != nil || !bi.IsInt64() {
+			return 0, errGetPriceInvalid
+		}
+		return bi.Int64(), nil
 	}
 }
diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_roles.go b/cmd/neofs-adm/internal/modules/morph/initialize_roles.go
index 080bb173e..2103b9f8f 100644
--- a/cmd/neofs-adm/internal/modules/morph/initialize_roles.go
+++ b/cmd/neofs-adm/internal/modules/morph/initialize_roles.go
@@ -42,6 +42,7 @@ func (c *initializeContext) setRolesFinished() (bool, error) {
 		return false, err
 	}
 
-	pubs, err := c.Client.GetDesignatedByRole(noderoles.NeoFSAlphabet, height)
+	h := c.nativeHash(nativenames.Designation)
+	pubs, err := getDesignatedByRole(c.Client, h, noderoles.NeoFSAlphabet, height)
 	return len(pubs) == len(c.Wallets), err
 }
diff --git a/cmd/neofs-adm/internal/modules/morph/local_client.go b/cmd/neofs-adm/internal/modules/morph/local_client.go
new file mode 100644
index 000000000..7b862d838
--- /dev/null
+++ b/cmd/neofs-adm/internal/modules/morph/local_client.go
@@ -0,0 +1,357 @@
+package morph
+
+import (
+	"bytes"
+	"crypto/elliptic"
+	"errors"
+	"fmt"
+	"os"
+	"sort"
+
+	"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"
+	"github.com/nspcc-dev/neo-go/pkg/core/block"
+	"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
+	"github.com/nspcc-dev/neo-go/pkg/core/fee"
+	"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
+	"github.com/nspcc-dev/neo-go/pkg/core/state"
+	"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/io"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/client"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
+	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
+	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
+	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
+	"github.com/nspcc-dev/neo-go/pkg/util"
+	"github.com/nspcc-dev/neo-go/pkg/vm"
+	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
+	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
+	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
+	"github.com/nspcc-dev/neo-go/pkg/wallet"
+	"github.com/spf13/viper"
+	"go.uber.org/zap"
+)
+
+type localClient struct {
+	bc           *core.Blockchain
+	transactions []*transaction.Transaction
+	dumpPath     string
+	accounts     []*wallet.Account
+}
+
+func newLocalClient(v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) {
+	cfg, err := config.LoadFile(v.GetString(protoConfigPath))
+	if err != nil {
+		return nil, err
+	}
+
+	bc, err := core.NewBlockchain(storage.NewMemoryStore(), cfg.ProtocolConfiguration, zap.NewNop())
+	if err != nil {
+		return nil, err
+	}
+	defer func() { go bc.Run() }()
+
+	m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount)
+	accounts := make([]*wallet.Account, len(wallets))
+	for i := range accounts {
+		accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	indexMap := make(map[string]int)
+	for i, pub := range cfg.ProtocolConfiguration.StandbyCommittee {
+		indexMap[pub] = i
+	}
+
+	sort.Slice(accounts, func(i, j int) bool {
+		pi := accounts[i].PrivateKey().PublicKey().Bytes()
+		pj := accounts[j].PrivateKey().PublicKey().Bytes()
+		return indexMap[string(pi)] < indexMap[string(pj)]
+	})
+	sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool {
+		pi := accounts[i].PrivateKey().PublicKey().Bytes()
+		pj := accounts[j].PrivateKey().PublicKey().Bytes()
+		return bytes.Compare(pi, pj) == -1
+	})
+
+	return &localClient{
+		bc:       bc,
+		dumpPath: v.GetString(localDumpFlag),
+		accounts: accounts[:m],
+	}, nil
+}
+
+func (l *localClient) GetBlockCount() (uint32, error) {
+	return l.bc.BlockHeight(), nil
+}
+
+func (l *localClient) GetContractStateByID(id int32) (*state.Contract, error) {
+	h, err := l.bc.GetContractScriptHash(id)
+	if err != nil {
+		return nil, err
+	}
+	return l.GetContractStateByHash(h)
+}
+
+func (l *localClient) GetContractStateByHash(h util.Uint160) (*state.Contract, error) {
+	if cs := l.bc.GetContractState(h); cs != nil {
+		return cs, nil
+	}
+	return nil, storage.ErrKeyNotFound
+}
+
+func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) {
+	return l.bc.GetNatives(), nil
+}
+
+func (l *localClient) GetNetwork() (netmode.Magic, error) {
+	return l.bc.GetConfig().Magic, nil
+}
+
+func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
+	aer, err := l.bc.GetAppExecResults(h, *t)
+	if err != nil {
+		return nil, err
+	}
+
+	a := result.NewApplicationLog(h, aer, *t)
+	return &a, nil
+}
+
+func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee int64, netFee int64, cosigners []client.SignerAccount) (*transaction.Transaction, error) {
+	signers, accounts, err := getSigners(acc, cosigners)
+	if err != nil {
+		return nil, fmt.Errorf("failed to construct tx signers: %w", err)
+	}
+	if sysFee < 0 {
+		res, err := l.InvokeScript(script, signers)
+		if err != nil {
+			return nil, fmt.Errorf("can't add system fee to transaction: %w", err)
+		}
+		if res.State != "HALT" {
+			return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s due to an error: %s", res.State, res.FaultException)
+		}
+		sysFee = res.GasConsumed
+	}
+
+	tx := transaction.New(script, sysFee)
+	tx.Signers = signers
+	tx.ValidUntilBlock = l.bc.BlockHeight() + 2
+
+	err = l.addNetworkFee(tx, netFee, accounts...)
+	if err != nil {
+		return nil, fmt.Errorf("failed to add network fee: %w", err)
+	}
+
+	return tx, nil
+}
+
+// addNetworkFee adds network fee for each witness script and optional extra
+// network fee to transaction. `accs` is an array signer's accounts.
+// Copied from neo-go with minor corrections (no need to support contract signers):
+// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L960
+func (l *localClient) addNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
+	if len(tx.Signers) != len(accs) {
+		return errors.New("number of signers must match number of scripts")
+	}
+
+	size := io.GetVarSize(tx)
+	ef := l.bc.GetBaseExecFee()
+	for i := range tx.Signers {
+		netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script)
+		tx.NetworkFee += netFee
+		size += sizeDelta
+	}
+
+	tx.NetworkFee += int64(size)*l.bc.FeePerByte() + extraFee
+	return nil
+}
+
+// getSigners returns an array of transaction signers and corresponding accounts from
+// given sender and cosigners. If cosigners list already contains sender, the sender
+// will be placed at the start of the list.
+// Copied from neo-go with minor corrections:
+// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L735
+func getSigners(sender *wallet.Account, cosigners []client.SignerAccount) ([]transaction.Signer, []*wallet.Account, error) {
+	var (
+		signers  []transaction.Signer
+		accounts []*wallet.Account
+	)
+
+	from := sender.Contract.ScriptHash()
+	s := transaction.Signer{
+		Account: from,
+		Scopes:  transaction.None,
+	}
+	for _, c := range cosigners {
+		if c.Signer.Account == from {
+			s = c.Signer
+			continue
+		}
+		signers = append(signers, c.Signer)
+		accounts = append(accounts, c.Account)
+	}
+	signers = append([]transaction.Signer{s}, signers...)
+	accounts = append([]*wallet.Account{sender}, accounts...)
+	return signers, accounts, nil
+}
+
+func (l *localClient) NEP17BalanceOf(h util.Uint160, acc util.Uint160) (int64, error) {
+	res, err := invokeFunction(l, h, "balanceOf", []interface{}{acc}, nil)
+	if err != nil {
+		return 0, err
+	}
+	if res.State != vm.HaltState.String() || len(res.Stack) == 0 {
+		return 0, fmt.Errorf("`balance`: invalid response (empty: %t): %s",
+			len(res.Stack) == 0, res.FaultException)
+	}
+	bi, err := res.Stack[0].TryInteger()
+	if err != nil || !bi.IsInt64() {
+		return 0, fmt.Errorf("`balance`: invalid response")
+	}
+	return bi.Int64(), nil
+}
+
+func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
+	lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
+	if err != nil {
+		return nil, err
+	}
+
+	tx := transaction.New(script, 0)
+	tx.Signers = signers
+	tx.ValidUntilBlock = l.bc.BlockHeight() + 2
+
+	ic := l.bc.GetTestVM(trigger.Application, tx, &block.Block{
+		Header: block.Header{
+			Index:     lastBlock.Index + 1,
+			Timestamp: lastBlock.Timestamp + 1,
+		},
+	})
+
+	ic.VM.GasLimit = 100_0000_0000
+	ic.VM.LoadScriptWithFlags(script, callflag.All)
+
+	var errStr string
+	if err := ic.VM.Run(); err != nil {
+		errStr = err.Error()
+	}
+	return &result.Invoke{
+		State:          ic.VM.State().String(),
+		GasConsumed:    ic.VM.GasConsumed(),
+		Script:         script,
+		Stack:          ic.VM.Estack().ToArray(),
+		FaultException: errStr,
+	}, nil
+}
+
+func (l *localClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
+	l.transactions = append(l.transactions, tx)
+	return tx.Hash(), nil
+}
+
+func (l *localClient) putTransactions() error {
+	// 1. Prepare new block.
+	lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
+	if err != nil {
+		panic(err)
+	}
+	defer func() { l.transactions = l.transactions[:0] }()
+
+	b := &block.Block{
+		Header: block.Header{
+			NextConsensus: l.accounts[0].Contract.ScriptHash(),
+			Script: transaction.Witness{
+				VerificationScript: l.accounts[0].Contract.Script,
+			},
+			Timestamp: lastBlock.Timestamp + 1,
+		},
+		Transactions: l.transactions,
+	}
+
+	if l.bc.GetConfig().StateRootInHeader {
+		b.StateRootEnabled = true
+		b.PrevStateRoot = l.bc.GetStateModule().CurrentLocalStateRoot()
+	}
+	b.PrevHash = lastBlock.Hash()
+	b.Index = lastBlock.Index + 1
+	b.RebuildMerkleRoot()
+
+	// 2. Sign prepared block.
+	var invocationScript []byte
+
+	magic := l.bc.GetConfig().Magic
+	for _, acc := range l.accounts {
+		sign := acc.PrivateKey().SignHashable(uint32(magic), b)
+		invocationScript = append(invocationScript, byte(opcode.PUSHDATA1), 64)
+		invocationScript = append(invocationScript, sign...)
+	}
+	b.Script.InvocationScript = invocationScript
+
+	// 3. Persist block.
+	return l.bc.AddBlock(b)
+}
+
+func invokeFunction(c Client, h util.Uint160, method string, parameters []interface{}, signers []transaction.Signer) (*result.Invoke, error) {
+	w := io.NewBufBinWriter()
+	emit.Array(w.BinWriter, parameters...)
+	emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
+	if w.Err != nil {
+		panic(fmt.Sprintf("BUG: invalid parameters for '%s': %v", method, w.Err))
+	}
+	return c.InvokeScript(w.Bytes(), signers)
+}
+
+var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response")
+
+func getDesignatedByRole(c Client, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) {
+	res, err := invokeFunction(c, h, "getDesignatedByRole", []interface{}{int64(role), int64(u)}, nil)
+	if err != nil {
+		return nil, err
+	}
+	if res.State != vm.HaltState.String() || len(res.Stack) == 0 {
+		return nil, errGetDesignatedByRoleResponse
+	}
+	arr, ok := res.Stack[0].Value().([]stackitem.Item)
+	if !ok {
+		return nil, errGetDesignatedByRoleResponse
+	}
+
+	pubs := make(keys.PublicKeys, len(arr))
+	for i := range arr {
+		bs, err := arr[i].TryBytes()
+		if err != nil {
+			return nil, errGetDesignatedByRoleResponse
+		}
+		pubs[i], err = keys.NewPublicKeyFromBytes(bs, elliptic.P256())
+		if err != nil {
+			return nil, errGetDesignatedByRoleResponse
+		}
+	}
+
+	return pubs, nil
+}
+
+func (l *localClient) dump() (err error) {
+	defer l.bc.Close()
+
+	f, err := os.Create(l.dumpPath)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		closeErr := f.Close()
+		if err == nil && closeErr != nil {
+			err = closeErr
+		}
+	}()
+
+	w := io.NewBinWriterFromIO(f)
+	err = chaindump.Dump(l.bc, w, 0, l.bc.BlockHeight())
+	return
+}
diff --git a/cmd/neofs-adm/internal/modules/morph/n3client.go b/cmd/neofs-adm/internal/modules/morph/n3client.go
index c3f5688eb..82f1ef61f 100644
--- a/cmd/neofs-adm/internal/modules/morph/n3client.go
+++ b/cmd/neofs-adm/internal/modules/morph/n3client.go
@@ -7,13 +7,10 @@ import (
 	"time"
 
 	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
-	"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
 	"github.com/nspcc-dev/neo-go/pkg/core/state"
 	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
-	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/client"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
-	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/wallet"
@@ -25,7 +22,6 @@ import (
 // and sending signed transactions to chain.
 type Client interface {
 	GetBlockCount() (uint32, error)
-	GetDesignatedByRole(noderoles.Role, uint32) (keys.PublicKeys, error)
 	GetContractStateByID(int32) (*state.Contract, error)
 	GetContractStateByHash(util.Uint160) (*state.Contract, error)
 	GetNativeContracts() ([]state.NativeContract, error)
@@ -33,7 +29,6 @@ type Client interface {
 	GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
 	CreateTxFromScript([]byte, *wallet.Account, int64, int64, []client.SignerAccount) (*transaction.Transaction, error)
 	NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error)
-	InvokeFunction(util.Uint160, string, []smartcontract.Parameter, []transaction.Signer) (*result.Invoke, error)
 	InvokeScript([]byte, []transaction.Signer) (*result.Invoke, error)
 	SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
 }
diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go
index a166b6eca..34ec928bb 100644
--- a/cmd/neofs-adm/internal/modules/morph/root.go
+++ b/cmd/neofs-adm/internal/modules/morph/root.go
@@ -35,6 +35,8 @@ const (
 	refillGasAmountFlag       = "gas"
 	walletAccountFlag         = "account"
 	notaryDepositTillFlag     = "till"
+	localDumpFlag             = "local-dump"
+	protoConfigPath           = "protocol"
 )
 
 var (
@@ -68,6 +70,8 @@ var (
 			_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
 			_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
 			_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
+			_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
+			_ = viper.BindPFlag(localDumpFlag, cmd.Flags().Lookup(localDumpFlag))
 		},
 		RunE: initializeSideChainCmd,
 	}
@@ -197,6 +201,8 @@ func init() {
 	// Defaults are taken from neo-preodolenie.
 	initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "container registration fee")
 	initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "container alias fee")
+	initCmd.Flags().String(protoConfigPath, "", "path to the consensus node configuration")
+	initCmd.Flags().String(localDumpFlag, "", "path to the blocks dump file")
 
 	RootCmd.AddCommand(generateStorageCmd)
 	generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
diff --git a/go.mod b/go.mod
index d9cc422a6..57f4ca9e9 100644
--- a/go.mod
+++ b/go.mod
@@ -49,6 +49,7 @@ require (
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/holiman/uint256 v1.2.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/ipfs/go-cid v0.0.7 // indirect
 	github.com/magiconair/properties v1.8.5 // indirect
diff --git a/go.sum b/go.sum
index 2ae3cce22bf91370c3d30250ebe280c739297faa..1a3023bdb067fba8c4f0cb2305d0791b1ee8b6b9 100644
GIT binary patch
delta 119
zcmeBcVq4M7wqaw6zCwnfReC{iWO9H{xO<sNvAc<}r%`U0Po_(hv!6?bzH5PdR&cgO
zNp49+W{HRI=Dw8YonTG=UKRO)`bCv_`ECX&$<8UB=E*(**(K%r89_$LrKyFHX<4}@
U#`%Tb#uk$=z7gA8^lr{10PRF9Z2$lO

delta 19
bcmZ3{%+}Y$wqaw+X6Do*otydJ&zuARSD*<|