From 6ff0aa8d11efb51c855aa279646d1de34f4fe9ab Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 2 Nov 2023 15:21:08 +0300 Subject: [PATCH] cli: add --no-nef-check option to `contract compile` command This option allows to skip the generated NEF file size check on contract compilation. The resulting .nef file can't be deployed to the chain and is invalid according to the updated verification rules, but it still may be useful either for previous node versions or for development/debugging purposes. Signed-off-by: Anna Shaleva --- cli/smartcontract/contract_test.go | 31 +++++++++++++++++++++++++++++ cli/smartcontract/smart_contract.go | 7 ++++++- docs/compiler.md | 9 +++++++++ pkg/compiler/compiler.go | 6 +++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/cli/smartcontract/contract_test.go b/cli/smartcontract/contract_test.go index fca6808f3a..b19558d6ee 100644 --- a/cli/smartcontract/contract_test.go +++ b/cli/smartcontract/contract_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "encoding/json" + "fmt" "os" "path/filepath" "strconv" @@ -304,6 +305,36 @@ func TestContractInitAndCompile(t *testing.T) { }) } +func TestContractCompile_NEFSizeCheck(t *testing.T) { + tmpDir := t.TempDir() + e := testcli.NewExecutor(t, false) + + src := `package nefconstraints + var data = "%s" + + func Main() string { + return data + }` + data := make([]byte, stackitem.MaxSize-10) + for i := range data { + data[i] = byte('a') + } + + in := filepath.Join(tmpDir, "main.go") + cfg := filepath.Join(tmpDir, "main.yml") + require.NoError(t, os.WriteFile(cfg, []byte("name: main"), os.ModePerm)) + require.NoError(t, os.WriteFile(in, []byte(fmt.Sprintf(src, data)), os.ModePerm)) + + t.Run("perform size check", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "compile", "--in", in) + require.NoFileExists(t, filepath.Join(tmpDir, "main.nef")) + }) + t.Run("skip size check", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "compile", "--in", in, "--no-nef-check") + require.FileExists(t, filepath.Join(tmpDir, "main.nef")) + }) +} + // Checks that error is returned if GAS available for test-invoke exceeds // GAS needed to be consumed. func TestDeployBigContract(t *testing.T) { diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 38dea5f139..aae1398f1d 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -126,7 +126,7 @@ func NewCommands() []cli.Command { { Name: "compile", Usage: "compile a smart contract to a .nef file", - UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions] [--guess-eventtypes]", + UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions] [--no-nef-check] [--guess-eventtypes]", Description: `Compiles given smart contract to a .nef file and emits other associated information (manifest, bindings configuration, debug information files) if asked to. If none of --out, --manifest, --config, --bindings flags are specified, @@ -172,6 +172,10 @@ func NewCommands() []cli.Command { Name: "no-permissions", Usage: "do not check if invoked contracts are allowed in manifest", }, + cli.BoolFlag{ + Name: "no-nef-check", + Usage: "do not check if the resulting .nef size exceeds allowed limits; the resulting .nef file can't be deployed if exceeds stackitem.MaxSize limits", + }, cli.BoolFlag{ Name: "guess-eventtypes", Usage: "guess event types for smart-contract bindings configuration from the code usages", @@ -454,6 +458,7 @@ func contractCompile(ctx *cli.Context) error { NoStandardCheck: ctx.Bool("no-standards"), NoEventsCheck: ctx.Bool("no-events"), NoPermissionsCheck: ctx.Bool("no-permissions"), + NoNEFSizeCheck: ctx.Bool("no-nef-check"), GuessEventTypes: ctx.Bool("guess-eventtypes"), } diff --git a/docs/compiler.md b/docs/compiler.md index 18660e7038..5f0997465a 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -352,6 +352,15 @@ Using either constant or literal for contract hash and method will allow the com to perform more extensive analysis. This check can be disabled with `--no-permissions` flag. +##### NEF file validation check + +By default, compiler performs size restriction check over generated NEF file and +fails compilation if the resulting NEF file doesn't fit into `stackitem.MaxSize` +limit. However, it's still possible to disable this check and properly generate +large NEF files with `--no-nef-check` compiling option. Large NEF files can't +be deployed onto chain, but still may be used for development or debugging +purposes. + ##### Overloads NeoVM allows a contract to have multiple methods with the same name but different parameters number. Go lacks this feature, but this can be circumvented diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index e990b5ad0d..7612b5fb11 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -53,6 +53,10 @@ type Options struct { // This setting has effect only if manifest is emitted. NoPermissionsCheck bool + // NoNEFSizeCheck allows to skip serialized NEF size check against max allowed + // stackitem size. + NoNEFSizeCheck bool + // GuessEventTypes specifies if types of runtime notifications need to be guessed // from the usage context. These types are used for RPC binding generation only and // can be defined for events with name known at the compilation time and without @@ -276,7 +280,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { }() f.Checksum = f.CalculateChecksum() } - bytes, err := f.Bytes(true) + bytes, err := f.Bytes(!o.NoNEFSizeCheck) if err != nil { return nil, fmt.Errorf("error while serializing .nef file: %w", err) }