diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index fd1bd275f..a08882d8d 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -132,21 +132,15 @@ func (s *Stack) insert(e, at *Element) *Element { return e } -// InsertBefore will insert the element before the mark on the stack. -func (s *Stack) InsertBefore(e, mark *Element) *Element { - if mark == nil { - return nil - } - return s.insert(e, mark.prev) -} - // InsertAt will insert the given item (n) deep on the stack. +// Be very careful using it and _always_ check both e and n before invocation +// as it will silently do wrong things otherwise. func (s *Stack) InsertAt(e *Element, n int) *Element { - before := s.Peek(n) + before := s.Peek(n - 1) if before == nil { return nil } - return s.InsertBefore(e, before) + return s.insert(e, before) } // Push pushes the given element on the stack. diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index a7d9e4c3b..422e8e9e6 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -290,6 +290,26 @@ func (v *VM) execute(ctx *Context, op Instruction) { 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 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 { @@ -306,13 +326,19 @@ func (v *VM) execute(ctx *Context, op Instruction) { b.value = aval } - case TUCK: + case XTUCK: n := int(v.estack.Pop().BigInt().Int64()) - if n <= 0 { - panic("OTUCK: invalid length") + if n < 0 { + panic("XTUCK: invalid length") } - - v.estack.InsertAt(v.estack.Peek(0), n) + 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() @@ -333,10 +359,27 @@ func (v *VM) execute(ctx *Context, op Instruction) { 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 { @@ -359,6 +402,11 @@ func (v *VM) execute(ctx *Context, op Instruction) { 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() @@ -499,8 +547,8 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.PushVal(!x) case NZ: - panic("todo NZ") - // x := v.estack.Pop().BigInt() + x := v.estack.Pop().BigInt() + v.estack.PushVal(x.Cmp(big.NewInt(0)) != 0) // Object operations. case NEWARRAY: @@ -529,10 +577,6 @@ func (v *VM) execute(ctx *Context, op Instruction) { panic("APPEND: not of underlying type Array") } - case REVERSE: - - case REMOVE: - case PACK: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 || n > v.estack.Len() { @@ -546,9 +590,6 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.PushVal(items) - case UNPACK: - panic("TODO") - case PICKITEM: var ( key = v.estack.Pop() @@ -671,6 +712,10 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.state = haltState } + case CAT, SUBSTR, LEFT, RIGHT, CHECKSIG, CHECKMULTISIG, + UNPACK, REVERSE, REMOVE: + panic("unimplemented") + // Cryptographic operations. case SHA1: b := v.estack.Pop().Bytes() @@ -690,12 +735,6 @@ func (v *VM) execute(ctx *Context, op Instruction) { b := v.estack.Pop().Bytes() v.estack.PushVal(hash.DoubleSha256(b).Bytes()) - case CHECKSIG: - // pubkey := v.estack.Pop().Bytes() - // sig := v.estack.Pop().Bytes() - - case CHECKMULTISIG: - case NOP: // unlucky ^^ diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 257271056..7abb7bc72 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -23,6 +23,7 @@ func TestInteropHook(t *testing.T) { EmitOpcode(buf, RET) v.Load(buf.Bytes()) v.Run() + assert.Equal(t, false, v.state.HasFlag(faultState)) assert.Equal(t, 1, v.estack.Len()) assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value()) } @@ -100,6 +101,7 @@ func TestAdd(t *testing.T) { vm.estack.PushVal(4) vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, int64(6), vm.estack.Pop().BigInt().Int64()) } @@ -109,6 +111,7 @@ func TestMul(t *testing.T) { vm.estack.PushVal(4) vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, int64(8), vm.estack.Pop().BigInt().Int64()) } @@ -118,6 +121,7 @@ func TestDiv(t *testing.T) { vm.estack.PushVal(4) vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, int64(2), vm.estack.Pop().BigInt().Int64()) } @@ -127,6 +131,7 @@ func TestSub(t *testing.T) { vm.estack.PushVal(4) vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, int64(2), vm.estack.Pop().BigInt().Int64()) } @@ -136,6 +141,7 @@ func TestLT(t *testing.T) { vm.estack.PushVal(4) vm.estack.PushVal(3) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, false, vm.estack.Pop().Bool()) } @@ -145,6 +151,7 @@ func TestLTE(t *testing.T) { vm.estack.PushVal(2) vm.estack.PushVal(3) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, true, vm.estack.Pop().Bool()) } @@ -154,6 +161,7 @@ func TestGT(t *testing.T) { vm.estack.PushVal(9) vm.estack.PushVal(3) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, true, vm.estack.Pop().Bool()) } @@ -164,6 +172,7 @@ func TestGTE(t *testing.T) { vm.estack.PushVal(3) vm.estack.PushVal(3) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, true, vm.estack.Pop().Bool()) } @@ -174,6 +183,7 @@ func TestDepth(t *testing.T) { vm.estack.PushVal(2) vm.estack.PushVal(3) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, int64(3), vm.estack.Pop().BigInt().Int64()) } @@ -183,6 +193,7 @@ func TestNumEqual(t *testing.T) { vm.estack.PushVal(1) vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, false, vm.estack.Pop().Bool()) } @@ -192,6 +203,7 @@ func TestNumNotEqual(t *testing.T) { vm.estack.PushVal(2) vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, false, vm.estack.Pop().Bool()) } @@ -200,6 +212,7 @@ func TestINC(t *testing.T) { vm := load(prog) vm.estack.PushVal(1) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, big.NewInt(2), vm.estack.Pop().BigInt()) } @@ -214,6 +227,7 @@ func TestAppCall(t *testing.T) { vm.estack.PushVal(2) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) elem := vm.estack.Pop() // depth should be 1 assert.Equal(t, int64(1), elem.BigInt().Int64()) } @@ -228,9 +242,245 @@ func TestSimpleCall(t *testing.T) { } vm := load(prog) vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, result, int(vm.estack.Pop().BigInt().Int64())) } +func TestNZtrue(t *testing.T) { + prog := makeProgram(NZ) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, true, vm.estack.Pop().Bool()) +} + +func TestNZfalse(t *testing.T) { + prog := makeProgram(NZ) + vm := load(prog) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, false, vm.estack.Pop().Bool()) +} + +func TestPICKbadNoitem(t *testing.T) { + prog := makeProgram(PICK) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestPICKbadNegative(t *testing.T) { + prog := makeProgram(PICK) + vm := load(prog) + vm.estack.PushVal(-1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestPICKgood(t *testing.T) { + prog := makeProgram(PICK) + result := 2 + vm := load(prog) + vm.estack.PushVal(0) + vm.estack.PushVal(1) + vm.estack.PushVal(result) + vm.estack.PushVal(3) + vm.estack.PushVal(4) + vm.estack.PushVal(5) + vm.estack.PushVal(3) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(result), vm.estack.Pop().BigInt().Int64()) +} + +func TestXTUCKbadNoitem(t *testing.T) { + prog := makeProgram(XTUCK) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestXTUCKbadNoN(t *testing.T) { + prog := makeProgram(XTUCK) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestXTUCKbadNegative(t *testing.T) { + prog := makeProgram(XTUCK) + vm := load(prog) + vm.estack.PushVal(-1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestXTUCKgood(t *testing.T) { + prog := makeProgram(XTUCK) + topelement := 5 + xtuckdepth := 3 + vm := load(prog) + vm.estack.PushVal(0) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.estack.PushVal(3) + vm.estack.PushVal(4) + vm.estack.PushVal(topelement) + vm.estack.PushVal(xtuckdepth) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(topelement), vm.estack.Peek(0).BigInt().Int64()) + assert.Equal(t, int64(topelement), vm.estack.Peek(xtuckdepth).BigInt().Int64()) +} + +func TestTUCKbadNoitems(t *testing.T) { + prog := makeProgram(TUCK) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestTUCKbadNoitem(t *testing.T) { + prog := makeProgram(TUCK) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestTUCKgood(t *testing.T) { + prog := makeProgram(TUCK) + vm := load(prog) + vm.estack.PushVal(42) + vm.estack.PushVal(34) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(34), vm.estack.Peek(0).BigInt().Int64()) + assert.Equal(t, int64(42), vm.estack.Peek(1).BigInt().Int64()) + assert.Equal(t, int64(34), vm.estack.Peek(2).BigInt().Int64()) +} + +func TestTUCKgood2(t *testing.T) { + prog := makeProgram(TUCK) + vm := load(prog) + vm.estack.PushVal(11) + vm.estack.PushVal(42) + vm.estack.PushVal(34) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(34), vm.estack.Peek(0).BigInt().Int64()) + assert.Equal(t, int64(42), vm.estack.Peek(1).BigInt().Int64()) + assert.Equal(t, int64(34), vm.estack.Peek(2).BigInt().Int64()) + assert.Equal(t, int64(11), vm.estack.Peek(3).BigInt().Int64()) +} + +func TestOVERbadNoitem(t *testing.T) { + prog := makeProgram(OVER) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestOVERbadNoitems(t *testing.T) { + prog := makeProgram(OVER) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestOVERgood(t *testing.T) { + prog := makeProgram(OVER) + vm := load(prog) + vm.estack.PushVal(42) + vm.estack.PushVal(34) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(42), vm.estack.Peek(0).BigInt().Int64()) + assert.Equal(t, int64(34), vm.estack.Peek(1).BigInt().Int64()) + assert.Equal(t, int64(42), vm.estack.Peek(2).BigInt().Int64()) + assert.Equal(t, 3, vm.estack.Len()) +} + +func TestXDROPbadNoitem(t *testing.T) { + prog := makeProgram(XDROP) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestXDROPbadNoN(t *testing.T) { + prog := makeProgram(XDROP) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestXDROPbadNegative(t *testing.T) { + prog := makeProgram(XDROP) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(-1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestXDROPgood(t *testing.T) { + prog := makeProgram(XDROP) + vm := load(prog) + vm.estack.PushVal(0) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 2, vm.estack.Len()) + assert.Equal(t, int64(2), vm.estack.Peek(0).BigInt().Int64()) + assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) +} + +func TestINVERTbadNoitem(t *testing.T) { + prog := makeProgram(INVERT) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestINVERTgood1(t *testing.T) { + prog := makeProgram(INVERT) + vm := load(prog) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(-1), vm.estack.Peek(0).BigInt().Int64()) +} + +func TestINVERTgood2(t *testing.T) { + prog := makeProgram(INVERT) + vm := load(prog) + vm.estack.PushVal(-1) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(0), vm.estack.Peek(0).BigInt().Int64()) +} + +func TestINVERTgood3(t *testing.T) { + prog := makeProgram(INVERT) + vm := load(prog) + vm.estack.PushVal(0x69) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, int64(-0x6A), vm.estack.Peek(0).BigInt().Int64()) +} + func makeProgram(opcodes ...Instruction) []byte { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ {