Merge pull request #266 from dauTT/dauTT/vm-bitwise-opcodes-191

VM: Implement bitwise opcodes, closes #191

Implemented following opcodes:
    AND
    XOR
    OR
    INVERT
This commit is contained in:
Roman Khimov 2019-08-12 12:28:51 +03:00 committed by GitHub
commit b50411b057
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 259 additions and 2 deletions

View file

@ -143,18 +143,49 @@ func (i *Int) Gt(s *Int) bool {
return i.Value().Cmp(s.Value()) == 1 return i.Value().Cmp(s.Value()) == 1
} }
// Invert returns an Integer whose underlying value is the bitwise complement
// of the original value.
func (i *Int) Invert() (*Int, error) {
res := new(big.Int).Not(i.Value())
return NewInt(res)
}
// And returns an Integer whose underlying value is the result of the
// application of the bitwise AND operator to the two original integers'
// values.
func (i *Int) And(s *Int) (*Int, error) {
res := new(big.Int).And(i.Value(), s.Value())
return NewInt(res)
}
// Or returns an Integer whose underlying value is the result of the
// application of the bitwise OR operator to the two original integers'
// values.
func (i *Int) Or(s *Int) (*Int, error) {
res := new(big.Int).Or(i.Value(), s.Value())
return NewInt(res)
}
// Xor returns an Integer whose underlying value is the result of the
// application of the bitwise XOR operator to the two original integers'
// values.
func (i *Int) Xor(s *Int) (*Int, error) {
res := new(big.Int).Xor(i.Value(), s.Value())
return NewInt(res)
}
// Hash overrides the default abstract hash method. // Hash overrides the default abstract hash method.
func (i *Int) Hash() (string, error) { func (i *Int) Hash() (string, error) {
data := fmt.Sprintf("%T %v", i, i.Value()) data := fmt.Sprintf("%T %v", i, i.Value())
return KeyGenerator([]byte(data)) return KeyGenerator([]byte(data))
}
// Min returns the mininum between two integers. // Min returns the mininum between two integers.
func Min(a *Int, b *Int) *Int { func Min(a *Int, b *Int) *Int {
if a.Lte(b) { if a.Lte(b) {
return a return a
} }
return b return b
} }
// Max returns the maximun between two integers. // Max returns the maximun between two integers.

View file

@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack"
type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error)
var opFunc = map[stack.Instruction]stackInfo{ var opFunc = map[stack.Instruction]stackInfo{
stack.XOR: Xor,
stack.OR: Or,
stack.AND: And,
stack.INVERT: Invert,
stack.MIN: Min, stack.MIN: Min,
stack.MAX: Max, stack.MAX: Max,
stack.WITHIN: Within, stack.WITHIN: Within,

View file

@ -15,3 +15,88 @@ func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r
ctx.Estack.Push(itemA.Equals(itemB)) ctx.Estack.Push(itemA.Equals(itemB))
return NONE, nil return NONE, nil
} }
// Invert pops an integer x off of the stack and
// pushes an integer on the stack whose value
// is the bitwise complement of the value of x.
// Returns an error if the popped value is not an integer or
// if the bitwise complement cannot be taken.
func Invert(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
i, err := ctx.Estack.PopInt()
if err != nil {
return FAULT, err
}
inv, err := i.Invert()
if err != nil {
return FAULT, err
}
ctx.Estack.Push(inv)
return NONE, nil
}
// And pops two integer off of the stack and
// pushes an integer onto the stack whose value
// is the result of the application of the bitwise AND
// operator to the two original integers' values.
// Returns an error if either items cannot be casted to an integer.
func And(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
operandA, operandB, err := popTwoIntegers(ctx)
if err != nil {
return FAULT, err
}
res, err := operandA.And(operandB)
if err != nil {
return FAULT, err
}
ctx.Estack.Push(res)
return NONE, nil
}
// Or pops two integer off of the stack and
// pushes an integer onto the stack whose value
// is the result of the application of the bitwise OR
// operator to the two original integers' values.
// Returns an error if either items cannot be casted to an integer.
func Or(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
operandA, operandB, err := popTwoIntegers(ctx)
if err != nil {
return FAULT, err
}
res, err := operandA.Or(operandB)
if err != nil {
return FAULT, err
}
ctx.Estack.Push(res)
return NONE, nil
}
// Xor pops two integer off of the stack and
// pushes an integer onto the stack whose value
// is the result of the application of the bitwise XOR
// operator to the two original integers' values.
// Returns an error if either items cannot be casted to an integer.
func Xor(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
operandA, operandB, err := popTwoIntegers(ctx)
if err != nil {
return FAULT, err
}
res, err := operandA.Xor(operandB)
if err != nil {
return FAULT, err
}
ctx.Estack.Push(res)
return NONE, nil
}

View file

@ -0,0 +1,137 @@
package vm
import (
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/vm/stack"
"github.com/stretchr/testify/assert"
)
func TestInvertOp(t *testing.T) {
v := VM{}
// 0000 00110 = 5
a, err := stack.NewInt(big.NewInt(5))
assert.Nil(t, err)
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a)
// 1111 11001 = -6 (two complement representation)
v.executeOp(stack.INVERT, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopInt()
assert.Nil(t, err)
assert.Equal(t, int64(-6), item.Value().Int64())
}
func TestAndOp(t *testing.T) {
v := VM{}
// 110001 = 49
a, err := stack.NewInt(big.NewInt(49))
assert.Nil(t, err)
// 100011 = 35
b, err := stack.NewInt(big.NewInt(35))
assert.Nil(t, err)
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a).Push(b)
// 100001 = 33
v.executeOp(stack.AND, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopInt()
assert.Nil(t, err)
assert.Equal(t, int64(33), item.Value().Int64())
}
func TestOrOp(t *testing.T) {
v := VM{}
// 110001 = 49
a, err := stack.NewInt(big.NewInt(49))
assert.Nil(t, err)
// 100011 = 35
b, err := stack.NewInt(big.NewInt(35))
assert.Nil(t, err)
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a).Push(b)
// 110011 = 51 (49 OR 35)
v.executeOp(stack.OR, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopInt()
assert.Nil(t, err)
assert.Equal(t, int64(51), item.Value().Int64())
}
func TestXorOp(t *testing.T) {
v := VM{}
// 110001 = 49
a, err := stack.NewInt(big.NewInt(49))
assert.Nil(t, err)
// 100011 = 35
b, err := stack.NewInt(big.NewInt(35))
assert.Nil(t, err)
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a).Push(b)
// 010010 = 18 (49 XOR 35)
v.executeOp(stack.XOR, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopInt()
assert.Nil(t, err)
assert.Equal(t, int64(18), item.Value().Int64())
}
func TestEqualOp(t *testing.T) {
v := VM{}
a, err := stack.NewInt(big.NewInt(10))
assert.Nil(t, err)
b, err := stack.NewInt(big.NewInt(10))
assert.Nil(t, err)
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a).Push(b)
v.executeOp(stack.EQUAL, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopBoolean()
assert.Nil(t, err)
assert.Equal(t, true, item.Value())
}