Skip to content
This repository has been archived by the owner on Nov 15, 2021. It is now read-only.

Commit

Permalink
Implementing functionality for map/dict in neo-vm
Browse files Browse the repository at this point in the history
  • Loading branch information
localhuman committed Mar 17, 2018
1 parent 815cbb9 commit b5693bd
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 98 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes to this project are documented in this file.

[0.6.2-dev] in progress
-----------------------
- ...
- Implementing interop type ``MAP`` along with new opcodes ``NEWMAP HASKEY KEYS VALUES`` and modify ``ARRAYSIZE PICKITEM SETITEM REMOVE`` to support ``MAP`` as `per PR here <https://github.com/neo-project/neo-vm/pull/28>__`


[0.6.1] 2018-03-16
Expand Down
2 changes: 0 additions & 2 deletions neo/SmartContract/tests/sc_vm_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from boa.interop.Neo.App import DynamicAppCall


# unfortunately neo-boa doesn't support importing VMFault

def Main(test, args):
if test == 1:
# test_invalid_array_index"
Expand Down
4 changes: 2 additions & 2 deletions neo/SmartContract/tests/test_vm_error_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ def test_invalid_array_index(self, mocked_logger):
@patch('logzero.logger.error')
def test_negative_array_indexing(self, mocked_logger):
tx, results, total_ops, engine = TestBuild(self.script, [2, ['my_arg0']], self.GetWallet1(), '0210', '07')
mocked_logger.assert_called_with(StringIn("Attempting to access an array using a negative index"))
mocked_logger.assert_called_with(StringIn("Array index is less than zero"))

@patch('logzero.logger.error')
def test_invalid_type_indexing(self, mocked_logger):
tx, results, total_ops, engine = TestBuild(self.script, [3, ['my_arg0']], self.GetWallet1(), '0210', '07')
mocked_logger.assert_called_with(StringIn("Cannot access item at index") and StringIn("Item is not an array but of type"))
mocked_logger.assert_called_with(StringIn("Cannot access item at index") and StringIn("Item is not an array or dict but of type"))

@patch('logzero.logger.error')
def test_invalid_appcall(self, mocked_logger):
Expand Down
209 changes: 149 additions & 60 deletions neo/VM/ExecutionEngine.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from neo.VM.ExecutionContext import ExecutionContext
from neo.VM import VMState
from neo.VM.OpCode import *
from neo.VM.InteropService import Array, Struct, StackItem
from neo.VM.InteropService import Array, Struct, StackItem, CollectionMixin, Map, Boolean
from neocore.UInt160 import UInt160
from neo.Settings import settings
from neo.VM.VMFault import VMFault
Expand Down Expand Up @@ -639,11 +639,11 @@ def ExecuteOp(self, opcode, context):
if not item:
return self.VM_FAULT_and_report(VMFault.UNKNOWN7)

if not item.IsArray:
estack.PushT(len(item.GetByteArray()))
if isinstance(item, CollectionMixin):
estack.PushT(item.Count)

else:
estack.PushT(len(item.GetArray()))
estack.PushT(len(item.GetByteArray()))

elif opcode == PACK:

Expand All @@ -663,7 +663,7 @@ def ExecuteOp(self, opcode, context):
elif opcode == UNPACK:
item = estack.Pop()

if not item.IsArray:
if not isinstance(item, Array):
return self.VM_FAULT_and_report(VMFault.UNPACK_INVALID_TYPE, item)

items = item.GetArray()
Expand All @@ -675,77 +675,98 @@ def ExecuteOp(self, opcode, context):

elif opcode == PICKITEM:

index = estack.Pop().GetBigInteger()
if index < 0:
return self.VM_FAULT_and_report(VMFault.PICKITEM_NEGATIVE_INDEX)
key = estack.Pop()

item = estack.Pop()
if isinstance(key, CollectionMixin):
# key must be an array index or dictionary key, but not a collection
return self.VM_FAULT_and_report(VMFault.UNKNOWN1, key)

if not item.IsArray:
return self.VM_FAULT_and_report(VMFault.PICKITEM_INVALID_TYPE, index, item)
collection = estack.Pop()

items = item.GetArray()
if isinstance(collection, Array):
index = key.GetBigInteger()
if index < 0 or index >= collection.Count:
return self.VM_FAULT_and_report(VMFault.PICKITEM_INVALID_INDEX, index, collection.Count)

