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) }