package vm import ( "crypto/sha1" "fmt" "io/ioutil" "log" "math/big" "os" "text/tabwriter" "reflect" "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/util" ) // Mode configures behaviour of the VM. type Mode uint // Available VM Modes. var ( ModeMute Mode = 1 << 0 ) // VM represents the virtual machine. type VM struct { state State // registered interop hooks. interop map[string]InteropFunc // scripts loaded in memory. scripts map[util.Uint160][]byte istack *Stack // invocation stack. estack *Stack // execution stack. astack *Stack // alt stack. // Mute all output after execution. mute bool } // New returns a new VM object ready to load .avm bytecode scripts. func New(mode Mode) *VM { vm := &VM{ interop: make(map[string]InteropFunc), scripts: make(map[util.Uint160][]byte), state: haltState, istack: NewStack("invocation"), estack: NewStack("evaluation"), astack: NewStack("alt"), } if mode == ModeMute { vm.mute = true } // Register native interop hooks. vm.RegisterInteropFunc("Neo.Runtime.Log", runtimeLog) vm.RegisterInteropFunc("Neo.Runtime.Notify", runtimeNotify) return vm } // RegisterInteropFunc will register the given InteropFunc to the VM. func (v *VM) RegisterInteropFunc(name string, f InteropFunc) { v.interop[name] = f } // Estack will return the evaluation stack so interop hooks can utilize this. func (v *VM) Estack() *Stack { return v.estack } // Astack will return the alt stack so interop hooks can utilize this. func (v *VM) Astack() *Stack { return v.astack } // Istack will return the invocation stack so interop hooks can utilize this. func (v *VM) Istack() *Stack { return v.istack } // LoadArgs will load in the arguments used in the Mian entry point. func (v *VM) LoadArgs(method []byte, args []StackItem) { if len(args) > 0 { v.estack.PushVal(args) } if method != nil { v.estack.PushVal(method) } } // PrintOps will print the opcodes of the current loaded program to stdout. func (v *VM) PrintOps() { prog := v.Context().Program() w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t") cursor := "" ip, _ := v.Context().CurrInstr() for i := 0; i < len(prog); i++ { if i == ip { cursor = "<<" } else { cursor = "" } fmt.Fprintf(w, "%d\t0x%2x\t%s\t%s\n", i, prog[i], Instruction(prog[i]).String(), cursor) } w.Flush() } // AddBreakPoint adds a breakpoint to the current context. func (v *VM) AddBreakPoint(n int) { ctx := v.Context() ctx.breakPoints = append(ctx.breakPoints, n) } // AddBreakPointRel adds a breakpoint relative to the current // instruction pointer. func (v *VM) AddBreakPointRel(n int) { ctx := v.Context() v.AddBreakPoint(ctx.ip + n) } // LoadFile will load a program from the given path, ready to execute it. func (v *VM) LoadFile(path string) error { b, err := ioutil.ReadFile(path) if err != nil { return err } v.Load(b) return nil } // Load initializes the VM with the program given. func (v *VM) Load(prog []byte) { // clear all stacks, it could be a reload. v.istack.Clear() v.estack.Clear() v.astack.Clear() v.istack.PushVal(NewContext(prog)) } // LoadScript will load a script from the internal script table. It // will immediately push a new context created from this script to // the invocation stack and starts executing it. func (v *VM) LoadScript(b []byte) { ctx := NewContext(b) v.istack.PushVal(ctx) } // Context returns the current executed context. Nil if there is no context, // which implies no program is loaded. func (v *VM) Context() *Context { if v.istack.Len() == 0 { return nil } return v.istack.Peek(0).value.Value().(*Context) } // PopResult is used to pop the first item of the evaluation stack. This allows // us to test compiler and vm in a bi-directional way. func (v *VM) PopResult() interface{} { return v.estack.Pop().value.Value() } // Stack returns json formatted representation of the given stack. func (v *VM) Stack(n string) string { var s *Stack if n == "astack" { s = v.astack } if n == "istack" { s = v.istack } if n == "estack" { s = v.estack } return buildStackOutput(s) } // Ready return true if the VM ready to execute the loaded program. // Will return false if no program is loaded. func (v *VM) Ready() bool { return v.istack.Len() > 0 } // Run starts the execution of the loaded program. func (v *VM) Run() { if !v.Ready() { fmt.Println("no program loaded") return } v.state = noneState for { switch { case v.state.HasFlag(haltState): if !v.mute { fmt.Println(v.Stack("estack")) } return case v.state.HasFlag(breakState): ctx := v.Context() i, op := ctx.CurrInstr() fmt.Printf("at breakpoint %d (%s)\n", i, op.String()) return case v.state.HasFlag(faultState): fmt.Println("FAULT") return case v.state == noneState: v.Step() } } } // Step 1 instruction in the program. func (v *VM) Step() { ctx := v.Context() op := ctx.Next() v.execute(ctx, op) // re-peek the context as it could been changed during execution. cctx := v.Context() if cctx != nil && cctx.atBreakPoint() { v.state = breakState } } // execute performs an instruction cycle in the VM. Acting on the instruction (opcode). func (v *VM) execute(ctx *Context, op Instruction) { // Instead of polluting the whole VM logic with error handling, we will recover // each panic at a central point, putting the VM in a fault state. defer func() { if err := recover(); err != nil { log.Printf("error encountered at instruction %d (%s)", ctx.ip, op) log.Println(err) v.state = faultState } }() if op >= PUSHBYTES1 && op <= PUSHBYTES75 { b := ctx.readBytes(int(op)) v.estack.PushVal(b) return } switch op { case PUSHM1, PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16: val := int(op) - int(PUSH1) + 1 v.estack.PushVal(val) case PUSH0: v.estack.PushVal(0) case PUSHDATA1: n := ctx.readByte() b := ctx.readBytes(int(n)) v.estack.PushVal(b) case PUSHDATA2: n := ctx.readUint16() b := ctx.readBytes(int(n)) v.estack.PushVal(b) case PUSHDATA4: n := ctx.readUint32() b := ctx.readBytes(int(n)) v.estack.PushVal(b) // Stack operations. case TOALTSTACK: v.astack.Push(v.estack.Pop()) case FROMALTSTACK: v.estack.Push(v.astack.Pop()) case DUPFROMALTSTACK: v.estack.Push(v.astack.Dup(0)) case DUP: v.estack.Push(v.estack.Dup(0)) case SWAP: a := v.estack.Pop() b := v.estack.Pop() v.estack.Push(a) v.estack.Push(b) case TUCK: a := v.estack.Dup(0) if a == nil { panic("no top-level element found") } if v.estack.Len() < 2 { panic("can't TUCK with a one-element stack") } v.estack.InsertAt(a, 2) case CAT: b := v.estack.Pop().Bytes() a := v.estack.Pop().Bytes() ab := append(a, b...) v.estack.PushVal(ab) case SUBSTR: l := int(v.estack.Pop().BigInt().Int64()) o := int(v.estack.Pop().BigInt().Int64()) s := v.estack.Pop().Bytes() v.estack.PushVal(s[o:o+l]) case LEFT: l := int(v.estack.Pop().BigInt().Int64()) s := v.estack.Pop().Bytes() v.estack.PushVal(s[:l]) case RIGHT: l := int(v.estack.Pop().BigInt().Int64()) s := v.estack.Pop().Bytes() v.estack.PushVal(s[len(s)-l:]) case XDROP: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("invalid length") } e := v.estack.RemoveAt(n) if e == nil { panic("bad index") } case XSWAP: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("XSWAP: invalid length") } // Swap values of elements instead of reordering stack elements. if n > 0 { a := v.estack.Peek(n) b := v.estack.Peek(0) aval := a.value bval := b.value a.value = bval b.value = aval } case XTUCK: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("XTUCK: invalid length") } a := v.estack.Dup(0) if a == nil { panic("no top-level element found") } if n > v.estack.Len() { panic("can't push to the position specified") } v.estack.InsertAt(a, n) case ROT: c := v.estack.Pop() b := v.estack.Pop() a := v.estack.Pop() v.estack.Push(b) v.estack.Push(c) v.estack.Push(a) case DEPTH: v.estack.PushVal(v.estack.Len()) case NIP: elem := v.estack.Pop() _ = v.estack.Pop() v.estack.Push(elem) case OVER: b := v.estack.Pop() if b == nil { panic("no top-level element found") } a := v.estack.Peek(0) if a == nil { panic("no second element found") } v.estack.Push(b) v.estack.Push(a) case PICK: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("negative stack item returned") } a := v.estack.Peek(n) if a == nil { panic("no nth element found") } v.estack.Push(a) case ROLL: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("negative stack item returned") } if n > 0 { e := v.estack.RemoveAt(n) if e == nil { panic("bad index") } v.estack.Push(e) } case DROP: v.estack.Pop() case EQUAL: b := v.estack.Pop() a := v.estack.Pop() v.estack.PushVal(reflect.DeepEqual(a,b)) // Bit operations. case INVERT: // inplace a := v.estack.Peek(0).BigInt() a.Not(a) case AND: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).And(b, a)) case OR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Or(b, a)) case XOR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Xor(b, a)) // Numeric operations. case ADD: a := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Add(a, b)) case SUB: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Sub(a, b)) case DIV: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Div(a, b)) case MUL: a := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Mul(a, b)) case MOD: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Mod(a, b)) case SHL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Lsh(a, uint(b.Int64()))) case SHR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Rsh(a, uint(b.Int64()))) case BOOLAND: b := v.estack.Pop().Bool() a := v.estack.Pop().Bool() v.estack.PushVal(a && b) case BOOLOR: b := v.estack.Pop().Bool() a := v.estack.Pop().Bool() v.estack.PushVal(a || b) case NUMEQUAL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) == 0) case NUMNOTEQUAL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) != 0) case LT: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) == -1) case GT: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) == 1) case LTE: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) <= 0) case GTE: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) >= 0) case MIN: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() val := a if a.Cmp(b) == 1 { val = b } v.estack.PushVal(val) case MAX: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() val := a if a.Cmp(b) == -1 { val = b } v.estack.PushVal(val) case WITHIN: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(x) <= 0 && x.Cmp(b) == -1) case INC: x := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Add(x, big.NewInt(1))) case DEC: x := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Sub(x, big.NewInt(1))) case SIGN: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Sign()) case NEGATE: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Neg(x)) case ABS: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Abs(x)) case NOT: x := v.estack.Pop().Bool() v.estack.PushVal(!x) case NZ: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Cmp(big.NewInt(0)) != 0) // Object operations. case NEWARRAY: item := v.estack.Pop() switch t := item.value.(type) { case *BigIntegerItem: n := t.value.Int64() items := make([]StackItem, n) v.estack.PushVal(&ArrayItem{items}) case *StructItem: v.estack.PushVal(&ArrayItem{t.value}) case *ArrayItem: v.estack.PushVal(t) default: panic("NEWARRAY: invalid operand") } case NEWSTRUCT: item := v.estack.Pop() switch t := item.value.(type) { case *BigIntegerItem: n := t.value.Int64() items := make([]StackItem, n) v.estack.PushVal(&StructItem{items}) case *ArrayItem: v.estack.PushVal(&StructItem{t.value}) case *StructItem: v.estack.PushVal(t) default: panic("NEWSTRUCT: invalid operand") } case APPEND: itemElem := v.estack.Pop() arrElem := v.estack.Pop() switch t := arrElem.value.(type) { case *ArrayItem, *StructItem: arr := t.Value().([]StackItem) arr = append(arr, itemElem.value) v.estack.PushVal(arr) case *ByteArrayItem: newVal := append(t.value, itemElem.value.Value().([]byte)...) v.estack.PushVal(newVal) default: panic("APPEND: not of underlying type Array") } case PACK: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 || n > v.estack.Len() { panic("OPACK: invalid length") } items := make([]StackItem, n) for i := 0; i < n; i++ { items[i] = v.estack.Pop().value } v.estack.PushVal(items) case UNPACK: a := v.estack.Pop().Array() l := len(a) for i := l - 1; i >= 0; i-- { v.estack.PushVal(a[i]) } v.estack.PushVal(l) case PICKITEM: var ( key = v.estack.Pop() obj = v.estack.Pop() index = int(key.BigInt().Int64()) ) switch t := obj.value.(type) { // Struct and Array items have their underlying value as []StackItem. case *ArrayItem, *StructItem: arr := t.Value().([]StackItem) if index < 0 || index >= len(arr) { panic("PICKITEM: invalid index") } item := arr[index] v.estack.PushVal(item) default: panic("PICKITEM: unknown type") } case SETITEM: var ( item = v.estack.Pop().value key = v.estack.Pop() obj = v.estack.Pop() index = int(key.BigInt().Int64()) ) switch t := obj.value.(type) { // Struct and Array items have their underlying value as []StackItem. case *ArrayItem, *StructItem: arr := t.Value().([]StackItem) if index < 0 || index >= len(arr) { panic("SETITEM: invalid index") } arr[index] = item default: panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) } case REVERSE: a := v.estack.Peek(0).Array() if len(a) > 1 { for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 { a[i], a[j] = a[j], a[i] } } case REMOVE: key := int(v.estack.Pop().BigInt().Int64()) elem := v.estack.Peek(0) a := elem.Array() a = append(a[:key], a[key+1:]...) elem.value = makeStackItem(a) case ARRAYSIZE: elem := v.estack.Pop() // Cause there is no native (byte) item type here, hence we need to check // the type of the item for array size operations. switch t := elem.value.Value().(type) { case []StackItem: v.estack.PushVal(len(t)) case []uint8: v.estack.PushVal(len(t)) default: panic("ARRAYSIZE: item not of type []StackItem") } case SIZE: elem := v.estack.Pop() arr, ok := elem.value.Value().([]uint8) if !ok { panic("SIZE: item not of type []uint8") } v.estack.PushVal(len(arr)) case JMP, JMPIF, JMPIFNOT: var ( rOffset = int16(ctx.readUint16()) offset = ctx.ip + int(rOffset) - 3 // sizeOf(int16 + uint8) ) if offset < 0 || offset > len(ctx.prog) { panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip)) } cond := true if op > JMP { cond = v.estack.Pop().Bool() if op == JMPIFNOT { cond = !cond } } if cond { ctx.ip = offset } case CALL: v.istack.PushVal(ctx.Copy()) ctx.ip += 2 v.execute(v.Context(), JMP) case SYSCALL: api := ctx.readVarBytes() ifunc, ok := v.interop[string(api)] if !ok { panic(fmt.Sprintf("interop hook (%s) not registered", api)) } if err := ifunc(v); err != nil { panic(fmt.Sprintf("failed to invoke syscall: %s", err)) } case APPCALL, TAILCALL: if len(v.scripts) == 0 { panic("script table is empty") } hash, err := util.Uint160DecodeBytes(ctx.readBytes(20)) if err != nil { panic(err) } script, ok := v.scripts[hash] if !ok { panic("could not find script") } if op == TAILCALL { _ = v.istack.Pop() } v.LoadScript(script) case RET: _ = v.istack.Pop() if v.istack.Len() == 0 { v.state = haltState } case CHECKSIG, VERIFY, CHECKMULTISIG, NEWMAP, HASKEY, KEYS, VALUES: panic("unimplemented") // Cryptographic operations. case SHA1: b := v.estack.Pop().Bytes() sha := sha1.New() sha.Write(b) v.estack.PushVal(sha.Sum(nil)) case SHA256: b := v.estack.Pop().Bytes() v.estack.PushVal(hash.Sha256(b).Bytes()) case HASH160: b := v.estack.Pop().Bytes() v.estack.PushVal(hash.Hash160(b).Bytes()) case HASH256: b := v.estack.Pop().Bytes() v.estack.PushVal(hash.DoubleSha256(b).Bytes()) case NOP: // unlucky ^^ case THROW: panic("THROW") case THROWIFNOT: if !v.estack.Pop().Bool() { panic("THROWIFNOT") } default: panic(fmt.Sprintf("unknown opcode %s", op.String())) } } func init() { log.SetPrefix("NEO-GO-VM > ") log.SetFlags(0) }