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) |
|
||||
| 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
|
||||
|
||||
|
|
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
|
||||
|
||||
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
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
|
||||
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: []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
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