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:
Anthony De Meulemeester 2018-02-09 17:08:50 +01:00 committed by GitHub
parent b6d8271b8d
commit f7d57e4e49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 861 additions and 49 deletions

View file

@ -50,8 +50,9 @@ The project will exist out of the following packages:
| 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) |
| vm | started | [@revett](https://github.com/revett) |
| cli | started | [@revett](https://github.com/revett) |
# Getting Started

View file

@ -1 +1 @@
0.8.0
0.9.0

View file

@ -1,43 +1,21 @@
package main
import (
"flag"
"strings"
"os"
"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() {
flag.Parse()
ctl := cli.NewApp()
ctl.Name = "neo-go"
opts := network.StartOpts{
Seeds: parseSeeds(*seed),
TCP: *tcp,
RPC: *rpc,
ctl.Commands = []cli.Command{
server.NewCommand(),
smartcontract.NewCommand(),
}
s := network.NewServer(network.NetMode(*net))
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
ctl.Run(os.Args)
}

56
cli/server/server.go Normal file
View 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
}

View 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
View file

@ -1,5 +1,5 @@
hash: 2b8debf7936e789545da367433ddbf5f0e3bb54658340d6a412970bca25e6335
updated: 2018-02-05T16:11:44.616743+01:00
hash: 5cb2fb077aa0e57c788a99f9ded4bf2d421a04f12f6b8436234d12e93f418f36
updated: 2018-02-09T16:49:32.538502496+01:00
imports:
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
@ -18,4 +18,6 @@ imports:
- leveldb/storage
- leveldb/table
- leveldb/util
- name: github.com/urfave/cli
version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1
testImports: []

View file

@ -1,5 +1,7 @@
package: github.com/CityOfZion/neo-go
import:
- package: github.com/syndtr/goleveldb/leveldb
- package: github.com/syndtr/goleveldb
subpackages:
- leveldb
- package: github.com/urfave/cli
version: ^1.20.0

View file

@ -28,8 +28,8 @@ type NetMode uint32
// String implements the stringer interface.
func (n NetMode) String() string {
switch n {
case ModeDevNet:
return "devnet"
case ModePrivNet:
return "privnet"
case ModeTestNet:
return "testnet"
case ModeMainNet:
@ -42,8 +42,8 @@ func (n NetMode) String() string {
// Values used for the magic field, according to the docs.
const (
ModeMainNet NetMode = 0x00746e41 // 7630401
ModeTestNet = 0x74746e41 // 1953787457
ModeDevNet = 56753 // docker privnet
ModeTestNet NetMode = 0x74746e41 // 1953787457
ModePrivNet NetMode = 56753 // docker privnet
)
// Message is the complete message send between nodes.

View file

@ -105,7 +105,7 @@ func (m protectedHashmap) has(h util.Uint256) bool {
func NewServer(net NetMode) *Server {
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)
}

View file

@ -9,7 +9,7 @@ import (
// TODO this should be moved to localPeer test.
func TestHandleVersionFailWrongPort(t *testing.T) {
s := NewServer(ModeDevNet)
s := NewServer(ModePrivNet)
go s.loop()
p := NewLocalPeer(s)
@ -21,7 +21,7 @@ func TestHandleVersionFailWrongPort(t *testing.T) {
}
func TestHandleVersionFailIdenticalNonce(t *testing.T) {
s := NewServer(ModeDevNet)
s := NewServer(ModePrivNet)
go s.loop()
p := NewLocalPeer(s)
@ -33,7 +33,7 @@ func TestHandleVersionFailIdenticalNonce(t *testing.T) {
}
func TestHandleVersion(t *testing.T) {
s := NewServer(ModeDevNet)
s := NewServer(ModePrivNet)
go s.loop()
p := NewLocalPeer(s)

View file

@ -23,7 +23,7 @@ func Uint256DecodeFromString(s string) (Uint256, error) {
return val, err
}
b = ReverseByteSlice(b)
b = ToArrayReverse(b)
return Uint256DecodeFromBytes(b)
}
@ -43,7 +43,8 @@ func Uint256DecodeFromBytes(b []byte) (Uint256, error) {
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))
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {

View file

194
pkg/vm/compiler.go Normal file
View 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
View 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
View 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
View 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
View 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]))
}
}

View 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)
}
}