diff --git a/cmd/neofs-adm/internal/modules/morph/deploy.go b/cmd/neofs-adm/internal/modules/morph/deploy.go new file mode 100644 index 000000000..779481680 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/deploy.go @@ -0,0 +1,121 @@ +package morph + +import ( + "fmt" + "os" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + contractPathFlag = "contract" +) + +var deployCmd = &cobra.Command{ + Use: "deploy", + Short: "Deploy additional smart-contracts", + Long: `Deploy additional smart-contract which are not related to core. +All contracts are deployed by the committee, so access to the alphabet wallets is required.`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + RunE: deployContractCmd, +} + +func init() { + ff := deployCmd.Flags() + + ff.String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + _ = deployCmd.MarkFlagFilename(alphabetWalletsFlag) + + ff.StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + ff.String(contractPathFlag, "", "Path to the contract directory") + _ = deployCmd.MarkFlagFilename(contractPathFlag) +} + +func deployContractCmd(cmd *cobra.Command, _ []string) error { + v := viper.GetViper() + c, err := newInitializeContext(cmd, v) + if err != nil { + return fmt.Errorf("initialization error: %w", err) + } + defer c.close() + + ctrPath, _ := cmd.Flags().GetString(contractPathFlag) + ctrName, err := probeContractName(ctrPath) + if err != nil { + return err + } + + cs, err := readContract(ctrPath, ctrName) + if err != nil { + return err + } + + cs.Hash = state.CreateContractHash( + c.CommitteeAcc.Contract.ScriptHash(), + cs.NEF.Checksum, + cs.Manifest.Name) + + err = c.addManifestGroup(cs.Hash, cs) + if err != nil { + return fmt.Errorf("can't sign manifest group: %v", err) + } + + params := getContractDeployParameters(cs, nil) + callHash := c.nativeHash(nativenames.Management) + + nnsCs, err := c.Client.GetContractStateByID(1) + if err != nil { + return fmt.Errorf("can't fetch NNS contract state: %w", err) + } + + domain := ctrName + ".neofs" + s, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain) + if err != nil { + return err + } + + w := io.NewBufBinWriter() + emit.AppCall(w.BinWriter, callHash, deployMethodName, callflag.All, params...) + emit.Opcodes(w.BinWriter, opcode.DROP) // contract state on stack + w.WriteBytes(s) + if w.Err != nil { + panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err)) + } + + c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE()) + if err := c.sendCommitteeTx(w.Bytes(), -1, false); err != nil { + return err + } + return c.awaitTx() +} + +func probeContractName(ctrPath string) (string, error) { + ds, err := os.ReadDir(ctrPath) + if err != nil { + return "", fmt.Errorf("can't read directory: %w", err) + } + + var ctrName string + for i := range ds { + if strings.HasSuffix(ds[i].Name(), "_contract.nef") { + ctrName = strings.TrimSuffix(ds[i].Name(), "_contract.nef") + break + } + } + + if ctrName == "" { + return "", fmt.Errorf("can't find any NEF files in %s", ctrPath) + } + return ctrName, nil +} diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go index 8033419e3..493505ca3 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go @@ -379,14 +379,9 @@ func (c *initializeContext) readContracts(names []string) error { if c.ContractPath != "" && fi.IsDir() { for _, ctrName := range names { - cs := new(contractState) - cs.RawNEF, err = os.ReadFile(filepath.Join(c.ContractPath, ctrName, ctrName+"_contract.nef")) + cs, err := readContract(filepath.Join(c.ContractPath, ctrName), ctrName) if err != nil { - return fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err) - } - cs.RawManifest, err = os.ReadFile(filepath.Join(c.ContractPath, ctrName, "config.json")) - if err != nil { - return fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err) + return err } c.Contracts[ctrName] = cs } @@ -408,17 +403,16 @@ func (c *initializeContext) readContracts(names []string) error { return err } for _, name := range names { + if err := m[name].parse(); err != nil { + return err + } c.Contracts[name] = m[name] } } for _, ctrName := range names { - cs := c.Contracts[ctrName] - if err := cs.parse(); err != nil { - return err - } - if ctrName != alphabetContract { + cs := c.Contracts[ctrName] cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) } @@ -426,6 +420,24 @@ func (c *initializeContext) readContracts(names []string) error { return nil } +func readContract(ctrPath, ctrName string) (*contractState, error) { + rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef")) + if err != nil { + return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err) + } + rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json")) + if err != nil { + return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err) + } + + cs := &contractState{ + RawNEF: rawNef, + RawManifest: rawManif, + } + + return cs, cs.parse() +} + func (cs *contractState) parse() error { nf, err := nef.FileFromBytes(cs.RawNEF) if err != nil { diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index 64a3f0e19..dd1dec03f 100644 --- a/cmd/neofs-adm/internal/modules/morph/root.go +++ b/cmd/neofs-adm/internal/modules/morph/root.go @@ -235,6 +235,8 @@ func init() { initCmd.Flags().String(protoConfigPath, "", "path to the consensus node configuration") initCmd.Flags().String(localDumpFlag, "", "path to the blocks dump file") + RootCmd.AddCommand(deployCmd) + RootCmd.AddCommand(generateStorageCmd) generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir") generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")