From c49eb86a2ecbc95e59ab14fd362ae370e5fa9bd9 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 17 Dec 2020 15:41:53 +0300 Subject: [PATCH] cli: allow to calculate contract hash before deployment --- cli/contract_test.go | 55 +++++++++++++++++++++++++++++ cli/smartcontract/smart_contract.go | 44 +++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/cli/contract_test.go b/cli/contract_test.go index 994216ae7..4db8aca80 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -9,14 +9,63 @@ import ( "strings" "testing" + "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "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/rpc/response/result" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/stretchr/testify/require" ) +func TestCalcHash(t *testing.T) { + e := newExecutor(t, false) + defer e.Close(t) + + nefPath := "./testdata/verify.nef" + src, err := ioutil.ReadFile(nefPath) + require.NoError(t, err) + nefF, err := nef.FileFromBytes(src) + require.NoError(t, err) + sender := random.Uint160() + + cmd := []string{"neo-go", "contract", "calc-hash"} + t.Run("no sender", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--in", nefPath)...) + }) + t.Run("no nef file", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--sender", sender.StringLE())...) + }) + t.Run("invalid path", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), + "--in", "./testdata/verify.nef123")...) + }) + t.Run("invalid file", func(t *testing.T) { + p := path.Join(os.TempDir(), "neogo.calchash.verify.nef") + defer os.Remove(p) + require.NoError(t, ioutil.WriteFile(p, src[:4], os.ModePerm)) + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", p)...) + }) + + cmd = append(cmd, "--in", nefPath) + expected := state.CreateContractHash(sender, nefF.Script) + t.Run("valid, uint160", func(t *testing.T) { + e.Run(t, append(cmd, "--sender", sender.StringLE())...) + e.checkNextLine(t, expected.StringLE()) + }) + t.Run("valid, uint160 with 0x", func(t *testing.T) { + e.Run(t, append(cmd, "--sender", "0x"+sender.StringLE())...) + e.checkNextLine(t, expected.StringLE()) + }) + t.Run("valid, address", func(t *testing.T) { + e.Run(t, append(cmd, "--sender", address.Uint160ToString(sender))...) + e.checkNextLine(t, expected.StringLE()) + }) +} + func TestContractInitAndCompile(t *testing.T) { tmpDir := path.Join(os.TempDir(), "neogo.inittest") require.NoError(t, os.Mkdir(tmpDir, os.ModePerm)) @@ -139,6 +188,12 @@ func TestComlileAndInvokeFunction(t *testing.T) { require.NoError(t, err) e.checkTxPersisted(t) + t.Run("check calc hash", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "calc-hash", + "--sender", validatorAddr, "--in", nefName) + e.checkNextLine(t, h.StringLE()) + }) + cmd := []string{"neo-go", "contract", "testinvokefunction", "--rpc-endpoint", "http://" + e.RPC.Addr} t.Run("missing hash", func(t *testing.T) { diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index bb130466c..c18bdb40b 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -337,6 +337,21 @@ func NewCommands() []cli.Command { }, }, }, + { + Name: "calc-hash", + Usage: "calculates hash of a contract after deployment", + Action: calcHash, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "sender, s", + Usage: "sender script hash or address", + }, + cli.StringFlag{ + Name: "in", + Usage: "path to NEF file", + }, + }, + }, }, }} } @@ -440,6 +455,35 @@ func contractCompile(ctx *cli.Context) error { return nil } +func calcHash(ctx *cli.Context) error { + s := ctx.String("sender") + u, err := address.StringToUint160(s) + if err != nil { + if strings.HasPrefix(s, "0x") { + s = s[2:] + } + u, err = util.Uint160DecodeStringLE(s) + if err != nil { + return cli.NewExitError(errors.New("invalid sender: must be either address or Uint160 in LE form"), 1) + } + } + + p := ctx.String("in") + if p == "" { + return cli.NewExitError(errors.New("no .nef file was provided"), 1) + } + f, err := ioutil.ReadFile(p) + if err != nil { + return cli.NewExitError(fmt.Errorf("can't read .nef file: %w", err), 1) + } + nefFile, err := nef.FileFromBytes(f) + if err != nil { + return cli.NewExitError(fmt.Errorf("can't unmarshal .nef file: %w", err), 1) + } + fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(u, nefFile.Script).StringLE()) + return nil +} + func testInvokeFunction(ctx *cli.Context) error { return invokeInternal(ctx, false) }