Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Bitwise Logical Opcodes #48

Merged
merged 5 commits into from
May 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions vm/op_codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const (
GtEq
ShiftL
ShiftR
BitwiseAnd
BitwiseOr
BitwiseXor
BitwiseNot
NoOp
Jmp
JmpTrue
Expand Down Expand Up @@ -107,8 +111,12 @@ var OpCodes = []OpCode{
{Gt, "gt", 0, nil, 1, 2},
{LtEq, "lte", 0, nil, 1, 2},
{GtEq, "gte", 0, nil, 1, 2},
{ShiftL, "shiftl", 1, []int{BYTE}, 1, 2},
{ShiftR, "shiftl", 1, []int{BYTE}, 1, 2},
{ShiftL, "shiftl", 0, nil, 1, 2},
{ShiftR, "shiftr", 0, nil, 1, 2},
{BitwiseAnd, "bitwiseand", 0, nil, 1, 2},
{BitwiseOr, "bitwiseor", 0, nil, 1, 2},
{BitwiseXor, "bitwisexor", 0, nil, 1, 2},
{BitwiseNot, "bitwisenot", 0, nil, 1, 2},
{NoOp, "nop", 0, nil, 1, 1},
{Jmp, "jmp", 1, []int{LABEL}, 1, 1},
{JmpTrue, "jmptrue", 1, []int{LABEL}, 1, 1},
Expand Down
2 changes: 1 addition & 1 deletion vm/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func NewStack() *Stack {
return &Stack{
Stack: nil,
memoryUsage: 0,
memoryMax: 1000000, // Max 1000000 Bytes = 1MB
memoryMax: 600000000, // Max 6000000 Bytes = 6MB
}
}

Expand Down
7 changes: 7 additions & 0 deletions vm/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ func BigIntToUInt16(value big.Int) (uint16, error) {
return ByteArrayToUI16(bytes)
}

func BigIntToUInt(value big.Int) (uint, error) {
if len(value.Bytes()) > 4 {
return 0, fmt.Errorf("value cannot be greater than 32bits")
}
return uint(value.Uint64()), nil
}

func ByteArrayToUI16(element []byte) (uint16, error) {
if bytes.Equal([]byte{}, element) {
return 0, nil
Expand Down
46 changes: 44 additions & 2 deletions vm/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ func TestUtils_ByteArrayToInt(t *testing.T) {
}

func TestUtils_Uint16ToBigInt_Minimum(t *testing.T) {
var value uint16 = 0
result := UInt16ToBigInt(value)
result := UInt16ToBigInt(0)
assert.Equal(t, result.Cmp(big.NewInt(0)), 0)
}

Expand Down Expand Up @@ -136,6 +135,49 @@ func TestUtils_BigIntToUInt16_Greater_Than_UInt16(t *testing.T) {
assert.Equal(t, err.Error(), fmt.Sprintf("value cannot be greater than %v", UINT16_MAX))
}

// big.Int to uint
// ---------------

func TestUtils_BigIntToUInt_Zero(t *testing.T) {
value := big.NewInt(0)
result, err := BigIntToUInt(*value)
assert.NilError(t, err)
assert.Equal(t, result, uint(0))
}

func TestUtils_BigIntToUInt_Positive(t *testing.T) {
value := big.NewInt(10)
result, err := BigIntToUInt(*value)
assert.NilError(t, err)
assert.Equal(t, result, uint(10))
}

func TestUtils_BigIntToUInt_Negative(t *testing.T) {
value := big.NewInt(-10)
result, err := BigIntToUInt(*value)
assert.NilError(t, err)
assert.Equal(t, result, uint(10))
}

func TestUtils_BigIntToUInt_Max(t *testing.T) {
var max uint = 4294967295
value := big.NewInt(int64(max))
result, err := BigIntToUInt(*value)
assert.NilError(t, err)
assert.Equal(t, result, max)
}

func TestUtils_BigIntToUInt_Overflow(t *testing.T) {
max := int64(4294967295) + 1
value := big.NewInt(max)
_, err := BigIntToUInt(*value)

assert.Equal(t, err.Error(), "value cannot be greater than 32bits")
}

// big.Int to []byte
// -----------------

func TestUtils_BigIntToByteArray_Zero(t *testing.T) {
value := big.NewInt(0)
result := BigIntToByteArray(*value)
Expand Down
147 changes: 104 additions & 43 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Context interface {
GetSig1() [64]byte
}

// VM is a stack-based virtual machine and executes the contract code sequentially.
type VM struct {
code []byte
pc int // Program counter
Expand All @@ -38,6 +39,7 @@ type VM struct {
context Context
}

// NewVM creates a new Bazo virtual machine with the context received from Bazo miner.
func NewVM(context Context) VM {
return VM{
code: []byte{},
Expand All @@ -49,6 +51,7 @@ func NewVM(context Context) VM {
}
}

// NewTestVM creates a new Bazo virtual machine with the test contract code.
func NewTestVM(byteCode []byte) VM {
return VM{
code: []byte{},
Expand Down Expand Up @@ -124,6 +127,7 @@ func (vm *VM) trace() {
fmt.Printf("%04d: %-6s %v \n", addr, opCode.Name, formattedArgs)
}

// Exec executes the contract code and stores the result on evaluation stack.
func (vm *VM) Exec(trace bool) bool {
vm.code = vm.context.GetContract()
vm.fee = vm.context.GetFee()
Expand Down Expand Up @@ -323,50 +327,27 @@ func (vm *VM) Exec(trace bool) bool {
}

case Add:
right, rerr := vm.PopSignedBigInt(opCode)
left, lerr := vm.PopSignedBigInt(opCode)

if !vm.checkErrors(opCode.Name, rerr, lerr) {
return false
}
isSuccess := vm.evaluateBigIntOperation(opCode, func(left *big.Int, right *big.Int) {
left.Add(left, right)
})

left.Add(&left, &right)
err := vm.evaluationStack.Push(SignedByteArrayConversion(left))

if err != nil {
vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
if !isSuccess {
return false
}

case Sub:
right, rerr := vm.PopSignedBigInt(opCode)
left, lerr := vm.PopSignedBigInt(opCode)

if !vm.checkErrors(opCode.Name, rerr, lerr) {
return false
}
isSuccess := vm.evaluateBigIntOperation(opCode, func(left *big.Int, right *big.Int) {
left.Sub(left, right)
})

left.Sub(&left, &right)
err := vm.evaluationStack.Push(SignedByteArrayConversion(left))

if err != nil {
vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
if !isSuccess {
return false
}

case Mul:
right, rerr := vm.PopSignedBigInt(opCode)
left, lerr := vm.PopSignedBigInt(opCode)
isSuccess := vm.evaluateBigIntOperation(opCode, func(left *big.Int, right *big.Int) {
left.Mul(left, right)
})

if !vm.checkErrors(opCode.Name, rerr, lerr) {
return false
}

left.Mul(&left, &right)
err := vm.evaluationStack.Push(SignedByteArrayConversion(left))

if err != nil {
vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
if !isSuccess {
return false
}

Expand Down Expand Up @@ -521,34 +502,90 @@ func (vm *VM) Exec(trace bool) bool {
return false
}
case ShiftL:
nrOfShifts, errArg := vm.fetch(opCode.Name)
shiftsBigInt, err := vm.PopSignedBigInt(opCode)
tos, errStack := vm.PopSignedBigInt(opCode)

if !vm.checkErrors(opCode.Name, errArg, errStack) {
if !vm.checkErrors(opCode.Name, err, errStack) {
return false
}

if shiftsBigInt.Sign() == -1 {
vm.pushError(opCode, fmt.Errorf("negative shift operand is not allowed"))
return false
}

tos.Lsh(&tos, uint(nrOfShifts))
nrOfShifts, err := BigIntToUInt(shiftsBigInt)
if !vm.checkErrors(opCode.Name, err) {
return false
}

tos.Lsh(&tos, nrOfShifts)
err = vm.evaluationStack.Push(SignedByteArrayConversion(tos))

if err != nil {
vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
_ = vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
return false
}

case ShiftR:
nrOfShifts, errArg := vm.fetch(opCode.Name)
shiftsBigInt, err := vm.PopSignedBigInt(opCode)
tos, errStack := vm.PopSignedBigInt(opCode)

if !vm.checkErrors(opCode.Name, errArg, errStack) {
if !vm.checkErrors(opCode.Name, err, errStack) {
return false
}

if shiftsBigInt.Sign() == -1 {
vm.pushError(opCode, fmt.Errorf("negative shift operand is not allowed"))
return false
}

tos.Rsh(&tos, uint(nrOfShifts))
nrOfShifts, err := BigIntToUInt(shiftsBigInt)
if !vm.checkErrors(opCode.Name, err) {
return false
}

tos.Rsh(&tos, nrOfShifts)
err = vm.evaluationStack.Push(SignedByteArrayConversion(tos))

if err != nil {
vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
_ = vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
return false
}
case BitwiseAnd:
isSuccess := vm.evaluateBigIntOperation(opCode, func(left *big.Int, right *big.Int) {
left.And(left, right)
})

if !isSuccess {
return false
}
case BitwiseOr:
isSuccess := vm.evaluateBigIntOperation(opCode, func(left *big.Int, right *big.Int) {
left.Or(left, right)
})

if !isSuccess {
return false
}
case BitwiseXor:
isSuccess := vm.evaluateBigIntOperation(opCode, func(left *big.Int, right *big.Int) {
left.Xor(left, right)
})

if !isSuccess {
return false
}
case BitwiseNot:
bigInt, err := vm.PopSignedBigInt(opCode)
if !vm.checkErrors(opCode.Name, err) {
return false
}

bigInt.Not(&bigInt)
err = vm.evaluationStack.Push(SignedByteArrayConversion(bigInt))

if !vm.checkErrors(opCode.Name, err) {
return false
}

Expand Down Expand Up @@ -1362,6 +1399,7 @@ func (vm *VM) pushError(opCode OpCode, err error) {
_ = vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
}

// PopBytes pops bytes from the evaluation stack.
func (vm *VM) PopBytes(opCode OpCode) (elements []byte, err error) {
bytes, err := vm.evaluationStack.Pop()
if err != nil {
Expand All @@ -1380,6 +1418,7 @@ func (vm *VM) PopBytes(opCode OpCode) (elements []byte, err error) {
return bytes, nil
}

// PopSignedBigInt pops bytes from evaluation stack and convert it to a big integer with sign.
func (vm *VM) PopSignedBigInt(opCode OpCode) (bigInt big.Int, err error) {
bytes, err := vm.evaluationStack.Pop()
if err != nil {
Expand All @@ -1399,6 +1438,7 @@ func (vm *VM) PopSignedBigInt(opCode OpCode) (bigInt big.Int, err error) {
return result, err
}

// PopUnsignedBigInt pops bytes from evaluation stack and convert it to an unsigned big integer.
func (vm *VM) PopUnsignedBigInt(opCode OpCode) (bigInt big.Int, err error) {
bytes, err := vm.evaluationStack.Pop()
if err != nil {
Expand Down Expand Up @@ -1435,6 +1475,7 @@ func (vm *VM) PeekEvalStack() [][]byte {
return copiedStack
}

// GetErrorMsg peeks bytes from evaluation stack and returns the error message.
func (vm *VM) GetErrorMsg() string {
tos, err := vm.evaluationStack.PeekBytes()
if err != nil {
Expand All @@ -1443,6 +1484,26 @@ func (vm *VM) GetErrorMsg() string {
return string(tos)
}

type bigIntAction func(left *big.Int, right *big.Int)

func (vm *VM) evaluateBigIntOperation(opCode OpCode, exec bigIntAction) bool {
right, rerr := vm.PopSignedBigInt(opCode)
left, lerr := vm.PopSignedBigInt(opCode)

if !vm.checkErrors(opCode.Name, rerr, lerr) {
return false
}

exec(&left, &right)
err := vm.evaluationStack.Push(SignedByteArrayConversion(left))

if err != nil {
_ = vm.evaluationStack.Push([]byte(opCode.Name + ": " + err.Error()))
return false
}
return true
}

func (vm *VM) evaluateRelationalComp(opCode OpCode, expectedResult ...int) bool {
right, rerr := vm.PopBytes(opCode)
left, lerr := vm.PopBytes(opCode)
Expand Down
Loading