From 23458582383232a8f05d59019550cd3c83cdf8f4 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Tue, 27 Feb 2018 10:04:24 +0100 Subject: [PATCH] Compiler update (basic sc ready) (#31) * refactored structs, the scope is not needed anymore + fix passing struct in func arguments. * implemented byte arrays and added runtime tests * Added sc examples in compiler README + added quick nested if test. * Updated README --- .gitignore | 3 + README.md | 12 ++- VERSION | 2 +- pkg/smartcontract/runtime/runtime.go | 42 +++++---- pkg/smartcontract/types/block.go | 9 ++ pkg/vm/compiler/README.md | 91 ++++++++++++++++++-- pkg/vm/compiler/analysis.go | 83 ++++++++++++++++++ pkg/vm/compiler/codegen.go | 86 ++++++++++++------- pkg/vm/compiler/compiler.go | 37 -------- pkg/vm/compiler/func_scope.go | 25 +----- pkg/vm/compiler/struct_scope.go | 89 ------------------- pkg/vm/compiler/tests/assign_test.go | 11 +++ pkg/vm/compiler/tests/bool_test.go | 42 +++++++++ pkg/vm/compiler/tests/compiler_test.go | 2 + pkg/vm/compiler/tests/if_statement_test.go | 17 ++++ pkg/vm/compiler/tests/runtime_test.go | 99 ++++++++++++++++++++++ pkg/vm/compiler/tests/storage_test.go | 2 +- pkg/vm/compiler/tests/struct_test.go | 25 ++++++ pkg/vm/syscall.go | 2 +- 19 files changed, 464 insertions(+), 215 deletions(-) create mode 100644 pkg/smartcontract/types/block.go delete mode 100644 pkg/vm/compiler/struct_scope.go create mode 100644 pkg/vm/compiler/tests/runtime_test.go diff --git a/.gitignore b/.gitignore index dfcb89ce5..93cb0d193 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ bin/ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json + +# anthdm todolists +/pkg/vm/compiler/todo.md diff --git a/README.md b/README.md index 60af7e999..813dd8a2f 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,21 @@ A complete toolkit for the NEO blockchain, including: ## Current State -This project is still under heavy development. Still working on internal API's and project layout. T -his should not take longer than 2 weeks. - The project will exist out of the following packages: | Package | State | Developer | |---------------|---------|--------------------------------------| -| api | started | [@anthdm](https://github.com/anthdm) | | core | started | [@anthdm](https://github.com/anthdm) | | network | started | [@anthdm](https://github.com/anthdm) | | vm | started | [@anthdm](https://github.com/anthdm) | -| smartcontract | started | [@revett](https://github.com/revett) | +| compiler | started | [@anthdm](https://github.com/anthdm) | +| client | started | [@revett](https://github.com/revett) | | cli | started | [@revett](https://github.com/revett) | +| wallet | started | [@pawanrawal](https://github.com/pawanrawal) | # Getting Started -## Server +## Node Install dependencies, this requires [dep](https://github.com/golang/dep): @@ -112,7 +110,7 @@ TODO ``` ## Smart Contracts -> NOTE: At this moment there is only a small subset of the Go language implemented. +> In depth documentation about the neo-go compiler and smart contract examples can be found inside the [compiler package](https://github.com/CityOfZion/neo-go/tree/master/pkg/vm/compiler). ### Compile a smart contract diff --git a/VERSION b/VERSION index 04a373efe..c5523bd09 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.16.0 +0.17.0 diff --git a/pkg/smartcontract/runtime/runtime.go b/pkg/smartcontract/runtime/runtime.go index 73d9c8e77..92aa42e7e 100644 --- a/pkg/smartcontract/runtime/runtime.go +++ b/pkg/smartcontract/runtime/runtime.go @@ -1,36 +1,42 @@ 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 } +import "github.com/CityOfZion/neo-go/pkg/smartcontract/types" // CheckWitness verifies if the invoker is the owner of the contract. -func CheckWitness(hash T) T { return 0 } +func CheckWitness(hash []byte) bool { + return true +} // GetCurrentBlock returns the current block. -func GetCurrentBlock() core.Block { return core.Block{} } +func GetCurrentBlock() types.Block { return types.Block{} } // GetTime returns the timestamp of the most recent block. -func GetTime() int { return 0 } +func GetTime() int { + return 0 +} // Notify an event to the VM. -func Notify(arg T) T { return 0 } +func Notify(arg interface{}) int { + 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 +func Log(message string) int { + return 0 } // Application returns the application trigger type. func Application() byte { return 0x10 } + +// Verification returns the verification trigger type. +func Verification() byte { + return 0x00 +} + +// GetTrigger return the current trigger type. The return in this function +// doesn't really mather, this is just an interop placeholder. +func GetTrigger() interface{} { + return 0 +} diff --git a/pkg/smartcontract/types/block.go b/pkg/smartcontract/types/block.go new file mode 100644 index 000000000..928449db6 --- /dev/null +++ b/pkg/smartcontract/types/block.go @@ -0,0 +1,9 @@ +package types + +// Block represents a block in the blockchain. +type Block struct{} + +// Index returns the height of the block. +func (b Block) Index() int { + return 0 +} diff --git a/pkg/vm/compiler/README.md b/pkg/vm/compiler/README.md index bb8928e6a..aac18be68 100644 --- a/pkg/vm/compiler/README.md +++ b/pkg/vm/compiler/README.md @@ -2,13 +2,9 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand. -> The neo-go compiler is under very active development and will be updated on a weekly basis. +***NOTE:*** The neo-go compiler is under very active development and will be updated on a weekly basis. The API is likely going to chance, ***hence do not use this in production environments (mainnet)*** yet. -## Usage - -``` -./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.avm -``` +For help, questions and discussion feel free to join the [City Of Zion discord](https://discordapp.com/invite/R8v48YA) and hop in the #golang channel. Or reach out to me on twitter [@anthdm](https://twitter.com/anthdm) ## Currently supported @@ -19,7 +15,7 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin - types int, string, byte and booleans - struct types + method receives - functions -- composite literals `[]int, []string` +- composite literals `[]int, []string, []byte` - basic if statements - binary expressions. - return statements @@ -41,6 +37,87 @@ Due to the limitations of the NEO virtual machine, features listed below will no - goroutines - multiple returns +## Smart contract examples + +### Check if the invoker of the contract is the owning address + +```Golang +package mycontract + +import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime" + +var owner = []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5} + +func Main() bool { + isOwner := runtime.CheckWitness(owner) + + if isOwner { + runtime.Log("invoker is the owner") + return true + } + + return false +} +``` + +### Simple token + +```Golang +package mytoken + +import ( + "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + "github.com/CityOfZion/neo-go/pkg/smartcontract/storage" +) + +var owner = []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5} + +type Token struct { + Name string + Symbol string + TotalSupply int + Owner []byte +} + +func (t Token) AddToCirculation(amount int) bool { + ctx := storage.GetContext() + inCirc := storage.GetInt(ctx, "in_circ") + inCirc += amount + storage.Put(ctx, "in_circ", inCirc) + return true +} + +func newToken() Token { + return Token{ + Name: "your awesome NEO token", + Symbol: "YANT", + TotalSupply: 1000, + Owner: owner, + } +} + +func Main(operation string, args []interface{}) bool { + token := newToken() + trigger := runtime.GetTrigger() + + if trigger == runtime.Verification() { + isOwner := runtime.CheckWitness(token.Owner) + if isOwner { + return true + } + return false + } + + if trigger == runtime.Application() { + if operation == "mintTokens" { + token.AddToCirculation(100) + } + } + + return true +} +``` + ## How to report compiler bugs 1. Make a proper testcase (example testcases can be found in the tests folder) 2. Create an issue on Github diff --git a/pkg/vm/compiler/analysis.go b/pkg/vm/compiler/analysis.go index 512bfb998..ac6c19ff7 100644 --- a/pkg/vm/compiler/analysis.go +++ b/pkg/vm/compiler/analysis.go @@ -9,6 +9,33 @@ import ( "golang.org/x/tools/go/loader" ) +// typeAndValueForField returns a zero initializd typeAndValue or the given type.Var. +func typeAndValueForField(fld *types.Var) types.TypeAndValue { + switch t := fld.Type().(type) { + case *types.Basic: + switch t.Kind() { + case types.Int: + return types.TypeAndValue{ + Type: t, + Value: constant.MakeInt64(0), + } + case types.String: + return types.TypeAndValue{ + Type: t, + Value: constant.MakeString(""), + } + case types.Bool, types.UntypedBool: + return types.TypeAndValue{ + Type: t, + Value: constant.MakeBool(false), + } + default: + log.Fatalf("could not initialize struct field %s to zero, type: %s", fld.Name(), t) + } + } + return types.TypeAndValue{} +} + // countGlobals counts the global variables in the program to add // them with the stacksize of the function. func countGlobals(f *ast.File) (i int64) { @@ -69,3 +96,59 @@ func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *a } return main, file } + +// indexOfStruct will return the index of the given field inside that struct. +// If the struct does not contain that field it will return -1. +func indexOfStruct(strct *types.Struct, fldName string) int { + for i := 0; i < strct.NumFields(); i++ { + if strct.Field(i).Name() == fldName { + return i + } + } + return -1 +} + +type funcUsage map[string]bool + +func (f funcUsage) funcUsed(name string) bool { + _, ok := f[name] + return ok +} + +func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage { + usage := funcUsage{} + + for _, pkg := range pkgs { + for _, f := range pkg.Files { + ast.Inspect(f, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.CallExpr: + switch t := n.Fun.(type) { + case *ast.Ident: + usage[t.Name] = true + case *ast.SelectorExpr: + usage[t.Sel.Name] = true + } + } + return true + }) + } + } + return usage +} + +func isByteArray(lit *ast.CompositeLit, tInfo *types.Info) bool { + if len(lit.Elts) == 0 { + return false + } + + typ := tInfo.Types[lit.Elts[0]].Type.Underlying() + switch t := typ.(type) { + case *types.Basic: + switch t.Kind() { + case types.Byte: + return true + } + } + return false +} diff --git a/pkg/vm/compiler/codegen.go b/pkg/vm/compiler/codegen.go index 87e95f7db..2b789cce1 100644 --- a/pkg/vm/compiler/codegen.go +++ b/pkg/vm/compiler/codegen.go @@ -89,13 +89,6 @@ func (c *codegen) emitLoadLocal(name string) { emitOpcode(c.prog, vm.Opickitem) } -func (c *codegen) emitLoadStructField(sName, fName string) { - strct := c.scope.loadStruct(sName) - pos := strct.loadField(fName) - emitInt(c.prog, int64(pos)) - emitOpcode(c.prog, vm.Opickitem) -} - func (c *codegen) emitStoreLocal(pos int) { emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.Odup) @@ -111,10 +104,13 @@ func (c *codegen) emitStoreLocal(pos int) { emitOpcode(c.prog, vm.Osetitem) } -func (c *codegen) emitStoreStructField(sName, fName string) { - strct := c.scope.loadStruct(sName) - pos := strct.loadField(fName) - emitInt(c.prog, int64(pos)) +func (c *codegen) emitLoadStructField(i int) { + emitInt(c.prog, int64(i)) + emitOpcode(c.prog, vm.Opickitem) +} + +func (c *codegen) emitStoreStructField(i int) { + emitInt(c.prog, int64(i)) emitOpcode(c.prog, vm.Orot) emitOpcode(c.prog, vm.Osetitem) } @@ -178,11 +174,11 @@ func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) { if decl.Recv != nil { for _, arg := range decl.Recv.List { ident := arg.Names[0] - t, ok := c.typeInfo.Defs[ident].Type().Underlying().(*types.Struct) + // Currently only method receives for struct types is supported. + _, ok := c.typeInfo.Defs[ident].Type().Underlying().(*types.Struct) if !ok { - log.Fatal("method receiver is not a struct type") + log.Fatal("method receives for non-struct types is not yet supported") } - c.scope.newStruct(t) l := c.scope.newLocal(ident.Name) c.emitStoreLocal(l) } @@ -235,12 +231,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch n.Tok { case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN: c.emitLoadLocal(t.Name) - ast.Walk(c, n.Rhs[0]) + ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS c.convertToken(n.Tok) l := c.scope.loadLocal(t.Name) c.emitStoreLocal(l) default: - ast.Walk(c, n.Rhs[0]) + ast.Walk(c, n.Rhs[i]) l := c.scope.loadLocal(t.Name) c.emitStoreLocal(l) } @@ -249,8 +245,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch expr := t.X.(type) { case *ast.Ident: ast.Walk(c, n.Rhs[i]) - c.emitLoadLocal(expr.Name) // load the struct - c.emitStoreStructField(expr.Name, t.Sel.Name) // store the field + typ := c.typeInfo.ObjectOf(expr).Type().Underlying() + if strct, ok := typ.(*types.Struct); ok { + c.emitLoadLocal(expr.Name) // load the struct + i := indexOfStruct(strct, t.Sel.Name) // get the index of the field + c.emitStoreStructField(i) // store the field + } default: log.Fatal("nested selector assigns not supported yet") } @@ -320,6 +320,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { typ = c.typeInfo.ObjectOf(t.Sel).Type().Underlying() default: ln := len(n.Elts) + // ByteArrays need a different approach then normal arrays. + if isByteArray(n, c.typeInfo) { + c.convertByteArray(n) + return nil + } for i := ln - 1; i >= 0; i-- { c.emitLoadConst(c.typeInfo.Types[n.Elts[i]]) } @@ -430,17 +435,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch t := n.X.(type) { case *ast.Ident: 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") + if strct, ok := typ.(*types.Struct); ok { + c.emitLoadLocal(t.Name) // load the struct + i := indexOfStruct(strct, n.Sel.Name) + c.emitLoadStructField(i) // load the field } default: log.Fatal("nested selectors not supported yet") } return nil + + case *ast.UnaryExpr: + // fmt.Println(n) } return c } @@ -454,24 +460,33 @@ func (c *codegen) convertSyscall(name string) { emitOpcode(c.prog, vm.Onop) } +func (c *codegen) convertByteArray(lit *ast.CompositeLit) { + buf := make([]byte, len(lit.Elts)) + for i := 0; i < len(lit.Elts); i++ { + t := c.typeInfo.Types[lit.Elts[i]] + val, _ := constant.Int64Val(t.Value) + buf[i] = byte(val) + } + emitBytes(c.prog, buf) +} + func (c *codegen) convertStruct(lit *ast.CompositeLit) { // Create a new structScope to initialize and store // the positions of its variables. - t, ok := c.typeInfo.TypeOf(lit).Underlying().(*types.Struct) + strct, ok := c.typeInfo.TypeOf(lit).Underlying().(*types.Struct) if !ok { log.Fatalf("the given literal is not of type struct: %v", lit) } - strct := c.scope.newStruct(t) emitOpcode(c.prog, vm.Onop) - emitInt(c.prog, int64(strct.t.NumFields())) + emitInt(c.prog, int64(strct.NumFields())) emitOpcode(c.prog, vm.Onewstruct) emitOpcode(c.prog, vm.Otoaltstack) // We need to locally store all the fields, even if they are not initialized. // We will initialize all fields to their "zero" value. - for i := 0; i < strct.t.NumFields(); i++ { - sField := strct.t.Field(i) + for i := 0; i < strct.NumFields(); i++ { + sField := strct.Field(i) fieldAdded := false // Fields initialized by the program. @@ -481,7 +496,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { if sField.Name() == fieldName { ast.Walk(c, f.Value) - pos := strct.loadField(fieldName) + pos := indexOfStruct(strct, fieldName) c.emitStoreLocal(pos) fieldAdded = true break @@ -490,7 +505,9 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { if fieldAdded { continue } - c.emitLoadConst(strct.typeAndValues[sField.Name()]) + + typeAndVal := typeAndValueForField(sField) + c.emitLoadConst(typeAndVal) c.emitStoreLocal(i) } emitOpcode(c.prog, vm.Ofromaltstack) @@ -552,6 +569,8 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) { log.Fatal("could not find func main. did you forgot to declare it?") } + funUsage := analyzeFuncUsage(info.program.AllPackages) + // Bring all imported functions into scope for _, pkg := range info.program.AllPackages { for _, f := range pkg.Files { @@ -565,11 +584,14 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) { // 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 { + // Dont convert the function if its not used. This will save alot + // of bytecode space. + if n.Name.Name != mainIdent && funUsage.funcUsed(n.Name.Name) { c.convertFuncDecl(f, n) } } diff --git a/pkg/vm/compiler/compiler.go b/pkg/vm/compiler/compiler.go index ea2f31697..786c1ac5b 100644 --- a/pkg/vm/compiler/compiler.go +++ b/pkg/vm/compiler/compiler.go @@ -6,15 +6,12 @@ import ( "fmt" "go/ast" "go/build" - "go/importer" "go/parser" - "go/token" "go/types" "io" "io/ioutil" "log" "os" - "path/filepath" "strings" "text/tabwriter" @@ -73,40 +70,6 @@ type archive struct { typeInfo *types.Info } -func resolveImports(f *ast.File) (map[string]*archive, error) { - packages := map[string]*archive{} - for _, imp := range f.Imports { - path := strings.Replace(imp.Path.Value, `"`, "", 2) - path = filepath.Join(gopath(), "src", path) - fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, path, nil, 0) - if err != nil { - return nil, err - } - - for name, pkg := range pkgs { - file := ast.MergePackageFiles(pkg, 0) - 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), - } - - // Typechecker - _, err = conf.Check("", fset, []*ast.File{file}, typeInfo) - if err != nil { - return nil, err - } - packages[name] = &archive{file, typeInfo} - } - } - return packages, nil -} - // CompileAndSave will compile and save the file to disk. func CompileAndSave(src string, o *Options) error { if !strings.HasSuffix(src, ".go") { diff --git a/pkg/vm/compiler/func_scope.go b/pkg/vm/compiler/func_scope.go index 2362aacb6..7fdc5f843 100644 --- a/pkg/vm/compiler/func_scope.go +++ b/pkg/vm/compiler/func_scope.go @@ -2,8 +2,6 @@ package compiler import ( "go/ast" - "go/types" - "log" ) // A funcScope represents the scope within the function context. @@ -21,9 +19,6 @@ type funcScope struct { // Local variables locals map[string]int - // A mapping of structs positions with their scope - structs map[int]*structScope - // voidCalls are basically functions that return their value // into nothing. The stack has their return value but there // is nothing that consumes it. We need to keep track of @@ -42,7 +37,6 @@ func newFuncScope(decl *ast.FuncDecl, label int) *funcScope { decl: decl, label: label, locals: map[string]int{}, - structs: map[int]*structScope{}, voidCalls: map[*ast.CallExpr]bool{}, i: -1, } @@ -77,7 +71,9 @@ func (c *funcScope) stackSize() int64 { size := 0 ast.Inspect(c.decl, func(n ast.Node) bool { switch n := n.(type) { - case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt: + case *ast.AssignStmt: + size += len(n.Rhs) + case *ast.ReturnStmt, *ast.IfStmt: size++ // This handles the inline GenDecl like "var x = 2" case *ast.GenDecl: @@ -99,21 +95,6 @@ func (c *funcScope) stackSize() int64 { return int64(size + numArgs + len(c.voidCalls)) } -func (c *funcScope) newStruct(t *types.Struct) *structScope { - strct := newStructScope(t) - c.structs[len(c.locals)] = strct - return strct -} - -func (c *funcScope) loadStruct(name string) *structScope { - l := c.loadLocal(name) - strct, ok := c.structs[l] - if !ok { - log.Fatalf("could not resolve struct %s", name) - } - return strct -} - // newLocal creates a new local variable into the scope of the function. func (c *funcScope) newLocal(name string) int { c.i++ diff --git a/pkg/vm/compiler/struct_scope.go b/pkg/vm/compiler/struct_scope.go deleted file mode 100644 index 992a559d9..000000000 --- a/pkg/vm/compiler/struct_scope.go +++ /dev/null @@ -1,89 +0,0 @@ -package compiler - -import ( - "go/constant" - "go/types" - "log" -) - -// A structScope holds the positions for it's fields. Struct fields have different -// positions then local variables in any scope. -type structScope struct { - // A pointer to the underlying type. - t *types.Struct - - // A mapping of fieldnames identifier and its position. - fields map[string]int - - // A mapping of fieldnames and with type and value. - // This will be populated in "initFields" to initialize all - // structs fields to their zero value. - // strings: "" (just a pushf 0x00) - // int: 0 - // bool: false - typeAndValues map[string]types.TypeAndValue -} - -// newStructScope will create a new structScope with all fields initialized. -func newStructScope(t *types.Struct) *structScope { - s := &structScope{ - fields: map[string]int{}, - typeAndValues: make(map[string]types.TypeAndValue, t.NumFields()), - t: t, - } - s.initFields() - return s -} - -func (s *structScope) initFields() { - var tv types.TypeAndValue - for i := 0; i < s.t.NumFields(); i++ { - f := s.t.Field(i) - s.newField(f.Name()) - - switch t := f.Type().(type) { - case *types.Basic: - switch t.Kind() { - case types.Int: - tv = types.TypeAndValue{ - Type: t, - Value: constant.MakeInt64(0), - } - case types.String: - tv = types.TypeAndValue{ - Type: t, - Value: constant.MakeString(""), - } - case types.Bool, types.UntypedBool: - tv = types.TypeAndValue{ - Type: t, - Value: constant.MakeBool(false), - } - default: - log.Fatalf("could not initialize struct field %s to zero, type: %s", f.Name(), t) - } - } - s.typeAndValues[f.Name()] = tv - } -} - -func (s *structScope) newField(name string) int { - i := len(s.fields) - s.fields[name] = i - return i -} - -func (s *structScope) loadField(name string) int { - i, ok := s.fields[name] - if !ok { - log.Fatalf("could not resolve field %s for struct %v", name, s) - } - return i -} - -func (s *structScope) initialize(t *types.Struct) { - s.t = t - for i := 0; i < t.NumFields(); i++ { - s.newField(t.Field(i).Name()) - } -} diff --git a/pkg/vm/compiler/tests/assign_test.go b/pkg/vm/compiler/tests/assign_test.go index 89984400e..bc9bf1d8c 100644 --- a/pkg/vm/compiler/tests/assign_test.go +++ b/pkg/vm/compiler/tests/assign_test.go @@ -112,4 +112,15 @@ var assignTestCases = []testCase{ `, "52c56b546c766b00527ac46203006c766b00c3616c7566", }, + { + "multi assign", + ` + package foo + func Main() int { + x, y := 1, 2 + return x + y + } + `, + "53c56b516c766b00527ac4526c766b51527ac46203006c766b00c36c766b51c393616c7566", + }, } diff --git a/pkg/vm/compiler/tests/bool_test.go b/pkg/vm/compiler/tests/bool_test.go index bee5425aa..5a59c9d16 100644 --- a/pkg/vm/compiler/tests/bool_test.go +++ b/pkg/vm/compiler/tests/bool_test.go @@ -12,4 +12,46 @@ var boolTestCases = []testCase{ `, "52c56b516c766b00527ac46203006c766b00c3616c7566", }, + { + "bool compare", + ` + package foo + func Main() int { + x := true + if x { + return 10 + } + return 0 + } + `, + "54c56b516c766b00527ac46c766b00c3640b006203005a616c756662030000616c7566", + }, + { + "bool compare verbose", + ` + package foo + func Main() int { + x := true + if x == true { + return 10 + } + return 0 + } + `, + "54c56b516c766b00527ac46c766b00c3519c640b006203005a616c756662030000616c7566", + }, + // { + // "bool invert (unary expr)", + // ` + // package foo + // func Main() int { + // x := true + // if !x { + // return 10 + // } + // return 0 + // } + // `, + // "54c56b516c766b00527ac46c766b00c3630b006203005a616c756662030000616c7566", + // }, } diff --git a/pkg/vm/compiler/tests/compiler_test.go b/pkg/vm/compiler/tests/compiler_test.go index d77e166f7..8ea758952 100644 --- a/pkg/vm/compiler/tests/compiler_test.go +++ b/pkg/vm/compiler/tests/compiler_test.go @@ -37,6 +37,7 @@ func TestAllCases(t *testing.T) { // Blockchain specific testCases = append(testCases, storageTestCases...) + testCases = append(testCases, runtimeTestCases...) for _, tc := range testCases { b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{}) @@ -50,6 +51,7 @@ func TestAllCases(t *testing.T) { } if bytes.Compare(b, expectedResult) != 0 { + fmt.Println(tc.src) t.Log(hex.EncodeToString(b)) dumpOpCodeSideBySide(b, expectedResult) t.Fatalf("compiling %s failed", tc.name) diff --git a/pkg/vm/compiler/tests/if_statement_test.go b/pkg/vm/compiler/tests/if_statement_test.go index 3ed9e44bd..0352eda86 100644 --- a/pkg/vm/compiler/tests/if_statement_test.go +++ b/pkg/vm/compiler/tests/if_statement_test.go @@ -71,4 +71,21 @@ var ifStatementTestCases = []testCase{ `, "54c56b5a6c766b00527ac46c766b00c35aa2630e006c766b00c30114a1640b0062030051616c756662030000616c7566", }, + { + "nested if statements", + ` + package testcase + func Main() int { + x := 10 + if x > 10 { + if x < 20 { + return 1 + } + return 2 + } + return 0 + } + `, + "56c56b5a6c766b00527ac46c766b00c35aa0641e006c766b00c301149f640b0062030051616c756662030052616c756662030000616c7566", + }, } diff --git a/pkg/vm/compiler/tests/runtime_test.go b/pkg/vm/compiler/tests/runtime_test.go new file mode 100644 index 000000000..b1cadced7 --- /dev/null +++ b/pkg/vm/compiler/tests/runtime_test.go @@ -0,0 +1,99 @@ +package compiler + +var runtimeTestCases = []testCase{ + { + "Notify test", + ` + package foo + + import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + + func Main() bool { + runtime.Notify("hello") + return true + } + `, + "52c56b0568656c6c6f6168124e656f2e52756e74696d652e4e6f746966796162030051616c756652c56b6c766b00527ac462030000616c7566", + }, + { + "Log test", + ` + package foo + + import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + + func Main() bool { + runtime.Log("hello you there!") + return true + } + `, + "52c56b1068656c6c6f20796f752074686572652161680f4e656f2e52756e74696d652e4c6f676162030051616c756652c56b6c766b00527ac462030000616c7566", + }, + { + "GetTime test", + ` + package foo + + import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + + func Main() int { + t := runtime.GetTime() + return t + } + `, + "52c56b6168134e656f2e52756e74696d652e47657454696d65616c766b00527ac46203006c766b00c3616c756651c56b62030000616c7566", + }, + { + "GetTrigger test", + ` + package foo + + import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + + func Main() int { + trigger := runtime.GetTrigger() + if trigger == runtime.Application() { + return 1 + } + if trigger == runtime.Verification() { + return 2 + } + return 0 + } + `, + "56c56b6168164e656f2e52756e74696d652e47657454726967676572616c766b00527ac46c766b00c361652c009c640b0062030051616c75666c766b00c3616523009c640b0062030052616c756662030000616c756651c56b6203000110616c756651c56b6203000100616c756651c56b62030000616c7566", + }, + { + "check witness", + ` + package foo + + import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + + func Main() int { + owner := []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5} + isOwner := runtime.CheckWitness(owner) + if isOwner { + return 1 + } + return 0 + } + `, + "55c56b14af12a8687b14948bc4a008128a550a63695bc1a56c766b00527ac46c766b00c36168184e656f2e52756e74696d652e436865636b5769746e657373616c766b51527ac46c766b51c3640b0062030051616c756662030000616c756652c56b6c766b00527ac462030000616c7566", + }, + { + "getCurrentBlock", + ` + package foo + + import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime" + + func Main() int { + block := runtime.GetCurrentBlock() + runtime.Notify(block) + return 0 + } + `, + "53c56b61681b4e656f2e52756e74696d652e47657443757272656e74426c6f636b616c766b00527ac46c766b00c36168124e656f2e52756e74696d652e4e6f746966796162030000616c756651c56b62030000616c756652c56b6c766b00527ac462030000616c7566", + }, +} diff --git a/pkg/vm/compiler/tests/storage_test.go b/pkg/vm/compiler/tests/storage_test.go index 4b395e5ca..a9de43359 100644 --- a/pkg/vm/compiler/tests/storage_test.go +++ b/pkg/vm/compiler/tests/storage_test.go @@ -15,6 +15,6 @@ var storageTestCases = []testCase{ return amount } `, - "54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566", + "54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566", }, } diff --git a/pkg/vm/compiler/tests/struct_test.go b/pkg/vm/compiler/tests/struct_test.go index 03a3047f8..9767aab0d 100644 --- a/pkg/vm/compiler/tests/struct_test.go +++ b/pkg/vm/compiler/tests/struct_test.go @@ -200,4 +200,29 @@ var structTestCases = []testCase{ `, "51c56b62030061650700616c756651c56b6203006154c66b516c766b00527ac4526c766b51527ac40568656c6c6f6c766b52527ac4006c766b53527ac46c616c7566", }, + { + "pass struct as argument", + ` + package foo + + type Bar struct { + amount int + } + + func addToAmount(x int, bar Bar) int { + bar.amount = bar.amount + x + return bar.amount + } + + func Main() int { + b := Bar{ + amount: 10, + } + + x := addToAmount(4, b) + return x + } + `, + "53c56b6151c66b5a6c766b00527ac46c6c766b00527ac4546c766b00c37c616516006c766b51527ac46203006c766b51c3616c756654c56b6c766b00527ac46c766b51527ac46c766b51c300c36c766b00c3936c766b51c3007bc46203006c766b51c300c3616c7566", + }, } diff --git a/pkg/vm/syscall.go b/pkg/vm/syscall.go index 2506b4f68..e5b1bab67 100644 --- a/pkg/vm/syscall.go +++ b/pkg/vm/syscall.go @@ -15,6 +15,6 @@ var Syscalls = map[string]string{ "CheckWitness": "Neo.Runtime.CheckWitness", "GetCurrentBlock": "Neo.Runtime.GetCurrentBlock", "GetTime": "Neo.Runtime.GetTime", - "Notify": "Neo.runtime.Notify", + "Notify": "Neo.Runtime.Notify", "Log": "Neo.Runtime.Log", }