From de3395fb5127ecde50a9f0876f5e0067f433a1a3 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Sun, 25 Feb 2018 13:26:56 +0100 Subject: [PATCH] Refactor of imports + lots of sweet stuff (#30) * implemented global variables. * refactored imports + lots and lots of other sweet stuff + fix #28. * Implemented the VM interop runtime API (GetTrigger, CheckWitness, ...) --- VERSION | 2 +- pkg/smartcontract/.keep | 0 pkg/smartcontract/runtime/runtime.go | 36 +++ pkg/{vm => }/smartcontract/storage/storage.go | 2 +- pkg/vm/compiler/README.md | 6 +- pkg/vm/compiler/analysis.go | 71 ++++++ pkg/vm/compiler/codegen.go | 220 ++++++++++-------- pkg/vm/compiler/compiler.go | 42 ++-- pkg/vm/compiler/func_scope.go | 23 +- pkg/vm/compiler/tests/compiler_test.go | 12 +- pkg/vm/compiler/tests/constant_test.go | 64 +++++ pkg/vm/compiler/tests/storage_test.go | 2 +- pkg/vm/smartcontract/runtime/runtime.go | 14 -- pkg/vm/syscall.go | 8 + 14 files changed, 344 insertions(+), 158 deletions(-) delete mode 100644 pkg/smartcontract/.keep create mode 100644 pkg/smartcontract/runtime/runtime.go rename pkg/{vm => }/smartcontract/storage/storage.go (91%) create mode 100644 pkg/vm/compiler/analysis.go create mode 100644 pkg/vm/compiler/tests/constant_test.go delete mode 100644 pkg/vm/smartcontract/runtime/runtime.go diff --git a/VERSION b/VERSION index a55105169..04a373efe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.15.0 +0.16.0 diff --git a/pkg/smartcontract/.keep b/pkg/smartcontract/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/smartcontract/runtime/runtime.go b/pkg/smartcontract/runtime/runtime.go new file mode 100644 index 000000000..73d9c8e77 --- /dev/null +++ b/pkg/smartcontract/runtime/runtime.go @@ -0,0 +1,36 @@ +package runtime + +import "github.com/CityOfZion/neo-go/pkg/core" + +// T is shorthand for interface{} which allows us to use the function signatures +// in a more elegant way. +type T interface{} + +// GetTrigger return the current trigger type. The return in this function +// doesn't really mather, this is just an interop placeholder. +func GetTrigger() T { return 0 } + +// CheckWitness verifies if the invoker is the owner of the contract. +func CheckWitness(hash T) T { return 0 } + +// GetCurrentBlock returns the current block. +func GetCurrentBlock() core.Block { return core.Block{} } + +// GetTime returns the timestamp of the most recent block. +func GetTime() int { return 0 } + +// Notify an event to the VM. +func Notify(arg T) T { return 0 } + +// Log intructs the VM to log the given message. +func Log(message string) T { return 0 } + +// Verification returns the verification trigger type. +func Verification() byte { + return 0x00 +} + +// Application returns the application trigger type. +func Application() byte { + return 0x10 +} diff --git a/pkg/vm/smartcontract/storage/storage.go b/pkg/smartcontract/storage/storage.go similarity index 91% rename from pkg/vm/smartcontract/storage/storage.go rename to pkg/smartcontract/storage/storage.go index 885f7b7c9..f6bfdcf0a 100644 --- a/pkg/vm/smartcontract/storage/storage.go +++ b/pkg/smartcontract/storage/storage.go @@ -1,7 +1,7 @@ package storage // GetContext .. -func GetContext() int { return 0 } +func GetContext() interface{} { return 0 } // Put stores a value in to the storage. func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 } diff --git a/pkg/vm/compiler/README.md b/pkg/vm/compiler/README.md index f3c495243..bb8928e6a 100644 --- a/pkg/vm/compiler/README.md +++ b/pkg/vm/compiler/README.md @@ -15,7 +15,8 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin ### Go internals - type checker - multiple assigns -- types int, string and booleans +- global variables +- types int, string, byte and booleans - struct types + method receives - functions - composite literals `[]int, []string` @@ -26,12 +27,13 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin ### VM API (interop layer) - storage +- runtime ## Not yet implemented - for loops - ranges - builtins (append, len, ..) -- large part of the interop layer (VM API) +- some parts of the interop layer (VM API) ## Not supported Due to the limitations of the NEO virtual machine, features listed below will not be supported. diff --git a/pkg/vm/compiler/analysis.go b/pkg/vm/compiler/analysis.go new file mode 100644 index 000000000..512bfb998 --- /dev/null +++ b/pkg/vm/compiler/analysis.go @@ -0,0 +1,71 @@ +package compiler + +import ( + "go/ast" + "go/constant" + "go/types" + "log" + + "golang.org/x/tools/go/loader" +) + +// countGlobals counts the global variables in the program to add +// them with the stacksize of the function. +func countGlobals(f *ast.File) (i int64) { + ast.Inspect(f, func(node ast.Node) bool { + switch node.(type) { + // Skip all functio declarations. + case *ast.FuncDecl: + return false + // After skipping all funcDecls we are sure that each value spec + // is a global declared variable or constant. + case *ast.ValueSpec: + i++ + } + return true + }) + return +} + +// isIdentBool looks if the given ident is a boolean. +func isIdentBool(ident *ast.Ident) bool { + return ident.Name == "true" || ident.Name == "false" +} + +// makeBoolFromIdent creates a bool type from an *ast.Ident. +func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue { + var b bool + if ident.Name == "true" { + b = true + } else if ident.Name == "false" { + b = false + } else { + log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name) + } + return types.TypeAndValue{ + Type: tinfo.ObjectOf(ident).Type(), + Value: constant.MakeBool(b), + } +} + +// resolveEntryPoint returns the function declaration of the entrypoint and the corresponding file. +func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *ast.File) { + var ( + main *ast.FuncDecl + file *ast.File + ) + for _, f := range pkg.Files { + ast.Inspect(f, func(n ast.Node) bool { + switch t := n.(type) { + case *ast.FuncDecl: + if t.Name.Name == entry { + main = t + file = f + return false + } + } + return true + }) + } + return main, file +} diff --git a/pkg/vm/compiler/codegen.go b/pkg/vm/compiler/codegen.go index 08bfd409a..87e95f7db 100644 --- a/pkg/vm/compiler/codegen.go +++ b/pkg/vm/compiler/codegen.go @@ -15,16 +15,20 @@ import ( const mainIdent = "Main" type codegen struct { + // Information about the program with all its dependencies. + buildInfo *buildInfo + + // prog holds the output buffer prog *bytes.Buffer // Type information typeInfo *types.Info - // a mapping of func identifiers with its scope. + // A mapping of func identifiers with their scope. funcs map[string]*funcScope - // current function being generated - fctx *funcScope + // Current funcScope being converted. + scope *funcScope // Label table for recording jump destinations. l []int @@ -50,15 +54,21 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) { switch typ := t.Type.Underlying().(type) { case *types.Basic: switch typ.Kind() { - case types.Int: + case types.Int, types.UntypedInt: val, _ := constant.Int64Val(t.Value) emitInt(c.prog, val) - case types.String: + case types.String, types.UntypedString: val := constant.StringVal(t.Value) emitString(c.prog, val) case types.Bool, types.UntypedBool: val := constant.BoolVal(t.Value) emitBool(c.prog, val) + case types.Byte: + val, _ := constant.Int64Val(t.Value) + b := byte(val) + emitBytes(c.prog, []byte{b}) + default: + log.Fatalf("compiler don't know how to convert this basic type: %v", t) } default: log.Fatalf("compiler don't know how to convert this constant: %v", t) @@ -66,7 +76,7 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) { } func (c *codegen) emitLoadLocal(name string) { - pos := c.fctx.loadLocal(name) + pos := c.scope.loadLocal(name) if pos < 0 { log.Fatalf("cannot load local variable with position: %d", pos) } @@ -80,7 +90,7 @@ func (c *codegen) emitLoadLocal(name string) { } func (c *codegen) emitLoadStructField(sName, fName string) { - strct := c.fctx.loadStruct(sName) + strct := c.scope.loadStruct(sName) pos := strct.loadField(fName) emitInt(c.prog, int64(pos)) emitOpcode(c.prog, vm.Opickitem) @@ -102,14 +112,42 @@ func (c *codegen) emitStoreLocal(pos int) { } func (c *codegen) emitStoreStructField(sName, fName string) { - strct := c.fctx.loadStruct(sName) + strct := c.scope.loadStruct(sName) pos := strct.loadField(fName) emitInt(c.prog, int64(pos)) emitOpcode(c.prog, vm.Orot) emitOpcode(c.prog, vm.Osetitem) } -func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) { +func (c *codegen) emitSyscallReturn() { + emitOpcode(c.prog, vm.Ojmp) + emitOpcode(c.prog, vm.Opcode(0x03)) + emitOpcode(c.prog, vm.Opush0) + + emitInt(c.prog, int64(0)) + + emitOpcode(c.prog, vm.Onop) + emitOpcode(c.prog, vm.Ofromaltstack) + emitOpcode(c.prog, vm.Odrop) + emitOpcode(c.prog, vm.Oret) +} + +// convertGlobals will traverse the AST and only convert global declarations. +// If we call this in convertFuncDecl then it will load all global variables +// into the scope of the function. +func (c *codegen) convertGlobals(f *ast.File) { + ast.Inspect(f, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.FuncDecl: + return false + case *ast.GenDecl: + ast.Walk(c, n) + } + return true + }) +} + +func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) { var ( f *funcScope ok bool @@ -122,10 +160,12 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) { f = c.newFunc(decl) } - c.fctx = f - ast.Inspect(decl, c.fctx.analyzeVoidCalls) + c.scope = f + ast.Inspect(decl, c.scope.analyzeVoidCalls) - emitInt(c.prog, f.stackSize()) + // All globals copied into the scope of the function need to be added + // to the stack size of the function. + emitInt(c.prog, f.stackSize()+countGlobals(file)) emitOpcode(c.prog, vm.Onewarray) emitOpcode(c.prog, vm.Otoaltstack) @@ -142,8 +182,8 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) { if !ok { log.Fatal("method receiver is not a struct type") } - c.fctx.newStruct(t) - l := c.fctx.newLocal(ident.Name) + c.scope.newStruct(t) + l := c.scope.newLocal(ident.Name) c.emitStoreLocal(l) } } @@ -151,26 +191,39 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) { // Load the arguments in scope. for _, arg := range decl.Type.Params.List { name := arg.Names[0].Name // for now. - l := c.fctx.newLocal(name) + l := c.scope.newLocal(name) c.emitStoreLocal(l) } - ast.Walk(c, decl.Body) + // If this function is a syscall we will manipulate the return value to 0. + // All the syscalls are just signatures functions and bring no real return value. + // The return values you will find in the smartcontract package is just for + // satisfying the typechecker and the user experience. + if isSyscall(f.name) { + c.emitSyscallReturn() + } else { + // After loading the arguments we can convert the globals into the scope of the function. + c.convertGlobals(file) + ast.Walk(c, decl.Body) + } } func (c *codegen) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { // General declarations. - // With value: var x int = 2 - // Without value: var x int + // var ( + // x = 2 + // ) case *ast.GenDecl: - switch t := n.Specs[0].(type) { - case *ast.ValueSpec: - if len(t.Values) > 0 { - ast.Walk(c, t.Values[0]) - l := c.fctx.newLocal(t.Names[0].Name) - c.emitStoreLocal(l) + for _, spec := range n.Specs { + switch t := spec.(type) { + case *ast.ValueSpec: + for i, val := range t.Values { + ast.Walk(c, val) + l := c.scope.newLocal(t.Names[i].Name) + c.emitStoreLocal(l) + } } } return nil @@ -184,11 +237,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emitLoadLocal(t.Name) ast.Walk(c, n.Rhs[0]) c.convertToken(n.Tok) - l := c.fctx.loadLocal(t.Name) + l := c.scope.loadLocal(t.Name) c.emitStoreLocal(l) default: ast.Walk(c, n.Rhs[0]) - l := c.fctx.loadLocal(t.Name) + l := c.scope.loadLocal(t.Name) c.emitStoreLocal(l) } @@ -246,7 +299,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil case *ast.BasicLit: - c.emitLoadConst(c.getTypeInfo(n)) + c.emitLoadConst(c.typeInfo.Types[n]) return nil case *ast.Ident: @@ -268,7 +321,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { default: ln := len(n.Elts) for i := ln - 1; i >= 0; i-- { - c.emitLoadConst(c.getTypeInfo(n.Elts[i])) + c.emitLoadConst(c.typeInfo.Types[n.Elts[i]]) } emitInt(c.prog, int64(ln)) emitOpcode(c.prog, vm.Opack) @@ -300,9 +353,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // The AST package will try to resolve all basic literals for us. // If the typeinfo.Value is not nil we know that the expr is resolved // and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6. - if tinfo := c.getTypeInfo(n); tinfo.Value != nil { + // NOTE: Constants will also be automagically resolved be the AST parser. + // example: + // const x = 10 + // x + 2 will results into 12 + if tinfo := c.typeInfo.Types[n]; tinfo.Value != nil { c.emitLoadConst(tinfo) - return c + return nil } ast.Walk(c, n.X) @@ -364,7 +421,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // If we are not assigning this function to a variable we need to drop // the top stack item. It's not a void but you get the point \o/. - if _, ok := c.fctx.voidCalls[n]; ok && !isNoRetSyscall(f.name) { + if _, ok := c.scope.voidCalls[n]; ok && !isNoRetSyscall(f.name) { emitOpcode(c.prog, vm.Odrop) } return nil @@ -372,8 +429,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.SelectorExpr: switch t := n.X.(type) { case *ast.Ident: - c.emitLoadLocal(t.Name) // load the struct - c.emitLoadStructField(t.Name, n.Sel.Name) // load the field + typ := c.typeInfo.ObjectOf(t).Type().Underlying() + switch typ.(type) { + case *types.Struct: + c.emitLoadLocal(t.Name) // load the struct + c.emitLoadStructField(t.Name, n.Sel.Name) // load the field + default: + log.Fatal("non struct import selections not yet implemented, please use functions instead") + } default: log.Fatal("nested selectors not supported yet") } @@ -398,7 +461,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { if !ok { log.Fatalf("the given literal is not of type struct: %v", lit) } - strct := c.fctx.newStruct(t) + strct := c.scope.newStruct(t) emitOpcode(c.prog, vm.Onop) emitInt(c.prog, int64(strct.t.NumFields())) @@ -472,82 +535,43 @@ func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope { return f } -// TODO: Don't know if we really use this. Check if it can be deleted. -// If it's used once or twice, also remove this functions and -// call .Types direcly. e.g. c.typeInfo.Types[expr] -func (c *codegen) getTypeInfo(expr ast.Expr) types.TypeAndValue { - return c.typeInfo.Types[expr] -} - -func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue { - var b bool - if ident.Name == "true" { - b = true - } else if ident.Name == "false" { - b = false - } else { - log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name) - } - - return types.TypeAndValue{ - Type: tinfo.ObjectOf(ident).Type(), - Value: constant.MakeBool(b), - } -} - -func isIdentBool(ident *ast.Ident) bool { - return ident.Name == "true" || ident.Name == "false" -} - // CodeGen is the function that compiles the program to bytecode. -func CodeGen(f *ast.File, tInfo *types.Info, imports map[string]*archive) (*bytes.Buffer, error) { +func CodeGen(info *buildInfo) (*bytes.Buffer, error) { + pkg := info.program.Package(info.initialPackage) c := &codegen{ - prog: new(bytes.Buffer), - l: []int{}, - funcs: map[string]*funcScope{}, - typeInfo: tInfo, + buildInfo: info, + prog: new(bytes.Buffer), + l: []int{}, + funcs: map[string]*funcScope{}, + typeInfo: &pkg.Info, } - var main *ast.FuncDecl - ast.Inspect(f, func(n ast.Node) bool { - switch t := n.(type) { - case *ast.FuncDecl: - if t.Name.Name == mainIdent { - main = t - return false - } - } - return true - }) + // Resolve the entrypoint of the program + main, mainFile := resolveEntryPoint(mainIdent, pkg) if main == nil { log.Fatal("could not find func main. did you forgot to declare it?") } // Bring all imported functions into scope - for _, arch := range imports { - c.resolveFuncDecls(arch.f) - } - - c.resolveFuncDecls(f) - c.convertFuncDecl(main) - - for _, decl := range f.Decls { - switch n := decl.(type) { - case *ast.FuncDecl: - if n.Name.Name != mainIdent { - c.convertFuncDecl(n) - } + for _, pkg := range info.program.AllPackages { + for _, f := range pkg.Files { + c.resolveFuncDecls(f) } } - // Generate code for the imported packages. - for _, arch := range imports { - c.typeInfo = arch.typeInfo - for _, decl := range arch.f.Decls { - switch n := decl.(type) { - case *ast.FuncDecl: - if n.Name.Name != mainIdent { - c.convertFuncDecl(n) + // convert the entry point first + c.convertFuncDecl(mainFile, main) + + // Generate the code for the program + for _, pkg := range info.program.AllPackages { + c.typeInfo = &pkg.Info + for _, f := range pkg.Files { + for _, decl := range f.Decls { + switch n := decl.(type) { + case *ast.FuncDecl: + if n.Name.Name != mainIdent { + c.convertFuncDecl(f, n) + } } } } diff --git a/pkg/vm/compiler/compiler.go b/pkg/vm/compiler/compiler.go index 46655b89c..ea2f31697 100644 --- a/pkg/vm/compiler/compiler.go +++ b/pkg/vm/compiler/compiler.go @@ -19,6 +19,7 @@ import ( "text/tabwriter" "github.com/CityOfZion/neo-go/pkg/vm" + "golang.org/x/tools/go/loader" ) const fileExt = "avm" @@ -35,36 +36,31 @@ type Options struct { Debug bool } +type buildInfo struct { + initialPackage string + program *loader.Program +} + // Compile compiles a Go program into bytecode that can run on the NEO virtual machine. -func Compile(input io.Reader, o *Options) ([]byte, error) { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", input, 0) +func Compile(r io.Reader, o *Options) ([]byte, error) { + conf := loader.Config{ParserMode: parser.ParseComments} + f, err := conf.ParseFile("", r) + if err != nil { + return nil, err + } + conf.CreateFromFiles("", f) + + prog, err := conf.Load() if err != nil { return nil, err } - conf := types.Config{Importer: importer.Default()} - typeInfo := &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), - Implicits: make(map[ast.Node]types.Object), - Selections: make(map[*ast.SelectorExpr]*types.Selection), - Scopes: make(map[ast.Node]*types.Scope), + ctx := &buildInfo{ + initialPackage: f.Name.Name, + program: prog, } - // Typechecker - _, err = conf.Check("", fset, []*ast.File{f}, typeInfo) - if err != nil { - return nil, err - } - - imports, err := resolveImports(f) - if err != nil { - return nil, err - } - - buf, err := CodeGen(f, typeInfo, imports) + buf, err := CodeGen(ctx) if err != nil { return nil, err } diff --git a/pkg/vm/compiler/func_scope.go b/pkg/vm/compiler/func_scope.go index 4c05718bf..2362aacb6 100644 --- a/pkg/vm/compiler/func_scope.go +++ b/pkg/vm/compiler/func_scope.go @@ -6,20 +6,20 @@ import ( "log" ) -// A funcScope represents a scope within the function context. +// A funcScope represents the scope within the function context. // It holds al the local variables along with the initialized struct positions. type funcScope struct { - // function identifier + // identifier of the function. name string - // The declaration of the function in the AST + // The declaration of the function in the AST. Nil if this scope is not a function. decl *ast.FuncDecl - // Program label of the function + // Program label of the scope label int - // Local scope of the function - scope map[string]int + // Local variables + locals map[string]int // A mapping of structs positions with their scope structs map[int]*structScope @@ -41,7 +41,7 @@ func newFuncScope(decl *ast.FuncDecl, label int) *funcScope { name: decl.Name.Name, decl: decl, label: label, - scope: map[string]int{}, + locals: map[string]int{}, structs: map[int]*structScope{}, voidCalls: map[*ast.CallExpr]bool{}, i: -1, @@ -64,8 +64,11 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool { case *ast.CallExpr: return false } + case *ast.BinaryExpr: + return false case *ast.CallExpr: c.voidCalls[n] = true + return false } return true } @@ -98,7 +101,7 @@ func (c *funcScope) stackSize() int64 { func (c *funcScope) newStruct(t *types.Struct) *structScope { strct := newStructScope(t) - c.structs[len(c.scope)] = strct + c.structs[len(c.locals)] = strct return strct } @@ -114,13 +117,13 @@ func (c *funcScope) loadStruct(name string) *structScope { // newLocal creates a new local variable into the scope of the function. func (c *funcScope) newLocal(name string) int { c.i++ - c.scope[name] = c.i + c.locals[name] = c.i return c.i } // loadLocal loads the position of a local variable inside the scope of the function. func (c *funcScope) loadLocal(name string) int { - i, ok := c.scope[name] + i, ok := c.locals[name] if !ok { // should emit a compiler warning. return c.newLocal(name) diff --git a/pkg/vm/compiler/tests/compiler_test.go b/pkg/vm/compiler/tests/compiler_test.go index e87c3481f..d77e166f7 100644 --- a/pkg/vm/compiler/tests/compiler_test.go +++ b/pkg/vm/compiler/tests/compiler_test.go @@ -32,14 +32,11 @@ func TestAllCases(t *testing.T) { testCases = append(testCases, structTestCases...) testCases = append(testCases, ifStatementTestCases...) testCases = append(testCases, customTypeTestCases...) - - // TODO: issue #28 - // These tests are passing locally, but circleci is failing to resolve the dependency. - // https://github.com/CityOfZion/neo-go/issues/28 - // testCases = append(testCases, importTestCases...) + testCases = append(testCases, constantTestCases...) + testCases = append(testCases, importTestCases...) // Blockchain specific - // testCases = append(testCases, storageTestCases...) + testCases = append(testCases, storageTestCases...) for _, tc := range testCases { b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{}) @@ -54,8 +51,7 @@ func TestAllCases(t *testing.T) { if bytes.Compare(b, expectedResult) != 0 { t.Log(hex.EncodeToString(b)) - want, _ := hex.DecodeString(tc.result) - dumpOpCodeSideBySide(b, want) + dumpOpCodeSideBySide(b, expectedResult) t.Fatalf("compiling %s failed", tc.name) } } diff --git a/pkg/vm/compiler/tests/constant_test.go b/pkg/vm/compiler/tests/constant_test.go new file mode 100644 index 000000000..1bca94d64 --- /dev/null +++ b/pkg/vm/compiler/tests/constant_test.go @@ -0,0 +1,64 @@ +package compiler + +var constantTestCases = []testCase{ + { + "basic constant", + ` + package foo + + const x = 10 + + func Main() int { + return x + 2 + } + `, + // This ouput wil not be the same als the boa compiler. + // The go compiler will automatically resolve binary expressions + // involving constants. + // x + 2 in this case will be resolved to 12. + "52c56b5a6c766b00527ac46203005c616c7566", + }, + { + "shorthand multi const", + ` + package foo + + const ( + z = 3 + y = 2 + x = 1 + ) + + // should load al 3 constants in the Main. + func Main() int { + return 0 + } + `, + "54c56b536c766b00527ac4526c766b51527ac4516c766b52527ac462030000616c7566", + }, + { + "globals with function arguments", + ` + package foobar + + const ( + bar = "FOO" + foo = "BAR" + ) + + func something(x int) string { + if x > 10 { + return bar + } + return foo + } + + func Main() string { + trigger := 100 + x := something(trigger) + return x + } + `, + "55c56b03464f4f6c766b00527ac4034241526c766b51527ac401646c766b52527ac46c766b52c3616516006c766b53527ac46203006c766b53c3616c756656c56b6c766b00527ac403464f4f6c766b51527ac4034241526c766b52527ac46c766b00c35aa0640f006203006c766b51c3616c75666203006c766b52c3616c7566", + }, +} diff --git a/pkg/vm/compiler/tests/storage_test.go b/pkg/vm/compiler/tests/storage_test.go index e62f6f7b3..4b395e5ca 100644 --- a/pkg/vm/compiler/tests/storage_test.go +++ b/pkg/vm/compiler/tests/storage_test.go @@ -6,7 +6,7 @@ var storageTestCases = []testCase{ ` package foo - import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/storage" + import "github.com/CityOfZion/neo-go/pkg/smartcontract/storage" func Main() int { ctx := storage.GetContext() diff --git a/pkg/vm/smartcontract/runtime/runtime.go b/pkg/vm/smartcontract/runtime/runtime.go deleted file mode 100644 index e237af3e9..000000000 --- a/pkg/vm/smartcontract/runtime/runtime.go +++ /dev/null @@ -1,14 +0,0 @@ -package runtime - -// TriggerType represents a byte. -type TriggerType byte - -// List of valid trigger types. -const ( - Verification TriggerType = 0x00 - Application TriggerType = 0x10 -) - -// GetTrigger return the current trigger type. The return in this function -// doesn't really mather, this is just an interop placeholder. -func GetTrigger() TriggerType { return 0x00 } diff --git a/pkg/vm/syscall.go b/pkg/vm/syscall.go index 275400195..2506b4f68 100644 --- a/pkg/vm/syscall.go +++ b/pkg/vm/syscall.go @@ -9,4 +9,12 @@ var Syscalls = map[string]string{ "GetInt": "Neo.Storage.Get", "GetString": "Neo.Storage.Get", "Delete": "Neo.Storage.Delete", + + // Runtime + "GetTrigger": "Neo.Runtime.GetTrigger", + "CheckWitness": "Neo.Runtime.CheckWitness", + "GetCurrentBlock": "Neo.Runtime.GetCurrentBlock", + "GetTime": "Neo.Runtime.GetTime", + "Notify": "Neo.runtime.Notify", + "Log": "Neo.Runtime.Log", }