items = collection.GetArray()
to_pick = items[index]
estack.PushT(to_pick)

elif isinstance(collection, Map):

if index >= len(items):
return self.VM_FAULT_and_report(VMFault.PICKITEM_INVALID_INDEX, index, len(items))
success, value = collection.TryGetValue(key)

to_pick = items[index]
if success:
estack.PushT(value)
else:
return self.VM_FAULT_and_report(VMFault.DICT_KEY_NOT_FOUND, key, collection.Keys)

estack.PushT(to_pick)
else:
return self.VM_FAULT_and_report(VMFault.PICKITEM_INVALID_TYPE, key, collection)

elif opcode == SETITEM:
newItem = estack.Pop()
value = estack.Pop()

if issubclass(type(newItem), StackItem) and newItem.IsStruct:
newItem = newItem.Clone()
if isinstance(value, Struct):
value = value.Clone()

index = estack.Pop().GetBigInteger()
key = estack.Pop()

arrItem = estack.Pop()
if isinstance(key, CollectionMixin):
return self.VM_FAULT_and_report(VMFault.UNKNOWN1)

collection = estack.Pop()

if isinstance(collection, Array):

index = key.GetBigInteger()

if not issubclass(type(arrItem), StackItem) or not arrItem.IsArray:
return self.VM_FAULT_and_report(VMFault.SETITEM_INVALID_TYPE)
if index < 0 or index >= collection.Count:
return self.VM_FAULT_and_report(VMFault.SETITEM_INVALID_INDEX)

items = arrItem.GetArray()
items = collection.GetArray()
items[index] = value

if index < 0 or index >= len(items):
return self.VM_FAULT_and_report(VMFault.SETITEM_INVALID_INDEX)
elif isinstance(collection, Map):

items[index] = newItem
collection.SetItem(key, value)

else:

return self.VM_FAULT_and_report(VMFault.SETITEM_INVALID_TYPE, key, collection)

elif opcode == NEWARRAY:

count = estack.Pop().GetBigInteger()
items = [None for i in range(0, count)]
items = [Boolean(False) for i in range(0, count)]
estack.PushT(Array(items))

elif opcode == NEWSTRUCT:

count = estack.Pop().GetBigInteger()

items = [None for i in range(0, count)]
items = [Boolean(False) for i in range(0, count)]

estack.PushT(Struct(items))

elif opcode == NEWDICT:

keysItem = estack.Pop()
valuesItem = estack.Pop()
elif opcode == NEWMAP:
estack.PushT(Map())

if not keysItem.IsArray or not valuesItem.IsArray:
return self.VM_FAULT_and_report(VMFault.POP_ITEM_NOT_ARRAY, keysItem, valuesItem)

estack.PushT(Struct([keysItem, valuesItem]))
# keysItem = estack.Pop()
# valuesItem = estack.Pop()
# if not keysItem.IsArray or not valuesItem.IsArray:
# return self.VM_FAULT_and_report(VMFault.POP_ITEM_NOT_ARRAY, keysItem, valuesItem)
# estack.PushT(Struct([keysItem, valuesItem]))

elif opcode == APPEND:
newItem = estack.Pop()

if type(newItem) is Struct:
if isinstance(newItem, Struct):
newItem = newItem.Clone()

arrItem = estack.Pop()

if not arrItem.IsArray:
if not isinstance(arrItem, Array):
return self.VM_FAULT_and_report(VMFault.APPEND_INVALID_TYPE, arrItem)

arr = arrItem.GetArray()
Expand All @@ -754,22 +775,95 @@ def ExecuteOp(self, opcode, context):
elif opcode == REVERSE:

arrItem = estack.Pop()
if not arrItem.IsArray:
if not isinstance(arrItem, Array):
return self.VM_FAULT_and_report(VMFault.REVERSE_INVALID_TYPE, arrItem)

arrItem.GetArray().reverse()
arrItem.Reverse()

elif opcode == REMOVE:
index = estack.Pop().GetBigInteger()
arrItem = estack.Pop()
if not arrItem.IsArray:
return self.VM_FAULT_and_report(VMFault.REMOVE_INVALID_TYPE, arrItem, index)
items = arrItem.GetArray()

if index < 0 or index >= len(items):
return self.VM_FAULT_and_report(VMFault.REMOVE_INVALID_INDEX, index, len(items))
key = estack.Pop()

