From 868198a36ecad98654dcbf68e26f4c525a995e55 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 2 Aug 2021 11:10:26 +0300 Subject: [PATCH] cli/smartcontract: add `manifest add-group` command Signed-off-by: Evgeniy Stratonikov --- cli/contract_test.go | 41 ++++++++++ cli/smartcontract/manifest.go | 122 ++++++++++++++++++++++++++++ cli/smartcontract/smart_contract.go | 48 ++++++++++- 3 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 cli/smartcontract/manifest.go diff --git a/cli/contract_test.go b/cli/contract_test.go index 964d782d5..6e865a746 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -227,6 +227,47 @@ func TestContractDeployWithData(t *testing.T) { require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value()) } +func TestContractManifestGroups(t *testing.T) { + e := newExecutor(t, true) + + // For proper nef generation. + config.Version = "0.90.0-test" + + tmpDir, err := ioutil.TempDir("", "neogo.test.deployfail") + require.NoError(t, err) + t.Cleanup(func() { + os.RemoveAll(tmpDir) + }) + + w, err := wallet.NewWalletFromFile(testWalletPath) + require.NoError(t, err) + defer w.Close() + + nefName := path.Join(tmpDir, "deploy.nef") + manifestName := path.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + cmd := []string{"neo-go", "contract", "manifest", "add-group", + "--nef", nefName, "--manifest", manifestName} + + e.In.WriteString("testpass\r") + e.Run(t, append(cmd, "--wallet", testWalletPath, + "--sender", testWalletAccount, "--account", testWalletAccount)...) + + e.In.WriteString("testpass\r") // should override signature with the previous sender + e.Run(t, append(cmd, "--wallet", testWalletPath, + "--sender", validatorAddr, "--account", testWalletAccount)...) + + e.In.WriteString("one\r") + e.Run(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", nefName, "--manifest", manifestName, + "--wallet", validatorWallet, "--address", validatorAddr) +} + func deployVerifyContract(t *testing.T, e *executor) util.Uint160 { return deployContract(t, e, "testdata/verify.go", "testdata/verify.yml", validatorWallet, validatorAddr, "one") } diff --git a/cli/smartcontract/manifest.go b/cli/smartcontract/manifest.go new file mode 100644 index 000000000..e7f5d4ee7 --- /dev/null +++ b/cli/smartcontract/manifest.go @@ -0,0 +1,122 @@ +package smartcontract + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/urfave/cli" +) + +func manifestAddGroup(ctx *cli.Context) error { + walletPath := ctx.String("wallet") + if len(walletPath) == 0 { + return cli.NewExitError(errNoWallet, 1) + } + + w, err := wallet.NewWalletFromFile(walletPath) + if err != nil { + return cli.NewExitError(err, 1) + } + defer w.Close() + + addr, err := flags.ParseAddress(ctx.String("account")) + if err != nil { + return cli.NewExitError(errors.New("address is missing"), 1) + } + + sender, err := flags.ParseAddress(ctx.String("sender")) + if err != nil { + return cli.NewExitError("invalid sender", 1) + } + + nf, _, err := readNEFFile(ctx.String("nef")) + if err != nil { + return cli.NewExitError(fmt.Errorf("can't read NEF file: %w", err), 1) + } + + mPath := ctx.String("manifest") + m, _, err := readManifest(mPath) + if err != nil { + return cli.NewExitError(fmt.Errorf("can't read contract manifest: %w", err), 1) + } + + h := state.CreateContractHash(sender, nf.Checksum, m.Name) + + gAcc, err := getUnlockedAccount(w, addr) + if err != nil { + return err + } + + var found bool + + sig := gAcc.PrivateKey().Sign(h.BytesBE()) + pub := gAcc.PrivateKey().PublicKey() + for i := range m.Groups { + if m.Groups[i].PublicKey.Equal(pub) { + m.Groups[i].Signature = sig + found = true + break + } + } + if !found { + m.Groups = append(m.Groups, manifest.Group{ + PublicKey: pub, + Signature: sig, + }) + } + + rawM, err := json.Marshal(m) + if err != nil { + return cli.NewExitError(fmt.Errorf("can't marshal manifest: %w", err), 1) + } + + err = ioutil.WriteFile(mPath, rawM, os.ModePerm) + if err != nil { + return cli.NewExitError(fmt.Errorf("can't write manifest file: %w", err), 1) + } + return nil +} + +func readNEFFile(filename string) (*nef.File, []byte, error) { + if len(filename) == 0 { + return nil, nil, errors.New("no nef file was provided") + } + + f, err := ioutil.ReadFile(filename) + if err != nil { + return nil, nil, err + } + + nefFile, err := nef.FileFromBytes(f) + if err != nil { + return nil, nil, fmt.Errorf("can't parse NEF file: %w", err) + } + + return &nefFile, f, nil +} + +func readManifest(filename string) (*manifest.Manifest, []byte, error) { + if len(filename) == 0 { + return nil, nil, errNoManifestFile + } + + manifestBytes, err := ioutil.ReadFile(filename) + if err != nil { + return nil, nil, err + } + + m := new(manifest.Manifest) + err = json.Unmarshal(manifestBytes, m) + if err != nil { + return nil, nil, err + } + return m, manifestBytes, nil +} diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index c715856fc..0c9cd6770 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -370,6 +370,36 @@ func NewCommands() []cli.Command { }, }, }, + { + Name: "manifest", + Usage: "manifest-related commands", + Subcommands: []cli.Command{ + { + Name: "add-group", + Usage: "adds group to the manifest", + Action: manifestAddGroup, + Flags: []cli.Flag{ + walletFlag, + cli.StringFlag{ + Name: "sender, s", + Usage: "deploy transaction sender", + }, + cli.StringFlag{ + Name: "account, a", + Usage: "account to sign group with", + }, + cli.StringFlag{ + Name: "nef, n", + Usage: "path to the NEF file", + }, + cli.StringFlag{ + Name: "manifest, m", + Usage: "path to the manifest", + }, + }, + }, + }, + }, }, }} } @@ -750,22 +780,32 @@ func getAccFromContext(ctx *cli.Context) (*wallet.Account, *wallet.Wallet, error } else { addr = wall.GetChangeAddress() } + + acc, err := getUnlockedAccount(wall, addr) + return acc, wall, err +} + +func getUnlockedAccount(wall *wallet.Wallet, addr util.Uint160) (*wallet.Account, error) { acc := wall.GetAccount(addr) if acc == nil { - return nil, nil, cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)), 1) + return nil, cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)), 1) + } + + if acc.PrivateKey() != nil { + return acc, nil } rawPass, err := input.ReadPassword( fmt.Sprintf("Enter account %s password > ", address.Uint160ToString(addr))) if err != nil { - return nil, nil, cli.NewExitError(err, 1) + return nil, cli.NewExitError(err, 1) } pass := strings.TrimRight(string(rawPass), "\n") err = acc.Decrypt(pass, wall.Scrypt) if err != nil { - return nil, nil, cli.NewExitError(err, 1) + return nil, cli.NewExitError(err, 1) } - return acc, wall, nil + return acc, nil } // contractDeploy deploys contract.