mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-01-09 11:19:06 +00:00
Optimisations and API changes for smart contracts (#67)
* support VM to pass method and arguments to a script. * added support for type assertions in smartcontracts. * added native vm support for print. * moved VM API packages to vm -> API * reverted the native Print opcode in favor of runtime.Log * added support for registering custom interop hooks in the VM. * Updated README * Updated compiler with @OPTIMIZE tags * Moved more tests to VM package. * optimized and refactored compiler and vm API * updated README with new smartcontract apis * bumped version
This commit is contained in:
parent
b2021c126e
commit
4bd5b2812e
29 changed files with 655 additions and 494 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.39.2
|
||||
0.40.0
|
||||
|
|
|
@ -111,6 +111,59 @@ NEO-GO-VM > run
|
|||
|
||||
```
|
||||
|
||||
### Running programs with arguments
|
||||
You can invoke smart contracts with arguments. Take the following ***roll the dice*** smartcontract as example.
|
||||
|
||||
```
|
||||
package rollthedice
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/api/runtime"
|
||||
|
||||
func Main(method string, args []interface{}) int {
|
||||
if method == "rollDice" {
|
||||
// args parameter is always of type []interface, hence we need to
|
||||
// cast it to an int.
|
||||
rollDice(args[0].(int))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func rollDice(number int) {
|
||||
if number == 0 {
|
||||
runtime.Log("you rolled 0, better luck next time!")
|
||||
}
|
||||
if number == 1 {
|
||||
runtime.Log("you rolled 1, still better then 0!")
|
||||
}
|
||||
if number == 2 {
|
||||
runtime.Log("you rolled 2, coming closer..")
|
||||
}
|
||||
if number == 3 {
|
||||
runtime.Log("Sweet you rolled 3. This dice has only 3 sides o_O")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To invoke this contract we need to specify both the method and the arguments.
|
||||
|
||||
The first parameter (called method or operation) is always of type string. Notice that arguments can have different types, to make the VM aware of the type we need to specify it when calling `run`:
|
||||
|
||||
```
|
||||
NEO-GO-VM > run rollDice int:1
|
||||
```
|
||||
|
||||
> The method is always of type string, hence we don't need to specify the type.
|
||||
|
||||
To add more then 1 argument:
|
||||
|
||||
```
|
||||
NEO-GO-VM > run someMethod int:1 int:2 string:foo string:bar
|
||||
```
|
||||
|
||||
Current supported types:
|
||||
- `int (int:1 int:100)`
|
||||
- `string (string:foo string:this is a string)`
|
||||
|
||||
### Debugging
|
||||
The `neo-go-vm` provides a debugger to inspect your program in-depth.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package runtime
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/types"
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/api/types"
|
||||
|
||||
// CheckWitness verifies if the invoker is the owner of the contract.
|
||||
func CheckWitness(hash []byte) bool {
|
||||
|
@ -21,9 +21,7 @@ func Notify(arg interface{}) int {
|
|||
}
|
||||
|
||||
// Log intructs the VM to log the given message.
|
||||
func Log(message string) int {
|
||||
return 0
|
||||
}
|
||||
func Log(message string) {}
|
||||
|
||||
// Application returns the application trigger type.
|
||||
func Application() byte {
|
13
pkg/vm/api/storage/storage.go
Normal file
13
pkg/vm/api/storage/storage.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package storage
|
||||
|
||||
// Context ..
|
||||
func Context() interface{} { return 0 }
|
||||
|
||||
// Put stores a value in to the storage.
|
||||
func Put(ctx interface{}, key string, value interface{}) {}
|
||||
|
||||
// Get returns the value from the storage.
|
||||
func Get(ctx interface{}, key string) interface{} { return 0 }
|
||||
|
||||
// Delete removes a stored key value pair.
|
||||
func Delete(ctx interface{}, key string) {}
|
8
pkg/vm/api/util/util.go
Normal file
8
pkg/vm/api/util/util.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package util
|
||||
|
||||
// Package util provides utility functions that can be used in smart contracts.
|
||||
// These functions are just signatures and provide not internal logic.
|
||||
// Only the compiler knows how to convert them to bytecode.
|
||||
|
||||
// Print is a VM helper function to print/log data.
|
||||
func Print(v interface{}) {}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -51,7 +52,7 @@ type VMCLI struct {
|
|||
// New returns a new VMCLI object.
|
||||
func New() *VMCLI {
|
||||
return &VMCLI{
|
||||
vm: vm.New(nil, 0),
|
||||
vm: vm.New(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +62,7 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
|
|||
fmt.Printf("unknown command (%s)\n", cmd)
|
||||
return
|
||||
}
|
||||
if len(args) < com.args {
|
||||
if (len(args) < com.args || len(args) > com.args) && cmd != "run" {
|
||||
fmt.Printf("command (%s) takes at least %d arguments\n", cmd, com.args)
|
||||
return
|
||||
}
|
||||
|
@ -125,7 +126,31 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
|
|||
c.vm.Load(b)
|
||||
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
|
||||
|
||||
case "run", "cont":
|
||||
case "run":
|
||||
var (
|
||||
method []byte
|
||||
params []vm.StackItem
|
||||
err error
|
||||
start int
|
||||
)
|
||||
|
||||
if len(args) == 0 {
|
||||
c.vm.Run()
|
||||
} else {
|
||||
if isMethodArg(args[0]) {
|
||||
method = []byte(args[0])
|
||||
start = 1
|
||||
}
|
||||
params, err = parseArgs(args[start:])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.vm.LoadArgs(method, params)
|
||||
c.vm.Run()
|
||||
|
||||
case "cont":
|
||||
c.vm.Run()
|
||||
|
||||
case "step":
|
||||
|
@ -172,6 +197,36 @@ func (c *VMCLI) Run() error {
|
|||
}
|
||||
}
|
||||
|
||||
func isMethodArg(s string) bool {
|
||||
return len(strings.Split(s, ":")) == 1
|
||||
}
|
||||
|
||||
func parseArgs(args []string) ([]vm.StackItem, error) {
|
||||
items := make([]vm.StackItem, len(args))
|
||||
for i, arg := range args {
|
||||
typeAndVal := strings.Split(arg, ":")
|
||||
if len(typeAndVal) < 2 {
|
||||
return nil, errors.New("arguments need to be specified as <typ:val>")
|
||||
}
|
||||
|
||||
typ := typeAndVal[0]
|
||||
value := typeAndVal[1]
|
||||
|
||||
switch typ {
|
||||
case "int":
|
||||
val, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items[i] = vm.NewBigIntegerItem(val)
|
||||
case "string":
|
||||
items[i] = vm.NewByteArrayItem([]byte(value))
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
names := make([]string, len(commands))
|
||||
i := 0
|
||||
|
|
|
@ -103,7 +103,7 @@ Will output something like:
|
|||
```Golang
|
||||
package mycontract
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime"
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/api/runtime"
|
||||
|
||||
var owner = []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5}
|
||||
|
||||
|
@ -125,8 +125,8 @@ func Main() bool {
|
|||
package mytoken
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/smartcontract/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/api/runtime"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/api/storage"
|
||||
)
|
||||
|
||||
var owner = []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5}
|
||||
|
@ -139,7 +139,7 @@ type Token struct {
|
|||
}
|
||||
|
||||
func (t Token) AddToCirculation(amount int) bool {
|
||||
ctx := storage.GetContext()
|
||||
ctx := storage.Context()
|
||||
inCirc := storage.GetInt(ctx, "in_circ")
|
||||
inCirc += amount
|
||||
storage.Put(ctx, "in_circ", inCirc)
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
var (
|
||||
// Go language builtin functions.
|
||||
builtinFuncs = []string{"len", "append"}
|
||||
|
||||
// VM system calls that have no return value.
|
||||
noRetSyscalls = []string{
|
||||
"Notify", "Log", "Put", "Register", "Delete",
|
||||
|
@ -128,6 +127,18 @@ func (f funcUsage) funcUsed(name string) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// hasReturnStmt look if the given FuncDecl has a return statement.
|
||||
func hasReturnStmt(decl *ast.FuncDecl) (b bool) {
|
||||
ast.Inspect(decl, func(node ast.Node) bool {
|
||||
if _, ok := node.(*ast.ReturnStmt); ok {
|
||||
b = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
|
||||
usage := funcUsage{}
|
||||
|
||||
|
|
|
@ -118,19 +118,6 @@ func (c *codegen) emitStoreStructField(i int) {
|
|||
emitOpcode(c.prog, vm.Osetitem)
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -160,7 +147,7 @@ func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) {
|
|||
}
|
||||
|
||||
c.scope = f
|
||||
ast.Inspect(decl, c.scope.analyzeVoidCalls)
|
||||
ast.Inspect(decl, c.scope.analyzeVoidCalls) // @OPTIMIZE
|
||||
|
||||
// All globals copied into the scope of the function need to be added
|
||||
// to the stack size of the function.
|
||||
|
@ -193,17 +180,19 @@ func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) {
|
|||
l := c.scope.newLocal(name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Load in all the global variables in to the scope of the function.
|
||||
// This is not necessary for syscalls.
|
||||
if !isSyscall(f.name) {
|
||||
c.convertGlobals(file)
|
||||
ast.Walk(c, decl.Body)
|
||||
}
|
||||
|
||||
ast.Walk(c, decl.Body)
|
||||
|
||||
// If this function returs the void (no return stmt) we will cleanup its junk on the stack.
|
||||
if !hasReturnStmt(decl) {
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odrop)
|
||||
emitOpcode(c.prog, vm.Oret)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,6 +257,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
// @OPTIMIZE: We could skip these 3 instructions for each return statement.
|
||||
// To be backwards compatible we will put them them in.
|
||||
// See issue #65 (https://github.com/CityOfZion/neo-go/issues/65)
|
||||
l := c.newLabel()
|
||||
emitJmp(c.prog, vm.Ojmp, int16(l))
|
||||
c.setLabel(l)
|
||||
|
@ -276,9 +266,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
ast.Walk(c, n.Results[0])
|
||||
}
|
||||
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitOpcode(c.prog, vm.Onop) // @OPTIMIZE
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odrop)
|
||||
emitOpcode(c.prog, vm.Odrop) // Cleanup the stack.
|
||||
emitOpcode(c.prog, vm.Oret)
|
||||
return nil
|
||||
|
||||
|
@ -439,8 +429,8 @@ 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.scope.voidCalls[n]; ok && !isNoRetSyscall(f.name) {
|
||||
// (cleanup) the top stack item. It's not a void but you get the point \o/.
|
||||
if _, ok := c.scope.voidCalls[n]; ok {
|
||||
emitOpcode(c.prog, vm.Odrop)
|
||||
}
|
||||
return nil
|
||||
|
@ -518,6 +508,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
c.setLabel(fend)
|
||||
|
||||
return nil
|
||||
|
||||
// We dont really care about assertions for the core logic.
|
||||
// The only thing we need is to please the compiler type checking.
|
||||
// For this to work properly, we only need to walk the expression
|
||||
// not the assertion type.
|
||||
case *ast.TypeAssertExpr:
|
||||
ast.Walk(c, n.X)
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
@ -528,7 +526,7 @@ func (c *codegen) convertSyscall(name string) {
|
|||
log.Fatalf("unknown VM syscall api: %s", name)
|
||||
}
|
||||
emitSyscall(c.prog, api)
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitOpcode(c.prog, vm.Onop) // @OPTIMIZE
|
||||
}
|
||||
|
||||
func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) {
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
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",
|
||||
},
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package compiler
|
||||
|
||||
var forTestCases = []testCase{
|
||||
{
|
||||
"classic for loop",
|
||||
`
|
||||
package foofor
|
||||
func Main() int {
|
||||
y := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
y += 1;
|
||||
}
|
||||
return y
|
||||
}
|
||||
`,
|
||||
"56c56b006a00527ac4006a53527ac4005a7c6548006a52527ac46a52c3c06a54527ac4616a53c36a54c39f6426006a52c36a53c3c36a51527ac46a53c351936a53527ac46a00c351936a00527ac462d5ff6161616a00c36c75665ec56b6a00527ac46a51527ac46a51c36a00c3946a52527ac46a52c3c56a53527ac4006a54527ac46a00c36a55527ac461616a00c36a51c39f6433006a54c36a55c3936a56527ac46a56c36a53c36a54c37bc46a54c351936a54527ac46a55c36a54c3936a00527ac462c8ff6161616a53c36c7566",
|
||||
},
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
package compiler
|
||||
|
||||
var functionCallTestCases = []testCase{
|
||||
{
|
||||
"simple function call",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
y := getSomeInteger()
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
x := 10
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b5a6c766b00527ac461651c006c766b51527ac46203006c766b00c36c766b51c393616c756652c56b5a6c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"test function call with no assign",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
getSomeInteger()
|
||||
getSomeInteger()
|
||||
return 0
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"53c56b616511007561650c007562030000616c756651c56b62030000616c7566",
|
||||
},
|
||||
{
|
||||
"multiple function calls",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
y := getSomeInteger()
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
x := 10
|
||||
y := getSomeOtherInt()
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeOtherInt() int {
|
||||
x := 8
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b5a6c766b00527ac461651c006c766b51527ac46203006c766b00c36c766b51c393616c756653c56b5a6c766b00527ac461651c006c766b51527ac46203006c766b00c36c766b51c393616c756652c56b586c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"function call with arguments",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
y := getSomeInteger(x)
|
||||
return y
|
||||
}
|
||||
|
||||
func getSomeInteger(x int) int {
|
||||
y := 8
|
||||
return x + y
|
||||
}
|
||||
`,
|
||||
"53c56b5a6c766b00527ac46c766b00c3616516006c766b51527ac46203006c766b51c3616c756653c56b6c766b00527ac4586c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
{
|
||||
"function call with arguments of interface type",
|
||||
`
|
||||
package testcase
|
||||
func Main() interface{} {
|
||||
x := getSomeInteger(10)
|
||||
return x
|
||||
}
|
||||
|
||||
func getSomeInteger(x interface{}) interface{} {
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b5a616516006c766b00527ac46203006c766b00c3616c756652c56b6c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"function call with multiple arguments",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := addIntegers(2, 4)
|
||||
return x
|
||||
}
|
||||
|
||||
func addIntegers(x int, y int) int {
|
||||
return x + y
|
||||
}
|
||||
`,
|
||||
"52c56b52547c616516006c766b00527ac46203006c766b00c3616c756653c56b6c766b00527ac46c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
{
|
||||
"test Main arguments",
|
||||
`
|
||||
package foo
|
||||
func Main(operation string, args []interface{}) int {
|
||||
if operation == "mintTokens" {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"55c56b6c766b00527ac46c766b51527ac46c766b00c30a6d696e74546f6b656e739c640b0062030051616c756662030000616c7566",
|
||||
},
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package compiler
|
||||
|
||||
var runtimeTestCases = []testCase{
|
||||
{
|
||||
"Notify test",
|
||||
`
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime"
|
||||
|
||||
func Main() bool {
|
||||
runtime.Notify("hello")
|
||||
return true
|
||||
}
|
||||
`,
|
||||
"52c56b0568656c6c6f6168124e656f2e52756e74696d652e4e6f746966796162030051616c756652c56b6c766b00527ac462030000616c7566",
|
||||
},
|
||||
{
|
||||
"Log test",
|
||||
`
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime"
|
||||
|
||||
func Main() bool {
|
||||
runtime.Log("hello you there!")
|
||||
return true
|
||||
}
|
||||
`,
|
||||
"52c56b1068656c6c6f20796f752074686572652161680f4e656f2e52756e74696d652e4c6f676162030051616c756652c56b6c766b00527ac462030000616c7566",
|
||||
},
|
||||
{
|
||||
"GetTime test",
|
||||
`
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime"
|
||||
|
||||
func Main() int {
|
||||
t := runtime.GetTime()
|
||||
return t
|
||||
}
|
||||
`,
|
||||
"52c56b6168134e656f2e52756e74696d652e47657454696d65616c766b00527ac46203006c766b00c3616c756651c56b62030000616c7566",
|
||||
},
|
||||
{
|
||||
"GetTrigger test",
|
||||
`
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/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/vm/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/vm/smartcontract/runtime"
|
||||
|
||||
func Main() int {
|
||||
block := runtime.GetCurrentBlock()
|
||||
runtime.Notify(block)
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"53c56b61681b4e656f2e52756e74696d652e47657443757272656e74426c6f636b616c766b00527ac46c766b00c36168124e656f2e52756e74696d652e4e6f746966796162030000616c756651c56b62030000616c756652c56b6c766b00527ac462030000616c7566",
|
||||
},
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package compiler
|
||||
|
||||
var storageTestCases = []testCase{
|
||||
{
|
||||
"interop storage test",
|
||||
`
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/storage"
|
||||
|
||||
func Main() int {
|
||||
ctx := storage.GetContext()
|
||||
storage.Put(ctx, "amount", 1000)
|
||||
amount := storage.GetInt(ctx, "amount")
|
||||
return amount
|
||||
}
|
||||
`,
|
||||
"54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566",
|
||||
},
|
||||
}
|
|
@ -1,32 +1,22 @@
|
|||
package vm
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// InteropFunc allows to hook into the VM.
|
||||
type InteropFunc func(vm *VM) error
|
||||
|
||||
// InteropService
|
||||
type InteropService struct {
|
||||
mapping map[string]InteropFunc
|
||||
// runtimeLog will handle the syscall "Neo.Runtime.Log" for printing and logging stuff.
|
||||
func runtimeLog(vm *VM) error {
|
||||
item := vm.Estack().Pop()
|
||||
fmt.Printf("NEO-GO-VM (log) > %s\n", item.value.Value())
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewInteropService returns a new InteropService object.
|
||||
func NewInteropService() *InteropService {
|
||||
return &InteropService{
|
||||
mapping: map[string]InteropFunc{},
|
||||
}
|
||||
}
|
||||
|
||||
// Register any API to the interop service.
|
||||
func (i *InteropService) Register(api string, fun InteropFunc) {
|
||||
i.mapping[api] = fun
|
||||
}
|
||||
|
||||
// Call will invoke the service mapped to the given api.
|
||||
func (i *InteropService) Call(api []byte, vm *VM) error {
|
||||
fun, ok := i.mapping[string(api)]
|
||||
if !ok {
|
||||
return fmt.Errorf("api (%s) not in interop mapping", api)
|
||||
}
|
||||
return fun(vm)
|
||||
// runtimeNotify will handle the syscall "Neo.Runtime.Notify" for printing and logging stuff.
|
||||
func runtimeNotify(vm *VM) error {
|
||||
item := vm.Estack().Pop()
|
||||
fmt.Printf("NEO-GO-VM (notify) > %s\n", item.value.Value())
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package vm
|
||||
|
||||
//go:generate stringer -type=Opcode
|
||||
|
||||
// Opcode is an single operational instruction for the GO NEO virtual machine.
|
||||
type Opcode byte
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Code generated by "stringer -type=Opcode ./pkg/vm/compiler"; DO NOT EDIT.
|
||||
// Code generated by "stringer -type=Opcode"; DO NOT EDIT.
|
||||
|
||||
package vm
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _Opcode_name = "Opush0Opushbytes1Opushbytes75Opushdata1Opushdata2Opushdata4Opushm1Opush1Opush2Opush3Opush4Opush5Opush6Opush7Opush8Opush9Opush10Opush11Opush12Opush13Opush14Opush15Opush16OnopOjmpOjmpifOjmpifnotOcallOretOpcallOsyscallOtailcallOdupfromaltstackOtoaltstackOfromaltstackOxdropOxswapOxtuckOdepthOdropOdupOnipOoverOpickOrollOrotOswapOtuckOcatOsubstrOleftOrightOsizeOinvertOandOorOxorOequalOincOdecOsignOnegateOabsOnotOnzOaddOsubOmulOdivOmodOshlOshrOboolandOboolorOnumequalOnumnotequalOltOgtOlteOgteOminOmaxOwithinOsha1Osha256Ohash160Ohash256OchecksigOcheckmultisigOarraysizeOpackOunpackOpickitemOsetitemOnewarrayOnewstructOappendOreverseOremoveOthrowOthrowifnot"
|
||||
const _Opcode_name = "Opush0Opushbytes1Opushbytes75Opushdata1Opushdata2Opushdata4Opushm1Opush1Opush2Opush3Opush4Opush5Opush6Opush7Opush8Opush9Opush10Opush11Opush12Opush13Opush14Opush15Opush16OnopOjmpOjmpifOjmpifnotOcallOretOappcallOsyscallOtailcallOdupfromaltstackOtoaltstackOfromaltstackOxdropOxswapOxtuckOdepthOdropOdupOnipOoverOpickOrollOrotOswapOtuckOcatOsubstrOleftOrightOsizeOinvertOandOorOxorOequalOincOdecOsignOnegateOabsOnotOnzOaddOsubOmulOdivOmodOshlOshrOboolandOboolorOnumequalOnumnotequalOltOgtOlteOgteOminOmaxOwithinOsha1Osha256Ohash160Ohash256OchecksigOcheckmultisigOarraysizeOpackOunpackOpickitemOsetitemOnewarrayOnewstructOappendOreverseOremoveOthrowOthrowifnot"
|
||||
|
||||
var _Opcode_map = map[Opcode]string{
|
||||
0: _Opcode_name[0:6],
|
||||
|
@ -36,78 +36,78 @@ var _Opcode_map = map[Opcode]string{
|
|||
100: _Opcode_name[183:192],
|
||||
101: _Opcode_name[192:197],
|
||||
102: _Opcode_name[197:201],
|
||||
103: _Opcode_name[201:207],
|
||||
104: _Opcode_name[207:215],
|
||||
105: _Opcode_name[215:224],
|
||||
106: _Opcode_name[224:240],
|
||||
107: _Opcode_name[240:251],
|
||||
108: _Opcode_name[251:264],
|
||||
109: _Opcode_name[264:270],
|
||||
114: _Opcode_name[270:276],
|
||||
115: _Opcode_name[276:282],
|
||||
116: _Opcode_name[282:288],
|
||||
117: _Opcode_name[288:293],
|
||||
118: _Opcode_name[293:297],
|
||||
119: _Opcode_name[297:301],
|
||||
120: _Opcode_name[301:306],
|
||||
121: _Opcode_name[306:311],
|
||||
122: _Opcode_name[311:316],
|
||||
123: _Opcode_name[316:320],
|
||||
124: _Opcode_name[320:325],
|
||||
125: _Opcode_name[325:330],
|
||||
126: _Opcode_name[330:334],
|
||||
127: _Opcode_name[334:341],
|
||||
128: _Opcode_name[341:346],
|
||||
129: _Opcode_name[346:352],
|
||||
130: _Opcode_name[352:357],
|
||||
131: _Opcode_name[357:364],
|
||||
132: _Opcode_name[364:368],
|
||||
133: _Opcode_name[368:371],
|
||||
134: _Opcode_name[371:375],
|
||||
135: _Opcode_name[375:381],
|
||||
139: _Opcode_name[381:385],
|
||||
140: _Opcode_name[385:389],
|
||||
141: _Opcode_name[389:394],
|
||||
143: _Opcode_name[394:401],
|
||||
144: _Opcode_name[401:405],
|
||||
145: _Opcode_name[405:409],
|
||||
146: _Opcode_name[409:412],
|
||||
147: _Opcode_name[412:416],
|
||||
148: _Opcode_name[416:420],
|
||||
149: _Opcode_name[420:424],
|
||||
150: _Opcode_name[424:428],
|
||||
151: _Opcode_name[428:432],
|
||||
152: _Opcode_name[432:436],
|
||||
153: _Opcode_name[436:440],
|
||||
154: _Opcode_name[440:448],
|
||||
155: _Opcode_name[448:455],
|
||||
156: _Opcode_name[455:464],
|
||||
158: _Opcode_name[464:476],
|
||||
159: _Opcode_name[476:479],
|
||||
160: _Opcode_name[479:482],
|
||||
161: _Opcode_name[482:486],
|
||||
162: _Opcode_name[486:490],
|
||||
163: _Opcode_name[490:494],
|
||||
164: _Opcode_name[494:498],
|
||||
165: _Opcode_name[498:505],
|
||||
167: _Opcode_name[505:510],
|
||||
168: _Opcode_name[510:517],
|
||||
169: _Opcode_name[517:525],
|
||||
170: _Opcode_name[525:533],
|
||||
172: _Opcode_name[533:542],
|
||||
174: _Opcode_name[542:556],
|
||||
192: _Opcode_name[556:566],
|
||||
193: _Opcode_name[566:571],
|
||||
194: _Opcode_name[571:578],
|
||||
195: _Opcode_name[578:587],
|
||||
196: _Opcode_name[587:595],
|
||||
197: _Opcode_name[595:604],
|
||||
198: _Opcode_name[604:614],
|
||||
200: _Opcode_name[614:621],
|
||||
201: _Opcode_name[621:629],
|
||||
202: _Opcode_name[629:636],
|
||||
240: _Opcode_name[636:642],
|
||||
241: _Opcode_name[642:653],
|
||||
103: _Opcode_name[201:209],
|
||||
104: _Opcode_name[209:217],
|
||||
105: _Opcode_name[217:226],
|
||||
106: _Opcode_name[226:242],
|
||||
107: _Opcode_name[242:253],
|
||||
108: _Opcode_name[253:266],
|
||||
109: _Opcode_name[266:272],
|
||||
114: _Opcode_name[272:278],
|
||||
115: _Opcode_name[278:284],
|
||||
116: _Opcode_name[284:290],
|
||||
117: _Opcode_name[290:295],
|
||||
118: _Opcode_name[295:299],
|
||||
119: _Opcode_name[299:303],
|
||||
120: _Opcode_name[303:308],
|
||||
121: _Opcode_name[308:313],
|
||||
122: _Opcode_name[313:318],
|
||||
123: _Opcode_name[318:322],
|
||||
124: _Opcode_name[322:327],
|
||||
125: _Opcode_name[327:332],
|
||||
126: _Opcode_name[332:336],
|
||||
127: _Opcode_name[336:343],
|
||||
128: _Opcode_name[343:348],
|
||||
129: _Opcode_name[348:354],
|
||||
130: _Opcode_name[354:359],
|
||||
131: _Opcode_name[359:366],
|
||||
132: _Opcode_name[366:370],
|
||||
133: _Opcode_name[370:373],
|
||||
134: _Opcode_name[373:377],
|
||||
135: _Opcode_name[377:383],
|
||||
139: _Opcode_name[383:387],
|
||||
140: _Opcode_name[387:391],
|
||||
141: _Opcode_name[391:396],
|
||||
143: _Opcode_name[396:403],
|
||||
144: _Opcode_name[403:407],
|
||||
145: _Opcode_name[407:411],
|
||||
146: _Opcode_name[411:414],
|
||||
147: _Opcode_name[414:418],
|
||||
148: _Opcode_name[418:422],
|
||||
149: _Opcode_name[422:426],
|
||||
150: _Opcode_name[426:430],
|
||||
151: _Opcode_name[430:434],
|
||||
152: _Opcode_name[434:438],
|
||||
153: _Opcode_name[438:442],
|
||||
154: _Opcode_name[442:450],
|
||||
155: _Opcode_name[450:457],
|
||||
156: _Opcode_name[457:466],
|
||||
158: _Opcode_name[466:478],
|
||||
159: _Opcode_name[478:481],
|
||||
160: _Opcode_name[481:484],
|
||||
161: _Opcode_name[484:488],
|
||||
162: _Opcode_name[488:492],
|
||||
163: _Opcode_name[492:496],
|
||||
164: _Opcode_name[496:500],
|
||||
165: _Opcode_name[500:507],
|
||||
167: _Opcode_name[507:512],
|
||||
168: _Opcode_name[512:519],
|
||||
169: _Opcode_name[519:527],
|
||||
170: _Opcode_name[527:535],
|
||||
172: _Opcode_name[535:544],
|
||||
174: _Opcode_name[544:558],
|
||||
192: _Opcode_name[558:568],
|
||||
193: _Opcode_name[568:573],
|
||||
194: _Opcode_name[573:580],
|
||||
195: _Opcode_name[580:589],
|
||||
196: _Opcode_name[589:597],
|
||||
197: _Opcode_name[597:606],
|
||||
198: _Opcode_name[606:616],
|
||||
200: _Opcode_name[616:623],
|
||||
201: _Opcode_name[623:631],
|
||||
202: _Opcode_name[631:638],
|
||||
240: _Opcode_name[638:644],
|
||||
241: _Opcode_name[644:655],
|
||||
}
|
||||
|
||||
func (i Opcode) String() string {
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package storage
|
||||
|
||||
// GetContext ..
|
||||
func GetContext() interface{} { return 0 }
|
||||
|
||||
// Put stores a value in to the storage.
|
||||
func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 }
|
||||
|
||||
// GetInt returns the value as an integer.
|
||||
func GetInt(ctx interface{}, key interface{}) int { return 0 }
|
||||
|
||||
// GetString returns the value as an string.
|
||||
func GetString(ctx interface{}, key interface{}) string { return "" }
|
||||
|
||||
// Delete removes a stored key value pair.
|
||||
func Delete(ctx interface{}, key interface{}) int { return 0 }
|
|
@ -6,11 +6,10 @@ var Syscalls = map[string]string{
|
|||
// Storage API
|
||||
"GetContext": "Neo.Storage.GetContext",
|
||||
"Put": "Neo.Storage.Put",
|
||||
"GetInt": "Neo.Storage.Get",
|
||||
"GetString": "Neo.Storage.Get",
|
||||
"Get": "Neo.Storage.Get",
|
||||
"Delete": "Neo.Storage.Delete",
|
||||
|
||||
// Runtime
|
||||
// Runtime API
|
||||
"GetTrigger": "Neo.Runtime.GetTrigger",
|
||||
"CheckWitness": "Neo.Runtime.CheckWitness",
|
||||
"GetCurrentBlock": "Neo.Runtime.GetCurrentBlock",
|
||||
|
|
63
pkg/vm/tests/contant_test.go
Normal file
63
pkg/vm/tests/contant_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package vm_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasicConstant(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
||||
const x = 10
|
||||
|
||||
func Main() int {
|
||||
return x + 2
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(12))
|
||||
}
|
||||
|
||||
func TestShortHandMultiConst(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
||||
const (
|
||||
z = 3
|
||||
y = 2
|
||||
x = 1
|
||||
)
|
||||
|
||||
// should load al 3 constants in the Main.
|
||||
func Main() int {
|
||||
return x + z + y
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(6))
|
||||
}
|
||||
|
||||
func TestGlobalsWithFunctionParams(t *testing.T) {
|
||||
src := `
|
||||
package foobar
|
||||
|
||||
const (
|
||||
// complex he o_O
|
||||
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
|
||||
}
|
||||
`
|
||||
eval(t, src, []byte("FOO"))
|
||||
}
|
|
@ -7,6 +7,47 @@ import (
|
|||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
func TestEntryPointWithMethod(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
||||
func Main(op string) int {
|
||||
if op == "a" {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`
|
||||
evalWithArgs(t, src, []byte("a"), nil, big.NewInt(1))
|
||||
}
|
||||
|
||||
func TestEntryPointWithArgs(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
||||
func Main(args []interface{}) int {
|
||||
return 2 + args[1].(int)
|
||||
}
|
||||
`
|
||||
args := []vm.StackItem{vm.NewBigIntegerItem(0), vm.NewBigIntegerItem(1)}
|
||||
evalWithArgs(t, src, nil, args, big.NewInt(3))
|
||||
}
|
||||
|
||||
func TestEntryPointWithMethodAndArgs(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
||||
func Main(method string, args []interface{}) int {
|
||||
if method == "foobar" {
|
||||
return 2 + args[1].(int)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`
|
||||
args := []vm.StackItem{vm.NewBigIntegerItem(0), vm.NewBigIntegerItem(1)}
|
||||
evalWithArgs(t, src, []byte("foobar"), args, big.NewInt(3))
|
||||
}
|
||||
|
||||
func TestArrayFieldInStruct(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
|
124
pkg/vm/tests/function_call_test.go
Normal file
124
pkg/vm/tests/function_call_test.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package vm_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleFunctionCall(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
y := getSomeInteger()
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
x := 10
|
||||
return x
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(20))
|
||||
}
|
||||
|
||||
func TestNotAssignedFunctionCall(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() int {
|
||||
getSomeInteger()
|
||||
getSomeInteger()
|
||||
return 0
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
return 0
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(0))
|
||||
}
|
||||
|
||||
func TestMultipleFunctionCalls(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
y := getSomeInteger()
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
x := 10
|
||||
y := getSomeOtherInt()
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeOtherInt() int {
|
||||
x := 8
|
||||
return x
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(28))
|
||||
}
|
||||
|
||||
func TestFunctionCallWithArgs(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
y := getSomeInteger(x)
|
||||
return y
|
||||
}
|
||||
|
||||
func getSomeInteger(x int) int {
|
||||
y := 8
|
||||
return x + y
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(18))
|
||||
}
|
||||
|
||||
func TestFunctionCallWithInterfaceType(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() interface{} {
|
||||
x := getSomeInteger(10)
|
||||
return x
|
||||
}
|
||||
|
||||
func getSomeInteger(x interface{}) interface{} {
|
||||
return x
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(10))
|
||||
}
|
||||
|
||||
func TestFunctionCallMultiArg(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := addIntegers(2, 4)
|
||||
return x
|
||||
}
|
||||
|
||||
func addIntegers(x int, y int) int {
|
||||
return x + y
|
||||
}
|
||||
`
|
||||
eval(t, src, big.NewInt(6))
|
||||
}
|
||||
|
||||
func TestFunctionWithVoidReturn(t *testing.T) {
|
||||
src := `
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 2
|
||||
getSomeInteger()
|
||||
y := 4
|
||||
return x + y
|
||||
}
|
||||
|
||||
func getSomeInteger() { }
|
||||
`
|
||||
eval(t, src, big.NewInt(6))
|
||||
}
|
22
pkg/vm/tests/syscall_test.go
Normal file
22
pkg/vm/tests/syscall_test.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package vm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStoragePutGet(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/api/storage"
|
||||
|
||||
func Main() string {
|
||||
ctx := storage.Context()
|
||||
key := "token"
|
||||
storage.Put(ctx, key, "foo")
|
||||
x := storage.Get(ctx, key)
|
||||
return x.(string)
|
||||
}
|
||||
`
|
||||
eval(t, src, []byte("foo"))
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package vm_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -16,19 +17,44 @@ type testCase struct {
|
|||
}
|
||||
|
||||
func eval(t *testing.T, src string, result interface{}) {
|
||||
vm := vm.New(nil, vm.ModeMute)
|
||||
vm := vmAndCompile(t, src)
|
||||
vm.Run()
|
||||
assertResult(t, vm, result)
|
||||
}
|
||||
|
||||
func evalWithArgs(t *testing.T, src string, op []byte, args []vm.StackItem, result interface{}) {
|
||||
vm := vmAndCompile(t, src)
|
||||
vm.LoadArgs(op, args)
|
||||
vm.Run()
|
||||
assertResult(t, vm, result)
|
||||
}
|
||||
|
||||
func assertResult(t *testing.T, vm *vm.VM, result interface{}) {
|
||||
assert.Equal(t, result, vm.PopResult())
|
||||
assert.Equal(t, 0, vm.Astack().Len())
|
||||
assert.Equal(t, 0, vm.Istack().Len())
|
||||
}
|
||||
|
||||
func vmAndCompile(t *testing.T, src string) *vm.VM {
|
||||
vm := vm.New(vm.ModeMute)
|
||||
|
||||
storePlugin := newStoragePlugin()
|
||||
vm.RegisterInteropFunc("Neo.Storage.Get", storePlugin.Get)
|
||||
vm.RegisterInteropFunc("Neo.Storage.Put", storePlugin.Put)
|
||||
|
||||
b, err := compiler.Compile(strings.NewReader(src), &compiler.Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vm.Load(b)
|
||||
vm.Run()
|
||||
assert.Equal(t, result, vm.PopResult())
|
||||
return vm
|
||||
}
|
||||
|
||||
func TestVMAndCompilerCases(t *testing.T) {
|
||||
vm := vm.New(nil, vm.ModeMute)
|
||||
vm := vm.New(vm.ModeMute)
|
||||
|
||||
storePlugin := newStoragePlugin()
|
||||
vm.RegisterInteropFunc("Neo.Storage.Get", storePlugin.Get)
|
||||
|
||||
testCases := []testCase{}
|
||||
testCases = append(testCases, numericTestCases...)
|
||||
|
@ -46,3 +72,38 @@ func TestVMAndCompilerCases(t *testing.T) {
|
|||
assert.Equal(t, tc.result, vm.PopResult())
|
||||
}
|
||||
}
|
||||
|
||||
type storagePlugin struct {
|
||||
mem map[string][]byte
|
||||
}
|
||||
|
||||
func newStoragePlugin() *storagePlugin {
|
||||
return &storagePlugin{
|
||||
mem: make(map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storagePlugin) Delete(vm *vm.VM) error {
|
||||
vm.Estack().Pop()
|
||||
key := vm.Estack().Pop().Bytes()
|
||||
delete(s.mem, string(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *storagePlugin) Put(vm *vm.VM) error {
|
||||
vm.Estack().Pop()
|
||||
key := vm.Estack().Pop().Bytes()
|
||||
value := vm.Estack().Pop().Bytes()
|
||||
s.mem[string(key)] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *storagePlugin) Get(vm *vm.VM) error {
|
||||
vm.Estack().Pop()
|
||||
item := vm.Estack().Pop().Bytes()
|
||||
if val, ok := s.mem[string(item)]; ok {
|
||||
vm.Estack().PushVal(val)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("could not find %+v", item)
|
||||
}
|
||||
|
|
53
pkg/vm/vm.go
53
pkg/vm/vm.go
|
@ -26,8 +26,8 @@ var (
|
|||
type VM struct {
|
||||
state State
|
||||
|
||||
// interop layer.
|
||||
interop *InteropService
|
||||
// registered interop hooks.
|
||||
interop map[string]InteropFunc
|
||||
|
||||
// scripts loaded in memory.
|
||||
scripts map[util.Uint160][]byte
|
||||
|
@ -41,12 +41,9 @@ type VM struct {
|
|||
}
|
||||
|
||||
// New returns a new VM object ready to load .avm bytecode scripts.
|
||||
func New(svc *InteropService, mode Mode) *VM {
|
||||
if svc == nil {
|
||||
svc = NewInteropService()
|
||||
}
|
||||
func New(mode Mode) *VM {
|
||||
vm := &VM{
|
||||
interop: svc,
|
||||
interop: make(map[string]InteropFunc),
|
||||
scripts: make(map[util.Uint160][]byte),
|
||||
state: haltState,
|
||||
istack: NewStack("invocation"),
|
||||
|
@ -56,9 +53,44 @@ func New(svc *InteropService, mode Mode) *VM {
|
|||
if mode == ModeMute {
|
||||
vm.mute = true
|
||||
}
|
||||
|
||||
// Register native interop hooks.
|
||||
vm.RegisterInteropFunc("Neo.Runtime.Log", runtimeLog)
|
||||
vm.RegisterInteropFunc("Neo.Runtime.Notify", runtimeNotify)
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
// RegisterInteropFunc will register the given InteropFunc to the VM.
|
||||
func (v *VM) RegisterInteropFunc(name string, f InteropFunc) {
|
||||
v.interop[name] = f
|
||||
}
|
||||
|
||||
// Estack will return the evalutation stack so interop hooks can utilize this.
|
||||
func (v *VM) Estack() *Stack {
|
||||
return v.estack
|
||||
}
|
||||
|
||||
// Astack will return the alt stack so interop hooks can utilize this.
|
||||
func (v *VM) Astack() *Stack {
|
||||
return v.astack
|
||||
}
|
||||
|
||||
// Istack will return the invocation stack so interop hooks can utilize this.
|
||||
func (v *VM) Istack() *Stack {
|
||||
return v.istack
|
||||
}
|
||||
|
||||
// LoadArgs will load in the arguments used in the Mian entry point.
|
||||
func (v *VM) LoadArgs(method []byte, args []StackItem) {
|
||||
if len(args) > 0 {
|
||||
v.estack.PushVal(args)
|
||||
}
|
||||
if method != nil {
|
||||
v.estack.PushVal([]byte(method))
|
||||
}
|
||||
}
|
||||
|
||||
// PrintOps will print the opcodes of the current loaded program to stdout.
|
||||
func (v *VM) PrintOps() {
|
||||
prog := v.Context().Program()
|
||||
|
@ -592,8 +624,11 @@ func (v *VM) execute(ctx *Context, op Opcode) {
|
|||
|
||||
case Osyscall:
|
||||
api := ctx.readVarBytes()
|
||||
err := v.interop.Call(api, v)
|
||||
if err != nil {
|
||||
ifunc, ok := v.interop[string(api)]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("interop hook (%s) not registered", api))
|
||||
}
|
||||
if err := ifunc(v); err != nil {
|
||||
panic(fmt.Sprintf("failed to invoke syscall: %s", err))
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,31 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInteropHook(t *testing.T) {
|
||||
v := New(ModeMute)
|
||||
v.RegisterInteropFunc("foo", func(evm *VM) error {
|
||||
evm.Estack().PushVal(1)
|
||||
return nil
|
||||
})
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
EmitSyscall(buf, "foo")
|
||||
EmitOpcode(buf, Oret)
|
||||
v.Load(buf.Bytes())
|
||||
v.Run()
|
||||
assert.Equal(t, 1, v.estack.Len())
|
||||
assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value())
|
||||
}
|
||||
|
||||
func TestRegisterInterop(t *testing.T) {
|
||||
v := New(ModeMute)
|
||||
currRegistered := len(v.interop)
|
||||
v.RegisterInteropFunc("foo", func(evm *VM) error { return nil })
|
||||
assert.Equal(t, currRegistered+1, len(v.interop))
|
||||
_, ok := v.interop["foo"]
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
|
||||
func TestPushBytes1to75(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 1; i <= 75; i++ {
|
||||
|
@ -216,7 +241,7 @@ func makeProgram(opcodes ...Opcode) []byte {
|
|||
}
|
||||
|
||||
func load(prog []byte) *VM {
|
||||
vm := New(nil, ModeMute)
|
||||
vm := New(ModeMute)
|
||||
vm.mute = true
|
||||
vm.istack.PushVal(NewContext(prog))
|
||||
return vm
|
Loading…
Reference in a new issue