if isinstance(key, CollectionMixin):
return self.VM_FAULT_and_report(VMFault.UNKNOWN1)

del items[index]
collection = estack.Pop()

if isinstance(collection, Array):

index = key.GetBigInteger()

if index < 0 or index >= collection.Count:
return self.VM_FAULT_and_report(VMFault.REMOVE_INVALID_INDEX, index, collection.Count)

collection.RemoveAt(index)

elif isinstance(collection, Map):

collection.Remove(key)

else:

return self.VM_FAULT_and_report(VMFault.REMOVE_INVALID_TYPE, key, collection)

elif opcode == HASKEY:

key = estack.Pop()

if isinstance(key, CollectionMixin):
return self.VM_FAULT_and_report(VMFault.DICT_KEY_ERROR)

collection = estack.Pop()

if isinstance(collection, Array):

index = key.GetBigInteger()

if index < 0:
return self.VM_FAULT_and_report(VMFault.DICT_KEY_ERROR)

estack.PushT(index < collection.Count)

elif isinstance(collection, Map):

estack.PushT(collection.ContainsKey(key))

else:

return self.VM_FAULT_and_report(VMFault.DICT_KEY_ERROR)

elif opcode == KEYS:

collection = estack.Pop()

if isinstance(collection, Map):

estack.PushT(Array(collection.Keys))
else:
return self.VM_FAULT_and_report(VMFault.DICT_KEY_ERROR)

elif opcode == VALUES:

collection = estack.Pop()
values = []

if isinstance(collection, Map):
values = collection.Values

elif isinstance(collection, Array):
values = collection

else:
return self.VM_FAULT_and_report(VMFault.DICT_KEY_ERROR)

newArray = Array()
for item in values:
if isinstance(item, Struct):
newArray.Add(item.Clone())
else:
newArray.Add(item)

estack.PushT(newArray)

elif opcode == THROW:
return self.VM_FAULT_and_report(VMFault.THROW)
Expand All @@ -778,10 +872,6 @@ def ExecuteOp(self, opcode, context):
if not estack.Pop().GetBoolean():
return self.VM_FAULT_and_report(VMFault.THROWIFNOT)

elif opcode == DEBUG:
pdb.set_trace()
return

else:
return self.VM_FAULT_and_report(VMFault.UNKNOWN_OPCODE, opcode)

Expand Down Expand Up @@ -819,10 +909,10 @@ def StepInto(self):
else:
op = self.CurrentContext.OpReader.ReadByte(do_ord=False)

# opname = ToName(op)
# logger.info("____________________________________________________")
# logger.info("[%s] [%s] %02x -> %s" % (self.CurrentContext.InstructionPointer,self.ops_processed,int.from_bytes(op,byteorder='little'), opname))
# logger.info("-----------------------------------")
# opname = ToName(op)
# logger.info("____________________________________________________")
# logger.info("[%s] [%s] %02x -> %s" % (self.CurrentContext.InstructionPointer,self.ops_processed,int.from_bytes(op,byteorder='little'), opname))
# logger.info("-----------------------------------")

self.ops_processed += 1

Expand Down Expand Up @@ -890,15 +980,15 @@ def VM_FAULT_and_report(self, id, *args):
elif id == VMFault.PICKITEM_INVALID_TYPE:
index = args[0]
item = args[1]
error_msg = "Cannot access item at index {}. Item is not an array but of type: {}".format(index, type(item))
error_msg = "Cannot access item at index {}. Item is not an array or dict but of type: {}".format(index, type(item))

elif id == VMFault.PICKITEM_NEGATIVE_INDEX:
error_msg = "Attempting to access an array using a negative index"

elif id == VMFault.PICKITEM_INVALID_INDEX:
index = args[0]
length = args[1]
error_msg = "Array index {} exceeds list length {}".format(index, length)
error_msg = "Array index is less than zero or {} exceeds list length {}".format(index, length)

elif id == VMFault.APPEND_INVALID_TYPE:
item = args[0]
Expand Down Expand Up @@ -932,7 +1022,6 @@ def VM_FAULT_and_report(self, id, *args):

else:
error_msg = id
pass

logger.error("({}) {}".format(self.ops_processed, error_msg))
return
Loading

0 comments on commit b5693bd

Please sign in to comment.