Merge pull request #354 from nspcc-dev/some-vm-opcodes-and-fixes

Implements NZ, PICK, TUCK, XDROP, INVERT. Fixes XTUCK.
This commit is contained in:
Roman Khimov 2019-09-05 19:55:50 +03:00 committed by GitHub
commit c899ec3038
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 313 additions and 30 deletions

View file

@ -132,21 +132,15 @@ func (s *Stack) insert(e, at *Element) *Element {
return e 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. // 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 { func (s *Stack) InsertAt(e *Element, n int) *Element {
before := s.Peek(n) before := s.Peek(n - 1)
if before == nil { if before == nil {
return nil return nil
} }
return s.InsertBefore(e, before) return s.insert(e, before)
} }
// Push pushes the given element on the stack. // Push pushes the given element on the stack.

View file

@ -290,6 +290,26 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.estack.Push(a) v.estack.Push(a)
v.estack.Push(b) 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: case XSWAP:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n < 0 { if n < 0 {
@ -306,13 +326,19 @@ func (v *VM) execute(ctx *Context, op Instruction) {
b.value = aval b.value = aval
} }
case TUCK: case XTUCK:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n <= 0 { if n < 0 {
panic("OTUCK: invalid length") panic("XTUCK: invalid length")
} }
a := v.estack.Dup(0)
v.estack.InsertAt(v.estack.Peek(0), n) 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: case ROT:
c := v.estack.Pop() c := v.estack.Pop()
@ -333,10 +359,27 @@ func (v *VM) execute(ctx *Context, op Instruction) {
case OVER: case OVER:
b := v.estack.Pop() b := v.estack.Pop()
if b == nil {
panic("no top-level element found")
}
a := v.estack.Peek(0) a := v.estack.Peek(0)
if a == nil {
panic("no second element found")
}
v.estack.Push(b) v.estack.Push(b)
v.estack.Push(a) 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: case ROLL:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n < 0 { if n < 0 {
@ -359,6 +402,11 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.estack.PushVal(reflect.DeepEqual(a,b)) v.estack.PushVal(reflect.DeepEqual(a,b))
// Bit operations. // Bit operations.
case INVERT:
// inplace
a := v.estack.Peek(0).BigInt()
a.Not(a)
case AND: case AND:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := 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) v.estack.PushVal(!x)
case NZ: 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. // Object operations.
case NEWARRAY: case NEWARRAY:
@ -529,10 +577,6 @@ func (v *VM) execute(ctx *Context, op Instruction) {
panic("APPEND: not of underlying type Array") panic("APPEND: not of underlying type Array")
} }
case REVERSE:
case REMOVE:
case PACK: case PACK:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n < 0 || n > v.estack.Len() { if n < 0 || n > v.estack.Len() {
@ -546,9 +590,6 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.estack.PushVal(items) v.estack.PushVal(items)
case UNPACK:
panic("TODO")
case PICKITEM: case PICKITEM:
var ( var (
key = v.estack.Pop() key = v.estack.Pop()
@ -671,6 +712,10 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.state = haltState v.state = haltState
} }
case CAT, SUBSTR, LEFT, RIGHT, CHECKSIG, CHECKMULTISIG,
UNPACK, REVERSE, REMOVE:
panic("unimplemented")
// Cryptographic operations. // Cryptographic operations.
case SHA1: case SHA1:
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
@ -690,12 +735,6 @@ func (v *VM) execute(ctx *Context, op Instruction) {
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
v.estack.PushVal(hash.DoubleSha256(b).Bytes()) v.estack.PushVal(hash.DoubleSha256(b).Bytes())
case CHECKSIG:
// pubkey := v.estack.Pop().Bytes()
// sig := v.estack.Pop().Bytes()
case CHECKMULTISIG:
case NOP: case NOP:
// unlucky ^^ // unlucky ^^

View file

@ -23,6 +23,7 @@ func TestInteropHook(t *testing.T) {
EmitOpcode(buf, RET) EmitOpcode(buf, RET)
v.Load(buf.Bytes()) v.Load(buf.Bytes())
v.Run() v.Run()
assert.Equal(t, false, v.state.HasFlag(faultState))
assert.Equal(t, 1, v.estack.Len()) assert.Equal(t, 1, v.estack.Len())
assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value()) 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(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, int64(6), vm.estack.Pop().BigInt().Int64()) 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(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, int64(8), vm.estack.Pop().BigInt().Int64()) 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(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, int64(2), vm.estack.Pop().BigInt().Int64()) 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(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, int64(2), vm.estack.Pop().BigInt().Int64()) 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(4)
vm.estack.PushVal(3) vm.estack.PushVal(3)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, false, vm.estack.Pop().Bool()) assert.Equal(t, false, vm.estack.Pop().Bool())
} }
@ -145,6 +151,7 @@ func TestLTE(t *testing.T) {
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.estack.PushVal(3) vm.estack.PushVal(3)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, true, vm.estack.Pop().Bool()) assert.Equal(t, true, vm.estack.Pop().Bool())
} }
@ -154,6 +161,7 @@ func TestGT(t *testing.T) {
vm.estack.PushVal(9) vm.estack.PushVal(9)
vm.estack.PushVal(3) vm.estack.PushVal(3)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, true, vm.estack.Pop().Bool()) 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.estack.PushVal(3) vm.estack.PushVal(3)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, true, vm.estack.Pop().Bool()) assert.Equal(t, true, vm.estack.Pop().Bool())
} }
@ -174,6 +183,7 @@ func TestDepth(t *testing.T) {
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.estack.PushVal(3) vm.estack.PushVal(3)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, int64(3), vm.estack.Pop().BigInt().Int64()) 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(1)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, false, vm.estack.Pop().Bool()) 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.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, false, vm.estack.Pop().Bool()) assert.Equal(t, false, vm.estack.Pop().Bool())
} }
@ -200,6 +212,7 @@ func TestINC(t *testing.T) {
vm := load(prog) vm := load(prog)
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, big.NewInt(2), vm.estack.Pop().BigInt()) assert.Equal(t, big.NewInt(2), vm.estack.Pop().BigInt())
} }
@ -214,6 +227,7 @@ func TestAppCall(t *testing.T) {
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
elem := vm.estack.Pop() // depth should be 1 elem := vm.estack.Pop() // depth should be 1
assert.Equal(t, int64(1), elem.BigInt().Int64()) assert.Equal(t, int64(1), elem.BigInt().Int64())
} }
@ -228,9 +242,245 @@ func TestSimpleCall(t *testing.T) {
} }
vm := load(prog) vm := load(prog)
vm.Run() vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, result, int(vm.estack.Pop().BigInt().Int64())) 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 { func makeProgram(opcodes ...Instruction) []byte {
prog := make([]byte, len(opcodes)+1) // RET prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ { for i := 0; i < len(opcodes); i++ {