def __init__(self, root_path: Optional[str] = None): if root_path is None: from boa3 import env root_path = env.TEST_ENGINE_DIRECTORY engine_path = '{0}/Neo.TestEngine.dll'.format(root_path) if not path.exists(engine_path): raise FileNotFoundError( "File at {0} was not found.\n" "Visit the docs or the README file and search for 'TestEngine' to correctly install it." .format(engine_path)) self._test_engine_path = engine_path self._vm_state: VMState = VMState.NONE self._gas_consumed: int = 0 self._result_stack: List[Any] = [] self._storage: Storage = Storage() self._notifications: List[Notification] = [] self._height: int = 0 self._blocks: List[Block] = [] self._accounts: List[bytes] = [] self._contract_paths: List[str] = [] self._error_message: Optional[str] = None self._neo_balance_prefix: bytes = b'\x14'
def storage_put(self, key: Union[str, bytes], value: Any, contract_path: str): if isinstance(key, str): key = String(key).to_bytes() if contract_path.endswith('.py'): contract_path = contract_path.replace('.py', '.nef') if contract_path in self.contracts: contract_id = self._get_contract_id(contract_path) storage_key = Storage.build_key(key, contract_id) self._storage[storage_key] = value
def storage_delete(self, key: Union[str, bytes], contract_path: str): if isinstance(key, str): key = String(key).to_bytes() if contract_path.endswith('.py'): contract_path = contract_path.replace('.py', '.nef') if contract_path not in self._contract_paths: return None index = self._contract_paths.index(contract_path) storage_key = Storage.build_key(key, index) if storage_key in self._storage: self._storage.pop(key)
def storage_get(self, key: Union[str, bytes], contract_path: str) -> Any: if isinstance(key, str): key = String(key).to_bytes() if contract_path.endswith('.py'): contract_path = contract_path.replace('.py', '.nef') if contract_path not in self.contracts: return None contract_id = self._get_contract_id(contract_path) storage_key = Storage.build_key(key, contract_id) if storage_key in self._storage: return self._storage[storage_key] else: return None
def run(self, nef_path: Union[str, UInt160], method: str, *arguments: Any, reset_engine: bool = False, rollback_on_fault: bool = True) -> Any: import json import subprocess if isinstance(nef_path, str) and nef_path not in self._contract_paths: self._contract_paths.append(nef_path) test_engine_args = self.to_json(nef_path, method, *arguments) param_json = json.dumps(test_engine_args, separators=(',', ':')) process = subprocess.Popen( ['dotnet', self._test_engine_path, param_json], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) stdout, stderr = process.communicate() if reset_engine: self.reset_engine() else: self.reset_state() stdout = stdout.splitlines()[-1] try: result = json.loads(stdout) self._error_message = result['error'] if 'error' in result else None if 'vm_state' in result: self._vm_state = VMState.get_vm_state(result['vm_state']) if 'gasconsumed' in result: self._gas_consumed = result['gasconsumed'] if 'result_stack' in result: if isinstance(result['result_stack'], list): self._result_stack = [ stack_item_from_json(value) for value in result['result_stack'] ] else: self._result_stack = [ stack_item_from_json(result['result_stack']) ] if self._vm_state is VMState.HALT or not rollback_on_fault: if 'notifications' in result: json_storage = result['notifications'] if not isinstance(json_storage, list): json_storage = [json_storage] notifications = [] for n in json_storage: new = Notification.from_json(n) if new is not None: notifications.append(new) self._notifications = notifications if 'storage' in result: json_storage = result['storage'] self._storage = Storage.from_json(json_storage) if 'blocks' in result: blocks_json = result['blocks'] if not isinstance(blocks_json, list): blocks_json = [blocks_json] self._blocks = sorted( [Block.from_json(js) for js in blocks_json], key=lambda b: b.index) if 'transaction' in result and self._vm_state is VMState.HALT: block = self.current_block if block is None: block = self.increase_block(self.height) tx = Transaction.from_json(result['transaction']) block.add_transaction(tx) except BaseException as e: self._error_message = str(e) # TODO: convert the result to the return type of the function in the manifest return self._result_stack[-1] if len( self._result_stack) > 0 else VoidType
def run(self, contract_id: Union[str, UInt160], method: str, *arguments: Any, reset_engine: bool = False, rollback_on_fault: bool = True) -> Any: import json import subprocess if isinstance(contract_id, str) and contract_id not in self.contracts: self.add_contract(contract_id) # build an UInt160 value if the contract_id is not a path if isinstance(contract_id, bytes) and not isinstance(contract_id, UInt160): contract_id = UInt160(contract_id) test_engine_args = self.to_json(contract_id, method, *arguments) param_json = json.dumps(test_engine_args, separators=(',', ':')) try: process = subprocess.Popen(['dotnet', self._test_engine_path, param_json], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) except BaseException: json_path = '{0}/test-engine-test.json'.format(path.curdir) with open(json_path, 'wb+') as json_file: json_file.write(String(param_json).to_bytes()) json_file.close() process = subprocess.Popen(['dotnet', self._test_engine_path, json_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) stdout, stderr = process.communicate() if reset_engine: self.reset_engine() else: self.reset_state() stdout = stdout.splitlines()[-1] try: result = json.loads(stdout) self._error_message = result['error'] if 'error' in result else None if 'vmstate' in result: self._vm_state = VMState.get_vm_state(result['vmstate']) if 'executedscripthash' in result: self._executed_script_hash = UInt160.from_string(result['executedscripthash']) if 'gasconsumed' in result: self._gas_consumed = int(result['gasconsumed']) if 'resultstack' in result: if isinstance(result['resultstack'], list): self._result_stack = [stack_item_from_json(value) for value in result['resultstack']] else: self._result_stack = [stack_item_from_json(result['resultstack'])] if self._vm_state is VMState.HALT or not rollback_on_fault: if 'notifications' in result: json_storage = result['notifications'] if not isinstance(json_storage, list): json_storage = [json_storage] notifications = [] for n in json_storage: new = Notification.from_json(n) if new is not None: notifications.append(new) self._notifications.extend(notifications) if 'storage' in result: json_storage = result['storage'] self._storage = Storage.from_json(json_storage) for contract in self._contract_paths.copy(): if (not isinstance(contract, TestContract) or contract.script_hash is None or not self._storage.has_contract(contract.script_hash)): self.remove_contract(contract.path) if 'currentblock' in result: current_block = Block.from_json(result['currentblock']) existing_block = next((block for block in self._blocks if block.index == current_block.index), None) if existing_block is not None: self._blocks.remove(existing_block) self._blocks.append(current_block) if 'transaction' in result and self._vm_state is VMState.HALT: block = self.current_block if block is None: block = self.increase_block(self.height) tx = Transaction.from_json(result['transaction']) block.add_transaction(tx) except BaseException as e: self._error_message = str(e) # TODO: convert the result to the return type of the function in the manifest return self._result_stack[-1] if len(self._result_stack) > 0 else VoidType