diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 5fd993f01..921c8008b 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -337,6 +337,12 @@ func isInitFunc(decl *ast.FuncDecl) bool { decl.Type.Results.NumFields() == 0 } +func (c *codegen) isVerifyFunc(decl *ast.FuncDecl) bool { + return decl.Name.Name == "Verify" && decl.Recv == nil && + decl.Type.Results.NumFields() == 1 && + isBool(c.typeOf(decl.Type.Results.List[0].Type)) +} + func (c *codegen) clearSlots(n int) { for i := 0; i < n; i++ { emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 0545c716e..867e70021 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -59,6 +59,7 @@ type Options struct { type buildInfo struct { initialPackage string program *loader.Program + options *Options } // ForEachPackage executes fn on each package used in the current program @@ -153,10 +154,18 @@ func Compile(name string, r io.Reader) ([]byte, error) { // CompileWithDebugInfo compiles a Go program into bytecode and emits debug info. func CompileWithDebugInfo(name string, r io.Reader) ([]byte, *DebugInfo, error) { + return CompileWithOptions(name, r, &Options{ + NoEventsCheck: true, + }) +} + +// CompileWithOptions compiles a Go program into bytecode with provided compiler options. +func CompileWithOptions(name string, r io.Reader, o *Options) ([]byte, *DebugInfo, error) { ctx, err := getBuildInfo(name, r) if err != nil { return nil, nil, err } + ctx.options = o return CodeGen(ctx) } @@ -173,7 +182,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { if len(o.Ext) == 0 { o.Ext = fileExt } - b, di, err := CompileWithDebugInfo(src, nil) + b, di, err := CompileWithOptions(src, nil, o) if err != nil { return nil, fmt.Errorf("error while trying to compile smart contract file: %w", err) } diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index d77a26012..eb18a1b1e 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -1,6 +1,7 @@ package compiler_test import ( + "fmt" "io/ioutil" "os" "path" @@ -175,3 +176,24 @@ func TestEventWarnings(t *testing.T) { require.NoError(t, err) }) } + +func TestNotifyInVerify(t *testing.T) { + srcTmpl := `package payable + import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + func Verify() bool { runtime.%s("Event"); return true }` + + for _, name := range []string{"Notify", "Log"} { + t.Run(name, func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, name) + _, _, err := compiler.CompileWithOptions("eventTest", strings.NewReader(src), + &compiler.Options{ContractEvents: []manifest.Event{{Name: "Event"}}}) + require.Error(t, err) + + t.Run("suppress", func(t *testing.T) { + _, _, err := compiler.CompileWithOptions("eventTest", strings.NewReader(src), + &compiler.Options{NoEventsCheck: true}) + require.NoError(t, err) + }) + }) + } +} diff --git a/pkg/compiler/inline.go b/pkg/compiler/inline.go index 6c5b676ce..f578888cb 100644 --- a/pkg/compiler/inline.go +++ b/pkg/compiler/inline.go @@ -121,7 +121,21 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) { } func (c *codegen) processNotify(f *funcScope, args []ast.Expr) { - if f != nil && f.pkg.Path() == interopPrefix+"/runtime" && f.name == "Notify" { + if f != nil && f.pkg.Path() == interopPrefix+"/runtime" { + if f.name != "Notify" && f.name != "Log" { + return + } + + if c.scope != nil && c.isVerifyFunc(c.scope.decl) && + c.scope.pkg == c.mainPkg.Pkg && !c.buildInfo.options.NoEventsCheck { + c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name) + return + } + + if f.name == "Log" { + return + } + // Sometimes event name is stored in a var. // Skip in this case. tv := c.typeAndValueOf(args[0])