VM draft + cli setup (#20)
* updated readme * added basic cmd. * added seperate folders for cmd packages. * Fix netmodes in test + reverse bigint bytes * glide get deps
This commit is contained in:
parent
b6d8271b8d
commit
f7d57e4e49
18 changed files with 861 additions and 49 deletions
|
@ -50,8 +50,9 @@ The project will exist out of the following packages:
|
||||||
| api | started | [@anthdm](https://github.com/anthdm) |
|
| api | started | [@anthdm](https://github.com/anthdm) |
|
||||||
| core | started | [@anthdm](https://github.com/anthdm) |
|
| core | started | [@anthdm](https://github.com/anthdm) |
|
||||||
| network | 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) |
|
| smartcontract | started | [@revett](https://github.com/revett) |
|
||||||
| vm | started | [@revett](https://github.com/revett) |
|
| cli | started | [@revett](https://github.com/revett) |
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.8.0
|
0.9.0
|
||||||
|
|
42
cli/main.go
42
cli/main.go
|
@ -1,43 +1,21 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/cli/server"
|
||||||
|
"github.com/CityOfZion/neo-go/cli/smartcontract"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
tcp = flag.Int("tcp", 3000, "port TCP listener will listen on.")
|
|
||||||
seed = flag.String("seed", "", "initial seed servers.")
|
|
||||||
net = flag.Int("net", 56753, "the mode the server will operate in.")
|
|
||||||
rpc = flag.Int("rpc", 0, "let this server also respond to rpc calls on this port")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Simple dirty and quick bootstrapping for the sake of development.
|
|
||||||
// e.g run 2 nodes:
|
|
||||||
// neoserver -tcp :4000
|
|
||||||
// neoserver -tcp :3000 -seed 127.0.0.1:4000
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
ctl := cli.NewApp()
|
||||||
|
ctl.Name = "neo-go"
|
||||||
|
|
||||||
opts := network.StartOpts{
|
ctl.Commands = []cli.Command{
|
||||||
Seeds: parseSeeds(*seed),
|
server.NewCommand(),
|
||||||
TCP: *tcp,
|
smartcontract.NewCommand(),
|
||||||
RPC: *rpc,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s := network.NewServer(network.NetMode(*net))
|
ctl.Run(os.Args)
|
||||||
s.Start(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSeeds(s string) []string {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
seeds := strings.Split(s, ",")
|
|
||||||
if len(seeds) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return seeds
|
|
||||||
}
|
}
|
||||||
|
|
56
cli/server/server.go
Normal file
56
cli/server/server.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCommand creates a new Node command
|
||||||
|
func NewCommand() cli.Command {
|
||||||
|
return cli.Command{
|
||||||
|
Name: "node",
|
||||||
|
Usage: "start a NEO node",
|
||||||
|
Action: startServer,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "tcp"},
|
||||||
|
cli.IntFlag{Name: "rpc"},
|
||||||
|
cli.StringFlag{Name: "seed"},
|
||||||
|
cli.BoolFlag{Name: "privnet, p"},
|
||||||
|
cli.BoolFlag{Name: "mainnet, m"},
|
||||||
|
cli.BoolFlag{Name: "testnet, t"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startServer(ctx *cli.Context) error {
|
||||||
|
opts := network.StartOpts{
|
||||||
|
Seeds: parseSeeds(ctx.String("seed")),
|
||||||
|
TCP: ctx.Int("tcp"),
|
||||||
|
RPC: ctx.Int("rpc"),
|
||||||
|
}
|
||||||
|
|
||||||
|
net := network.ModePrivNet
|
||||||
|
if ctx.Bool("testnet") {
|
||||||
|
net = network.ModeTestNet
|
||||||
|
}
|
||||||
|
if ctx.Bool("mainnet") {
|
||||||
|
net = network.ModeMainNet
|
||||||
|
}
|
||||||
|
|
||||||
|
s := network.NewServer(net)
|
||||||
|
s.Start(opts)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSeeds(s string) []string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
seeds := strings.Split(s, ",")
|
||||||
|
if len(seeds) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return seeds
|
||||||
|
}
|
44
cli/smartcontract/smart_contract.go
Normal file
44
cli/smartcontract/smart_contract.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCommand returns a new contract command.
|
||||||
|
func NewCommand() cli.Command {
|
||||||
|
return cli.Command{
|
||||||
|
Name: "contract",
|
||||||
|
Usage: "compile - debug - deploy smart contracts",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "compile",
|
||||||
|
Usage: "compile a smart contract to a .avm file",
|
||||||
|
Action: contractCompile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "opdump",
|
||||||
|
Usage: "dump the opcode of a .go file",
|
||||||
|
Action: contractDumpOpcode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contractCompile(ctx *cli.Context) error {
|
||||||
|
fmt.Println("compile")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contractDumpOpcode(ctx *cli.Context) error {
|
||||||
|
src := ctx.Args()[0]
|
||||||
|
|
||||||
|
c := vm.NewCompiler()
|
||||||
|
if err := c.CompileSource(src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.DumpOpcode()
|
||||||
|
return nil
|
||||||
|
}
|
6
glide.lock
generated
6
glide.lock
generated
|
@ -1,5 +1,5 @@
|
||||||
hash: 2b8debf7936e789545da367433ddbf5f0e3bb54658340d6a412970bca25e6335
|
hash: 5cb2fb077aa0e57c788a99f9ded4bf2d421a04f12f6b8436234d12e93f418f36
|
||||||
updated: 2018-02-05T16:11:44.616743+01:00
|
updated: 2018-02-09T16:49:32.538502496+01:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/golang/snappy
|
- name: github.com/golang/snappy
|
||||||
version: 553a641470496b2327abcac10b36396bd98e45c9
|
version: 553a641470496b2327abcac10b36396bd98e45c9
|
||||||
|
@ -18,4 +18,6 @@ imports:
|
||||||
- leveldb/storage
|
- leveldb/storage
|
||||||
- leveldb/table
|
- leveldb/table
|
||||||
- leveldb/util
|
- leveldb/util
|
||||||
|
- name: github.com/urfave/cli
|
||||||
|
version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1
|
||||||
testImports: []
|
testImports: []
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package: github.com/CityOfZion/neo-go
|
package: github.com/CityOfZion/neo-go
|
||||||
import:
|
import:
|
||||||
- package: github.com/syndtr/goleveldb/leveldb
|
- package: github.com/syndtr/goleveldb
|
||||||
|
subpackages:
|
||||||
|
- leveldb
|
||||||
|
- package: github.com/urfave/cli
|
||||||
|
version: ^1.20.0
|
||||||
|
|
|
@ -28,8 +28,8 @@ type NetMode uint32
|
||||||
// String implements the stringer interface.
|
// String implements the stringer interface.
|
||||||
func (n NetMode) String() string {
|
func (n NetMode) String() string {
|
||||||
switch n {
|
switch n {
|
||||||
case ModeDevNet:
|
case ModePrivNet:
|
||||||
return "devnet"
|
return "privnet"
|
||||||
case ModeTestNet:
|
case ModeTestNet:
|
||||||
return "testnet"
|
return "testnet"
|
||||||
case ModeMainNet:
|
case ModeMainNet:
|
||||||
|
@ -42,8 +42,8 @@ func (n NetMode) String() string {
|
||||||
// Values used for the magic field, according to the docs.
|
// Values used for the magic field, according to the docs.
|
||||||
const (
|
const (
|
||||||
ModeMainNet NetMode = 0x00746e41 // 7630401
|
ModeMainNet NetMode = 0x00746e41 // 7630401
|
||||||
ModeTestNet = 0x74746e41 // 1953787457
|
ModeTestNet NetMode = 0x74746e41 // 1953787457
|
||||||
ModeDevNet = 56753 // docker privnet
|
ModePrivNet NetMode = 56753 // docker privnet
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message is the complete message send between nodes.
|
// Message is the complete message send between nodes.
|
||||||
|
|
|
@ -105,7 +105,7 @@ func (m protectedHashmap) has(h util.Uint256) bool {
|
||||||
func NewServer(net NetMode) *Server {
|
func NewServer(net NetMode) *Server {
|
||||||
logger := log.New(os.Stdout, "[NEO SERVER] :: ", 0)
|
logger := log.New(os.Stdout, "[NEO SERVER] :: ", 0)
|
||||||
|
|
||||||
if net != ModeTestNet && net != ModeMainNet && net != ModeDevNet {
|
if net != ModeTestNet && net != ModeMainNet && net != ModePrivNet {
|
||||||
logger.Fatalf("invalid network mode %d", net)
|
logger.Fatalf("invalid network mode %d", net)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
// TODO this should be moved to localPeer test.
|
// TODO this should be moved to localPeer test.
|
||||||
|
|
||||||
func TestHandleVersionFailWrongPort(t *testing.T) {
|
func TestHandleVersionFailWrongPort(t *testing.T) {
|
||||||
s := NewServer(ModeDevNet)
|
s := NewServer(ModePrivNet)
|
||||||
go s.loop()
|
go s.loop()
|
||||||
|
|
||||||
p := NewLocalPeer(s)
|
p := NewLocalPeer(s)
|
||||||
|
@ -21,7 +21,7 @@ func TestHandleVersionFailWrongPort(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleVersionFailIdenticalNonce(t *testing.T) {
|
func TestHandleVersionFailIdenticalNonce(t *testing.T) {
|
||||||
s := NewServer(ModeDevNet)
|
s := NewServer(ModePrivNet)
|
||||||
go s.loop()
|
go s.loop()
|
||||||
|
|
||||||
p := NewLocalPeer(s)
|
p := NewLocalPeer(s)
|
||||||
|
@ -33,7 +33,7 @@ func TestHandleVersionFailIdenticalNonce(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleVersion(t *testing.T) {
|
func TestHandleVersion(t *testing.T) {
|
||||||
s := NewServer(ModeDevNet)
|
s := NewServer(ModePrivNet)
|
||||||
go s.loop()
|
go s.loop()
|
||||||
|
|
||||||
p := NewLocalPeer(s)
|
p := NewLocalPeer(s)
|
||||||
|
|
|
@ -23,7 +23,7 @@ func Uint256DecodeFromString(s string) (Uint256, error) {
|
||||||
return val, err
|
return val, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b = ReverseByteSlice(b)
|
b = ToArrayReverse(b)
|
||||||
|
|
||||||
return Uint256DecodeFromBytes(b)
|
return Uint256DecodeFromBytes(b)
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,8 @@ func Uint256DecodeFromBytes(b []byte) (Uint256, error) {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReverseByteSlice(b []byte) []byte {
|
// ToArrayReverse return a reversed version of the given byte slice.
|
||||||
|
func ToArrayReverse(b []byte) []byte {
|
||||||
dest := make([]byte, len(b))
|
dest := make([]byte, len(b))
|
||||||
|
|
||||||
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
|
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
|
194
pkg/vm/compiler.go
Normal file
194
pkg/vm/compiler.go
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
outputExt = ".avm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Syscalls that have no return value.
|
||||||
|
var nonReturnSysCalls = []string{
|
||||||
|
"Notify", "print", "Log", "Put", "Register",
|
||||||
|
"append", "Delete", "SetVotes", "ContractDestroy",
|
||||||
|
"MerkleRoot", "Hash", "PrevHash", "GetHeader"}
|
||||||
|
|
||||||
|
// Compiler holds the output buffer of the compiled source.
|
||||||
|
type Compiler struct {
|
||||||
|
// Output extension of the file. Default .avm.
|
||||||
|
OutputExt string
|
||||||
|
sb *ScriptBuilder
|
||||||
|
curLineNum int
|
||||||
|
|
||||||
|
i int
|
||||||
|
varList []Variable
|
||||||
|
vars map[string]Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCompiler returns a new compiler ready to compile smartcontracts.
|
||||||
|
func NewCompiler() *Compiler {
|
||||||
|
return &Compiler{
|
||||||
|
OutputExt: outputExt,
|
||||||
|
sb: &ScriptBuilder{new(bytes.Buffer)},
|
||||||
|
vars: map[string]Variable{},
|
||||||
|
varList: []Variable{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileSource will compile the source file into an avm format.
|
||||||
|
func (c *Compiler) CompileSource(src string) error {
|
||||||
|
file, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Compile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit implements the ast.Visitor interface.
|
||||||
|
func (c *Compiler) Visit(node ast.Node) ast.Visitor {
|
||||||
|
switch t := node.(type) {
|
||||||
|
case *ast.AssignStmt:
|
||||||
|
c.processAssignStmt(t)
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
case *ast.ReturnStmt:
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) newVariable(k token.Token, i, val string) Variable {
|
||||||
|
v := Variable{
|
||||||
|
kind: k,
|
||||||
|
ident: i,
|
||||||
|
value: val,
|
||||||
|
pos: c.i,
|
||||||
|
}
|
||||||
|
c.vars[v.ident] = v
|
||||||
|
c.i++
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) initialize(n OpCode) {
|
||||||
|
// Get the (n) localVars which is basicly the number of args passed in Main
|
||||||
|
// and the number of local Vars in the function body.
|
||||||
|
c.sb.emitPush(n)
|
||||||
|
c.sb.emitPush(OpNewArray)
|
||||||
|
c.sb.emitPush(OpToAltStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) teardown() {
|
||||||
|
c.sb.emitPush(OpNOP)
|
||||||
|
c.sb.emitPush(OpFromAltStack)
|
||||||
|
c.sb.emitPush(OpDrop)
|
||||||
|
c.sb.emitPush(OpRET)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a variable on to the stack.
|
||||||
|
func (c *Compiler) storeLocal(v Variable) {
|
||||||
|
if v.kind == token.INT {
|
||||||
|
val, _ := strconv.Atoi(v.value)
|
||||||
|
c.sb.emitPushInt(int64(val))
|
||||||
|
}
|
||||||
|
if v.kind == token.STRING {
|
||||||
|
val := strings.Replace(v.value, `"`, "", 2)
|
||||||
|
c.sb.emitPushString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.sb.emitPush(OpFromAltStack)
|
||||||
|
c.sb.emitPush(OpDup)
|
||||||
|
c.sb.emitPush(OpToAltStack)
|
||||||
|
|
||||||
|
pos := int64(v.pos)
|
||||||
|
|
||||||
|
c.sb.emitPushInt(pos)
|
||||||
|
c.sb.emitPushInt(2)
|
||||||
|
c.sb.emitPush(OpRoll)
|
||||||
|
c.sb.emitPush(OpSetItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) loadLocal(ident string) {
|
||||||
|
val, ok := c.vars[ident]
|
||||||
|
if !ok {
|
||||||
|
c.reportError(fmt.Sprintf("local variable %s not found", ident))
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := int64(val.pos)
|
||||||
|
|
||||||
|
c.sb.emitPush(OpFromAltStack)
|
||||||
|
c.sb.emitPush(OpDup)
|
||||||
|
c.sb.emitPush(OpToAltStack)
|
||||||
|
|
||||||
|
// push it's index on the stack
|
||||||
|
c.sb.emitPushInt(pos)
|
||||||
|
c.sb.emitPush(OpPickItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: instead of passing the stmt in to this, put the lhs and rhs in.
|
||||||
|
// so we can reuse this.
|
||||||
|
func (c *Compiler) processAssignStmt(stmt *ast.AssignStmt) {
|
||||||
|
lhs := stmt.Lhs[0].(*ast.Ident)
|
||||||
|
switch t := stmt.Rhs[0].(type) {
|
||||||
|
case *ast.BasicLit:
|
||||||
|
c.storeLocal(c.newVariable(t.Kind, lhs.Name, t.Value))
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
switch t.Type.(type) {
|
||||||
|
case *ast.StructType:
|
||||||
|
c.reportError("assigning struct literals not yet implemented")
|
||||||
|
case *ast.ArrayType:
|
||||||
|
// for _, expr := range t.Elts {
|
||||||
|
// v := expr.(*ast.BasicLit)
|
||||||
|
// c.storeLocal(c.newVariable(v.Kind, lhs.Name, v.Value))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
case *ast.Ident:
|
||||||
|
c.loadLocal(t.Name)
|
||||||
|
case *ast.FuncLit:
|
||||||
|
c.reportError("assigning function literals not yet implemented")
|
||||||
|
default:
|
||||||
|
fmt.Println(reflect.TypeOf(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile will compile from r into an avm format.
|
||||||
|
func (c *Compiler) Compile(r io.Reader) error {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
f, err := parser.ParseFile(fset, "", r, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.initialize(OpPush2) // initialize the compiler with n local stack vars.
|
||||||
|
ast.Walk(c, f) // walk through and process the AST
|
||||||
|
c.teardown() // done compiling
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: More detailed report (lineno, ...)
|
||||||
|
func (c *Compiler) reportError(msg string) {
|
||||||
|
fmt.Printf("COMPILER ERROR :: %s\n", msg)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpOpcode dumps the current buffer, formatted with index, hex and opcode.
|
||||||
|
// Usefull for debugging smartcontracts.
|
||||||
|
func (c *Compiler) DumpOpcode() {
|
||||||
|
c.sb.dumpOpcode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Variable can represent any variable in the program.
|
||||||
|
type Variable struct {
|
||||||
|
ident string
|
||||||
|
kind token.Token
|
||||||
|
value string
|
||||||
|
pos int
|
||||||
|
}
|
86
pkg/vm/compiler_test.go
Normal file
86
pkg/vm/compiler_test.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// opener
|
||||||
|
//
|
||||||
|
// 0x53 push len arguments
|
||||||
|
// 0xc5 open new array
|
||||||
|
// 0x6B to alt stack
|
||||||
|
//
|
||||||
|
// 0x5A the number 10 is pushed on the stack
|
||||||
|
// 0x6C put the input onto the main stack remove from alt
|
||||||
|
// 0x76 dup the item on top of the stack
|
||||||
|
// 0x6B put the item on the alt stack
|
||||||
|
// 0x00 put empty array on the stack
|
||||||
|
// 0x52 put the number 2 on the stack
|
||||||
|
// 0x7A put the item n back on top of the stack
|
||||||
|
// 0xC4 set item
|
||||||
|
// 0x59 put the number 9 on the stack
|
||||||
|
// 0x6C put the input onto the main stack remove from alt stack
|
||||||
|
// 0x76 dup the item on top of the stackj
|
||||||
|
// 0x6B put the item on the alt stack
|
||||||
|
// 0x51 push the number 1 on the stack
|
||||||
|
// 0x52 push the number 2 on the stack
|
||||||
|
// 0x7A put the item n back on top of the stack
|
||||||
|
// 0xC4 set the item
|
||||||
|
// 0x62 JMP
|
||||||
|
// 0x03 the next 3 bytes is dat pushed on the stack
|
||||||
|
// 0x6C put the input ont the main stack remove from alt stack
|
||||||
|
// 0x00 put empty array onto the stack
|
||||||
|
// 0x02 the next 2 bytes is data pushed on the stack
|
||||||
|
// 0xE8 1000 uint16
|
||||||
|
// 0x03 1000 uint16
|
||||||
|
// 0x6C put the input onto the main stack remove from alt
|
||||||
|
// 0x76 dup the item on top of the stack
|
||||||
|
// 0x6B put the item on the alt stack
|
||||||
|
// 0x52 push the number 2 on the stack
|
||||||
|
// 0x52 push the number 2 on the stack
|
||||||
|
// 0x7A put the item n back on top of the stack
|
||||||
|
// 0xC4 set the item
|
||||||
|
// 0x00 empty array is pushed on the stack
|
||||||
|
// 0x61 nop
|
||||||
|
// 0x6C put the input onto the main stack remove from alt
|
||||||
|
// 0x75 removes the top stack item
|
||||||
|
// 0x66 return
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
func TestSimpleAssign(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package NEP5
|
||||||
|
|
||||||
|
func Main() {
|
||||||
|
x := 10
|
||||||
|
y := 8
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
c := NewCompiler()
|
||||||
|
if err := c.Compile(strings.NewReader(src)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.DumpOpcode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssignLoadLocal(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package NEP5
|
||||||
|
|
||||||
|
func Main() {
|
||||||
|
x := 1
|
||||||
|
y := x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
c := NewCompiler()
|
||||||
|
if err := c.Compile(strings.NewReader(src)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.DumpOpcode()
|
||||||
|
}
|
129
pkg/vm/opcode.go
Normal file
129
pkg/vm/opcode.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
// OpCode is an single operational instruction for the GO NEO virtual machine.
|
||||||
|
type OpCode byte
|
||||||
|
|
||||||
|
// List of supported opcodes.
|
||||||
|
const (
|
||||||
|
// Constants
|
||||||
|
OpPush0 OpCode = 0x00 // An empty array of bytes is pushed onto the stack.
|
||||||
|
OpPushF OpCode = OpPush0
|
||||||
|
OpPushBytes1 OpCode = 0x01 // 0x01-0x4B The next opcode bytes is data to be pushed onto the stack
|
||||||
|
OpPushBytes75 OpCode = 0x4B
|
||||||
|
OpPushData1 OpCode = 0x4C // The next byte contains the number of bytes to be pushed onto the stack.
|
||||||
|
OpPushData2 OpCode = 0x4D // The next two bytes contain the number of bytes to be pushed onto the stack.
|
||||||
|
OpPushData4 OpCode = 0x4E // The next four bytes contain the number of bytes to be pushed onto the stack.
|
||||||
|
OpPushM1 OpCode = 0x4F // The number -1 is pushed onto the stack.
|
||||||
|
OpPush1 OpCode = 0x51
|
||||||
|
OpPushT OpCode = OpPush1
|
||||||
|
OpPush2 OpCode = 0x52 // The number 2 is pushed onto the stack.
|
||||||
|
OpPush3 OpCode = 0x53 // The number 3 is pushed onto the stack.
|
||||||
|
OpPush4 OpCode = 0x54 // The number 4 is pushed onto the stack.
|
||||||
|
OpPush5 OpCode = 0x55 // The number 5 is pushed onto the stack.
|
||||||
|
OpPush6 OpCode = 0x56 // The number 6 is pushed onto the stack.
|
||||||
|
OpPush7 OpCode = 0x57 // The number 7 is pushed onto the stack.
|
||||||
|
OpPush8 OpCode = 0x58 // The number 8 is pushed onto the stack.
|
||||||
|
OpPush9 OpCode = 0x59 // The number 9 is pushed onto the stack.
|
||||||
|
OpPush10 OpCode = 0x5A // The number 10 is pushed onto the stack.
|
||||||
|
OpPush11 OpCode = 0x5B // The number 11 is pushed onto the stack.
|
||||||
|
OpPush12 OpCode = 0x5C // The number 12 is pushed onto the stack.
|
||||||
|
OpPush13 OpCode = 0x5D // The number 13 is pushed onto the stack.
|
||||||
|
OpPush14 OpCode = 0x5E // The number 14 is pushed onto the stack.
|
||||||
|
OpPush15 OpCode = 0x5F // The number 15 is pushed onto the stack.
|
||||||
|
OpPush16 OpCode = 0x60 // The number 16 is pushed onto the stack.
|
||||||
|
|
||||||
|
// Flow control
|
||||||
|
OpNOP OpCode = 0x61 // No operation.
|
||||||
|
OpJMP OpCode = 0x62
|
||||||
|
OpJMPIF OpCode = 0x63
|
||||||
|
OpJMPIFNOT OpCode = 0x64
|
||||||
|
OpCall OpCode = 0x65
|
||||||
|
OpRET OpCode = 0x66
|
||||||
|
OpAppCall OpCode = 0x67
|
||||||
|
OpSysCall OpCode = 0x68
|
||||||
|
OpTailCall OpCode = 0x69
|
||||||
|
|
||||||
|
// The stack
|
||||||
|
OpDupFromAltStack OpCode = 0x6A
|
||||||
|
OpToAltStack OpCode = 0x6B // Puts the input onto the top of the alt stack. Removes it from the main stack.
|
||||||
|
OpFromAltStack OpCode = 0x6C // Puts the input onto the top of the main stack. Removes it from the alt stack.
|
||||||
|
OpXDrop OpCode = 0x6D
|
||||||
|
OpXSwap OpCode = 0x72
|
||||||
|
OpXTuck OpCode = 0x73
|
||||||
|
OpDepth OpCode = 0x74 // Puts the number of stack items onto the stack.
|
||||||
|
OpDrop OpCode = 0x75 // Removes the top stack item.
|
||||||
|
OpDup OpCode = 0x76 // Duplicates the top stack item.
|
||||||
|
OpNip OpCode = 0x77 // Removes the second-to-top stack item.
|
||||||
|
OpOver OpCode = 0x78 // Copies the second-to-top stack item to the top.
|
||||||
|
OpPick OpCode = 0x79 // The item n back in the stack is copied to the top.
|
||||||
|
OpRoll OpCode = 0x7A // The item n back in the stack is moved to the top.
|
||||||
|
OpRot OpCode = 0x7B // The top three items on the stack are rotated to the left.
|
||||||
|
OpSwap OpCode = 0x7C // The top two items on the stack are swapped.
|
||||||
|
OpTuck OpCode = 0x7D // The item at the top of the stack is copied and inserted before the second-to-top item.
|
||||||
|
|
||||||
|
// Splice
|
||||||
|
OpCat OpCode = 0x7E // Concatenates two strings.
|
||||||
|
OpSubStr OpCode = 0x7F // Returns a section of a string.
|
||||||
|
OpLeft OpCode = 0x80 // Keeps only characters left of the specified point in a string.
|
||||||
|
OpRight OpCode = 0x81 // Keeps only characters right of the specified point in a string.
|
||||||
|
OpSize OpCode = 0x82 // Returns the length of the input string.
|
||||||
|
|
||||||
|
// Bitwise logic
|
||||||
|
OpInvert OpCode = 0x83 // Flips all of the bits in the input.
|
||||||
|
OpAnd OpCode = 0x84 // Boolean and between each bit in the inputs.
|
||||||
|
OpOr OpCode = 0x85 // Boolean or between each bit in the inputs.
|
||||||
|
OpXor OpCode = 0x86 // Boolean exclusive or between each bit in the inputs.
|
||||||
|
OpEqual OpCode = 0x87 // Returns 1 if the inputs are exactly equal, 0 otherwise.
|
||||||
|
|
||||||
|
// Arithmetic
|
||||||
|
// Note: Arithmetic inputs are limited to signed 32-bit integers, but may overflow their output.
|
||||||
|
OpInc OpCode = 0x8B // 1 is added to the input.
|
||||||
|
OpDec OpCode = 0x8C // 1 is subtracted from the input.
|
||||||
|
OpSign OpCode = 0x8D
|
||||||
|
OpNegate OpCode = 0x8F // The sign of the input is flipped.
|
||||||
|
OpAbs OpCode = 0x90 // The input is made positive.
|
||||||
|
OpNot OpCode = 0x91 // If the input is 0 or 1, it is flipped. Otherwise the output will be 0.
|
||||||
|
OpNZ OpCode = 0x92 // Returns 0 if the input is 0. 1 otherwise.
|
||||||
|
OpAdd OpCode = 0x93 // a is added to b.
|
||||||
|
OpSub OpCode = 0x94 // b is subtracted from a.
|
||||||
|
OpMul OpCode = 0x95 // a is multiplied by b.
|
||||||
|
OpDiv OpCode = 0x96 // a is divided by b.
|
||||||
|
OpMod OpCode = 0x97 // Returns the remainder after dividing a by b.
|
||||||
|
OpShl OpCode = 0x98 // Shifts a left b bits, preserving sign.
|
||||||
|
OpShr OpCode = 0x99 // Shifts a right b bits, preserving sign.
|
||||||
|
OpBoolAnd OpCode = 0x9A // If both a and b are not 0, the output is 1. Otherwise 0.
|
||||||
|
OpBoolOr OpCode = 0x9B // If a or b is not 0, the output is 1. Otherwise 0.
|
||||||
|
OpNumEqual OpCode = 0x9C // Returns 1 if the numbers are equal, 0 otherwise.
|
||||||
|
OpNumNotEqual OpCode = 0x9E // Returns 1 if the numbers are not equal, 0 otherwise.
|
||||||
|
OpLT OpCode = 0x9F // Returns 1 if a is less than b, 0 otherwise.
|
||||||
|
OpGT OpCode = 0xA0 // Returns 1 if a is greater than b, 0 otherwise.
|
||||||
|
OpLTE OpCode = 0xA1 // Returns 1 if a is less than or equal to b, 0 otherwise.
|
||||||
|
OpGTE OpCode = 0xA2 // Returns 1 if a is greater than or equal to b, 0 otherwise.
|
||||||
|
OpMin OpCode = 0xA3 // Returns the smaller of a and b.
|
||||||
|
OpMax OpCode = 0xA4 // Returns the larger of a and b.
|
||||||
|
OpWithin OpCode = 0xA5 // Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
|
||||||
|
|
||||||
|
// Crypto
|
||||||
|
OpSHA1 OpCode = 0xA7 // The input is hashed using SHA-1.
|
||||||
|
OpSHA256 OpCode = 0xA8 // The input is hashed using SHA-256.
|
||||||
|
OpHASH160 OpCode = 0xA9
|
||||||
|
OpHASH256 OpCode = 0xAA
|
||||||
|
OpCheckSig OpCode = 0xAC
|
||||||
|
OpCheckMultiSig OpCode = 0xAE
|
||||||
|
|
||||||
|
// Array
|
||||||
|
OpArraySize OpCode = 0xC0
|
||||||
|
OpPack OpCode = 0xC1
|
||||||
|
OpUnpack OpCode = 0xC2
|
||||||
|
OpPickItem OpCode = 0xC3
|
||||||
|
OpSetItem OpCode = 0xC4
|
||||||
|
OpNewArray OpCode = 0xC5 // Pops size from stack and creates a new array with that size, and pushes the array into the stack
|
||||||
|
OpNewStruct OpCode = 0xC6
|
||||||
|
OpAppend OpCode = 0xC8
|
||||||
|
OpReverse OpCode = 0xC9
|
||||||
|
OpRemove OpCode = 0xCA
|
||||||
|
|
||||||
|
// Exceptions
|
||||||
|
OpThrow OpCode = 0xF0
|
||||||
|
OpThrowIfNot OpCode = 0xF1
|
||||||
|
)
|
118
pkg/vm/opcode_string.go
Normal file
118
pkg/vm/opcode_string.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// Code generated by "stringer -type=OpCode ./pkg/vm"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const _OpCode_name = "OpPush0OpPushBytes1OpPushBytes75OpPushData1OpPushData2OpPushData4OpPushM1OpPush1OpPush2OpPush3OpPush4OpPush5OpPush6OpPush7OpPush8OpPush9OpPush10OpPush11OpPush12OpPush13OpPush14OpPush15OpPush16OpNOPOpJMPOpJMPIFOpJMPIFNOTOpCallOpRETOpAppCallOpSysCallOpTailCallOpDupFromAltStackOpToAltStackOpFromAltStackOpXDropOpXSwapOpXTuckOpDepthOpDropOpDupOpNipOpOverOpPickOpRollOpRotOpSwapOpTuckOpCatOpSubStrOpLeftOpRightOpSizeOpInvertOpAndOpOrOpXorOpEqualOpIncOpDecOpSignOpNegateOpAbsOpNotOpNZOpAddOpSubOpMulOpDivOpModOpShlOpShrOpBoolAndOpBoolOrOpNumEqualOpNumNotEqualOpLTOpGTOpLTEOpGTEOpMinOpMaxOpWithinOpSHA1OpSHA256OpHASH160OpHASH256OpCheckSigOpCheckMultiSigOpArraySizeOpPackOpUnpackOpPickItemOpSetItemOpNewArrayOpNewStructOpAppendOpReverseOpRemoveOpThrowOpThrowIfNot"
|
||||||
|
|
||||||
|
var _OpCode_map = map[OpCode]string{
|
||||||
|
0: _OpCode_name[0:7],
|
||||||
|
1: _OpCode_name[7:19],
|
||||||
|
75: _OpCode_name[19:32],
|
||||||
|
76: _OpCode_name[32:43],
|
||||||
|
77: _OpCode_name[43:54],
|
||||||
|
78: _OpCode_name[54:65],
|
||||||
|
79: _OpCode_name[65:73],
|
||||||
|
81: _OpCode_name[73:80],
|
||||||
|
82: _OpCode_name[80:87],
|
||||||
|
83: _OpCode_name[87:94],
|
||||||
|
84: _OpCode_name[94:101],
|
||||||
|
85: _OpCode_name[101:108],
|
||||||
|
86: _OpCode_name[108:115],
|
||||||
|
87: _OpCode_name[115:122],
|
||||||
|
88: _OpCode_name[122:129],
|
||||||
|
89: _OpCode_name[129:136],
|
||||||
|
90: _OpCode_name[136:144],
|
||||||
|
91: _OpCode_name[144:152],
|
||||||
|
92: _OpCode_name[152:160],
|
||||||
|
93: _OpCode_name[160:168],
|
||||||
|
94: _OpCode_name[168:176],
|
||||||
|
95: _OpCode_name[176:184],
|
||||||
|
96: _OpCode_name[184:192],
|
||||||
|
97: _OpCode_name[192:197],
|
||||||
|
98: _OpCode_name[197:202],
|
||||||
|
99: _OpCode_name[202:209],
|
||||||
|
100: _OpCode_name[209:219],
|
||||||
|
101: _OpCode_name[219:225],
|
||||||
|
102: _OpCode_name[225:230],
|
||||||
|
103: _OpCode_name[230:239],
|
||||||
|
104: _OpCode_name[239:248],
|
||||||
|
105: _OpCode_name[248:258],
|
||||||
|
106: _OpCode_name[258:275],
|
||||||
|
107: _OpCode_name[275:287],
|
||||||
|
108: _OpCode_name[287:301],
|
||||||
|
109: _OpCode_name[301:308],
|
||||||
|
114: _OpCode_name[308:315],
|
||||||
|
115: _OpCode_name[315:322],
|
||||||
|
116: _OpCode_name[322:329],
|
||||||
|
117: _OpCode_name[329:335],
|
||||||
|
118: _OpCode_name[335:340],
|
||||||
|
119: _OpCode_name[340:345],
|
||||||
|
120: _OpCode_name[345:351],
|
||||||
|
121: _OpCode_name[351:357],
|
||||||
|
122: _OpCode_name[357:363],
|
||||||
|
123: _OpCode_name[363:368],
|
||||||
|
124: _OpCode_name[368:374],
|
||||||
|
125: _OpCode_name[374:380],
|
||||||
|
126: _OpCode_name[380:385],
|
||||||
|
127: _OpCode_name[385:393],
|
||||||
|
128: _OpCode_name[393:399],
|
||||||
|
129: _OpCode_name[399:406],
|
||||||
|
130: _OpCode_name[406:412],
|
||||||
|
131: _OpCode_name[412:420],
|
||||||
|
132: _OpCode_name[420:425],
|
||||||
|
133: _OpCode_name[425:429],
|
||||||
|
134: _OpCode_name[429:434],
|
||||||
|
135: _OpCode_name[434:441],
|
||||||
|
139: _OpCode_name[441:446],
|
||||||
|
140: _OpCode_name[446:451],
|
||||||
|
141: _OpCode_name[451:457],
|
||||||
|
143: _OpCode_name[457:465],
|
||||||
|
144: _OpCode_name[465:470],
|
||||||
|
145: _OpCode_name[470:475],
|
||||||
|
146: _OpCode_name[475:479],
|
||||||
|
147: _OpCode_name[479:484],
|
||||||
|
148: _OpCode_name[484:489],
|
||||||
|
149: _OpCode_name[489:494],
|
||||||
|
150: _OpCode_name[494:499],
|
||||||
|
151: _OpCode_name[499:504],
|
||||||
|
152: _OpCode_name[504:509],
|
||||||
|
153: _OpCode_name[509:514],
|
||||||
|
154: _OpCode_name[514:523],
|
||||||
|
155: _OpCode_name[523:531],
|
||||||
|
156: _OpCode_name[531:541],
|
||||||
|
158: _OpCode_name[541:554],
|
||||||
|
159: _OpCode_name[554:558],
|
||||||
|
160: _OpCode_name[558:562],
|
||||||
|
161: _OpCode_name[562:567],
|
||||||
|
162: _OpCode_name[567:572],
|
||||||
|
163: _OpCode_name[572:577],
|
||||||
|
164: _OpCode_name[577:582],
|
||||||
|
165: _OpCode_name[582:590],
|
||||||
|
167: _OpCode_name[590:596],
|
||||||
|
168: _OpCode_name[596:604],
|
||||||
|
169: _OpCode_name[604:613],
|
||||||
|
170: _OpCode_name[613:622],
|
||||||
|
172: _OpCode_name[622:632],
|
||||||
|
174: _OpCode_name[632:647],
|
||||||
|
192: _OpCode_name[647:658],
|
||||||
|
193: _OpCode_name[658:664],
|
||||||
|
194: _OpCode_name[664:672],
|
||||||
|
195: _OpCode_name[672:682],
|
||||||
|
196: _OpCode_name[682:691],
|
||||||
|
197: _OpCode_name[691:701],
|
||||||
|
198: _OpCode_name[701:712],
|
||||||
|
200: _OpCode_name[712:720],
|
||||||
|
201: _OpCode_name[720:729],
|
||||||
|
202: _OpCode_name[729:737],
|
||||||
|
240: _OpCode_name[737:744],
|
||||||
|
241: _OpCode_name[744:756],
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i OpCode) String() string {
|
||||||
|
if str, ok := _OpCode_map[i]; ok {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return "OpCode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
127
pkg/vm/script_builder.go
Normal file
127
pkg/vm/script_builder.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScriptBuilder generates bytecode and will write all
|
||||||
|
// generated bytecode into its internal buffer.
|
||||||
|
type ScriptBuilder struct {
|
||||||
|
buf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emit(op OpCode, b []byte) error {
|
||||||
|
if err := sb.buf.WriteByte(byte(op)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := sb.buf.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitPush(op OpCode) error {
|
||||||
|
return sb.buf.WriteByte(byte(op))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitPushBool(b bool) error {
|
||||||
|
if b {
|
||||||
|
return sb.emitPush(OpPushT)
|
||||||
|
}
|
||||||
|
return sb.emitPush(OpPushF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitPushInt(i int64) error {
|
||||||
|
if i == -1 {
|
||||||
|
return sb.emitPush(OpPushM1)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
return sb.emitPush(OpPushF)
|
||||||
|
}
|
||||||
|
if i > 0 && i < 16 {
|
||||||
|
val := OpCode((int(OpPush1) - 1 + int(i)))
|
||||||
|
return sb.emitPush(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
bInt := big.NewInt(i)
|
||||||
|
val := util.ToArrayReverse(bInt.Bytes())
|
||||||
|
return sb.emitPushArray(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitPushArray(b []byte) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
n = len(b)
|
||||||
|
)
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
return errors.New("0 bytes given in pushArray")
|
||||||
|
}
|
||||||
|
if n <= int(OpPushBytes75) {
|
||||||
|
return sb.emit(OpCode(n), b)
|
||||||
|
} else if n < 0x100 {
|
||||||
|
err = sb.emit(OpPushData1, []byte{byte(n)})
|
||||||
|
} else if n < 0x10000 {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
binary.LittleEndian.PutUint16(buf, uint16(n))
|
||||||
|
err = sb.emit(OpPushData2, buf)
|
||||||
|
} else {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(buf, uint32(n))
|
||||||
|
err = sb.emit(OpPushData4, buf)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = sb.buf.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitPushString(str string) error {
|
||||||
|
return sb.emitPushArray([]byte(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitSysCall(api string) error {
|
||||||
|
lenAPI := len(api)
|
||||||
|
if lenAPI == 0 {
|
||||||
|
return errors.New("syscall argument cant be 0")
|
||||||
|
}
|
||||||
|
if lenAPI > 252 {
|
||||||
|
return fmt.Errorf("invalid syscall argument: %s", api)
|
||||||
|
}
|
||||||
|
|
||||||
|
bapi := []byte(api)
|
||||||
|
args := make([]byte, lenAPI+1)
|
||||||
|
args[0] = byte(lenAPI)
|
||||||
|
copy(args, bapi[1:])
|
||||||
|
return sb.emit(OpSysCall, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitPushCall(scriptHash []byte, tailCall bool) error {
|
||||||
|
if len(scriptHash) != 20 {
|
||||||
|
return errors.New("expected a 20 byte long scriptHash (uint160) for pushCall")
|
||||||
|
}
|
||||||
|
op := OpAppCall
|
||||||
|
if tailCall {
|
||||||
|
op = OpTailCall
|
||||||
|
}
|
||||||
|
return sb.emit(op, scriptHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) emitJump(op OpCode, offset int16) error {
|
||||||
|
if op != OpJMP && op != OpJMPIF && op != OpJMPIFNOT && op != OpCall {
|
||||||
|
return fmt.Errorf("invalid jump opcode: %v", op)
|
||||||
|
}
|
||||||
|
return sb.emit(op, []byte{}) // convert to bits?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *ScriptBuilder) dumpOpcode() {
|
||||||
|
buf := sb.buf.Bytes()
|
||||||
|
for i := 0; i < len(buf); i++ {
|
||||||
|
fmt.Printf("OPCODE AT INDEX \t %d \t 0x%2x \t %s \n", i, buf[i], OpCode(buf[i]))
|
||||||
|
}
|
||||||
|
}
|
74
pkg/vm/script_builder_test.go
Normal file
74
pkg/vm/script_builder_test.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmitPush(t *testing.T) {
|
||||||
|
sb := &ScriptBuilder{new(bytes.Buffer)}
|
||||||
|
|
||||||
|
if err := sb.emitPush(OpPush1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sb.buf.Len() != 1 {
|
||||||
|
t.Fatalf("expect buffer len of 1 got %d", sb.buf.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmitPushInt(t *testing.T) {
|
||||||
|
sb := &ScriptBuilder{new(bytes.Buffer)}
|
||||||
|
|
||||||
|
val := -1
|
||||||
|
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want, have := OpPushM1, OpCode(sb.buf.Bytes()[0]); want != have {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
val = 0
|
||||||
|
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want, have := OpPushF, OpCode(sb.buf.Bytes()[1]); want != have {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
val = 1
|
||||||
|
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want, have := OpPush1, OpCode(sb.buf.Bytes()[2]); want != have {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
val = 1000
|
||||||
|
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bInt := big.NewInt(int64(val))
|
||||||
|
if want, have := byte(len(bInt.Bytes())), byte(sb.buf.Bytes()[3]); want != have {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
want := util.ToArrayReverse(bInt.Bytes()) // reverse
|
||||||
|
have := sb.buf.Bytes()[4:]
|
||||||
|
if bytes.Compare(want, have) != 0 {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmitPushString(t *testing.T) {
|
||||||
|
sb := &ScriptBuilder{new(bytes.Buffer)}
|
||||||
|
str := "anthdm"
|
||||||
|
if err := sb.emitPushString(str); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want, have := byte(len(str)), sb.buf.Bytes()[0]; want != have {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
want, have := []byte(str), sb.buf.Bytes()[1:]
|
||||||
|
if bytes.Compare(want, have) != 0 {
|
||||||
|
t.Fatalf("expected %v got %v", want, have)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue