def test_get_transaction_from_block_uint256(self): call_flags = Integer(CallFlags.ALL).to_byte_array(signed=True, min_length=1) method = String('getTransactionFromBlock').to_bytes() expected_output = ( Opcode.INITSLOT + b'\x00\x02' + Opcode.LDARG1 + Opcode.LDARG0 + Opcode.PUSH2 + Opcode.PACK + Opcode.PUSHDATA1 + Integer(len(call_flags)).to_byte_array() + call_flags + Opcode.PUSHDATA1 + Integer(len(method)).to_byte_array() + method + Opcode.PUSHDATA1 + Integer(len(constants.LEDGER_SCRIPT)).to_byte_array() + constants.LEDGER_SCRIPT + Opcode.SYSCALL + Interop.CallContract.interop_method_hash + Opcode.RET ) path = self.get_contract_path('GetTransactionFromBlockUInt256.py') output = Boa3.compile(path) self.assertEqual(expected_output, output) path_burn_gas = self.get_contract_path('../runtime', 'BurnGas.py') engine = TestEngine() engine.increase_block(10) sender = bytes(range(20)) self.run_smart_contract(engine, path_burn_gas, 'main', 100, signer_accounts=[sender]) block_10 = engine.current_block block_hash = block_10.hash self.assertIsNotNone(block_hash) txs = block_10.get_transactions() tx_hash = txs[0].hash tx_script = txs[0]._script engine.increase_block() result = self.run_smart_contract(engine, path, 'main', block_hash, 0) self.assertEqual(8, len(result)) if isinstance(result[0], str): result[0] = String(result[0]).to_bytes() self.assertEqual(UInt256(tx_hash), UInt256(result[0])) # hash self.assertIsInstance(result[1], int) # version self.assertIsInstance(result[2], int) # nonce if isinstance(result[3], str): result[3] = String(result[3]).to_bytes() self.assertEqual(UInt160(sender), UInt160(result[3])) # sender self.assertIsInstance(result[4], int) # system_fee self.assertIsInstance(result[5], int) # network_fee self.assertIsInstance(result[6], int) # valid_until_block if isinstance(result[7], str): result[7] = String(result[7]).to_bytes() self.assertEqual(tx_script, result[7]) # script
def _verify_is_valid_contract_hash(contract_hash: str) -> bool: """ Verifies if a given contract hash is valid. :return: whether the given contract hash is valid or not """ if contract_hash.startswith('0x'): try: from boa3.neo3.core.types import UInt160 # if contract_hash is not a valid UInt160, it will raise a ValueError UInt160.from_string(contract_hash[2:]) return True except ValueError: pass return False
def test_contract_interface_code_optimization(self): from boa3.model.builtin.interop.interop import Interop from boa3.neo.vm.opcode.Opcode import Opcode from boa3.neo.vm.type.Integer import Integer from boa3.neo.vm.type.String import String from boa3.neo3.core.types import UInt160 external_contract_name = 'symbol' function_name_bytes = String(external_contract_name).to_bytes() contract_script_bytes = UInt160.from_string( '21f19f84e144f91abe755efb21a6798ac95c2e70').to_array() expected_output = ( Opcode.NEWARRAY0 # arguments list + Opcode.PUSH15 # CallFlag + Opcode.PUSHDATA1 # function name + Integer(len(function_name_bytes)).to_byte_array() + function_name_bytes + Opcode.PUSHDATA1 # contract script + Integer(len(contract_script_bytes)).to_byte_array() + contract_script_bytes + Opcode.SYSCALL + Interop.CallContract.interop_method_hash + Opcode.RET) path = self.get_contract_path('ContractInterfaceCodeOptimization.py') output, manifest = self.compile_and_save(path) self.assertEqual(expected_output, output) nep17_path = self.get_contract_path('examples', 'nep17.py') engine = TestEngine() nep17_result = self.run_smart_contract(engine, nep17_path, 'symbol') result = self.run_smart_contract(engine, path, 'nep17_symbol') self.assertEqual(nep17_result, result)
def _get_script_hash(self) -> Optional[bytes]: try: return UInt160.from_string( self._contract_hash[2:] if self._contract_hash. startswith('0x') else self._contract_hash).to_array() except BaseException: return None
def run_smart_contract(self, test_engine: TestEngine, smart_contract_path: Union[str, bytes], method: str, *arguments: Any, reset_engine: bool = False, fake_storage: Dict[Tuple[str, str], Any] = None, signer_accounts: Iterable[bytes] = (), expected_result_type: Type = None, rollback_on_fault: bool = True) -> Any: if isinstance(smart_contract_path, str) and smart_contract_path.endswith('.py'): if not (os.path.isfile(smart_contract_path.replace('.py', '.nef')) and os.path.isfile( smart_contract_path.replace('.py', '.manifest.json'))): # both .nef and .manifest.json are required to execute the smart contract self.compile_and_save(smart_contract_path, log=False) smart_contract_path = smart_contract_path.replace('.py', '.nef') elif isinstance(smart_contract_path, bytes): from boa3.neo3.core.types import UInt160 smart_contract_path = UInt160(smart_contract_path) self._set_fake_data(test_engine, fake_storage, signer_accounts) result = test_engine.run(smart_contract_path, method, *arguments, reset_engine=reset_engine, rollback_on_fault=rollback_on_fault) return self._filter_result(test_engine, expected_result_type, result)
def evaluate_literal(self, *args: Any) -> Any: from boa3.neo3.core.types import UInt160 if len(args) == 0: return UInt160.zero().to_array() if len(args) == 1: arg = args[0] if isinstance(arg, int): from boa3.neo.vm.type.Integer import Integer arg = Integer(arg).to_byte_array(min_length=UInt160._BYTE_LEN) if isinstance(arg, bytes): value = UInt160(arg).to_array() return value return super().evaluate_literal(*args)
def validate_values(self, *params: Any) -> List[Any]: values = [] if len(params) != 2: return values origin, visitor = params values.append(self.contract_hash) from boa3.analyser.astanalyser import IAstAnalyser if not isinstance(visitor, IAstAnalyser): return values from boa3.exception import CompilerError if not isinstance(origin, ast.Call) or len(origin.args) < 1: visitor._log_error( CompilerError.UnfilledArgument(origin.lineno, origin.col_offset, list(self.args.keys())[0])) return values argument_hash = visitor.visit(origin.args[0]) try: if isinstance(argument_hash, str): from boa3.neo import from_hex_str argument_hash = from_hex_str(argument_hash) if isinstance(argument_hash, bytes): values[0] = UInt160(argument_hash) except BaseException: visitor._log_error( CompilerError.InvalidUsage( origin.lineno, origin.col_offset, "Only literal values are accepted for 'script_hash' argument" )) return values
def _internal_contract_delete(self, script_hash: types.UInt160, batch=None): if batch: db = batch else: db = self._real_db db.delete(DBPrefixes.CONTRACTS + script_hash.to_array())
def _get_script_from_str(cls, script: str) -> bytes: if isinstance(script, str): if script.startswith('0x'): str_script = script[2:] else: str_script = script script = UInt160.from_string(str_script).to_array() return script
def __init__(self): from boa3.model.type.primitive.bytestringtype import ByteStringType identifier = 'contract' args: Dict[str, Variable] = { 'script_hash': Variable(ByteStringType.build()) } super().__init__(identifier, args) self.contract_hash = UInt160()
def is_valid(self, contract_hash: types.UInt160) -> bool: """ Validate if the group has agreed on allowing the specific contract_hash. Args: contract_hash: """ return cryptography.verify_signature(contract_hash.to_array(), self.signature, self.public_key.encode_point(False))
def test_transaction_init(self): path = self.get_contract_path('Transaction.py') engine = TestEngine() result = self.run_smart_contract(engine, path, 'main') self.assertEqual(8, len(result)) if isinstance(result[0], str): result[0] = String(result[0]).to_bytes() self.assertEqual(UInt256(), UInt256(result[0])) # hash self.assertEqual(0, result[1]) # version self.assertEqual(0, result[2]) # nonce if isinstance(result[3], str): result[3] = String(result[3]).to_bytes() self.assertEqual(UInt160(), UInt160(result[3])) # sender self.assertEqual(0, result[4]) # system_fee self.assertEqual(0, result[5]) # network_fee self.assertEqual(0, result[6]) # valid_until_block if isinstance(result[7], str): result[7] = String(result[7]).to_bytes() self.assertEqual(b'', result[7]) # script
def test_block_constructor(self): path = self.get_contract_path('Block.py') engine = TestEngine() result = self.run_smart_contract(engine, path, 'main') self.assertIsInstance(result, list) self.assertEqual(10, len(result)) for k in range(len(result)): if isinstance(result[k], str): result[k] = String(result[k]).to_bytes() self.assertEqual(UInt256(), UInt256(result[0])) # hash self.assertEqual(0, result[1]) # version self.assertEqual(UInt256(), UInt256(result[2])) # previous_hash self.assertEqual(UInt256(), UInt256(result[3])) # merkle_root self.assertEqual(0, result[4]) # timestamp self.assertEqual(0, result[5]) # nonce self.assertEqual(0, result[6]) # index self.assertEqual(0, result[7]) # primary_index self.assertEqual(UInt160(), UInt160(result[8])) # next_consensus self.assertEqual(0, result[9]) # transaction_count
def get_events(self, event_name: str, origin: UInt160 = None) -> List[Notification]: if origin is None: return [n for n in self._notifications if n.name == event_name] else: origin_bytes = origin.to_array() if isinstance( origin, UInt160) else bytes(origin) return [ n for n in self._notifications if n.name == event_name and n.origin == origin_bytes ]
def run_smart_contract(self, test_engine: TestEngine, smart_contract_path: Union[str, bytes], method: str, *arguments: Any, reset_engine: bool = False, fake_storage: Dict[str, Any] = None, signer_accounts: Iterable[bytes] = (), expected_result_type: Type = None, rollback_on_fault: bool = True) -> Any: if isinstance(smart_contract_path, str) and smart_contract_path.endswith('.py'): if not (os.path.isfile(smart_contract_path.replace('.py', '.nef')) and os.path.isfile( smart_contract_path.replace('.py', '.manifest.json'))): # both .nef and .manifest.json are required to execute the smart contract self.compile_and_save(smart_contract_path, log=False) smart_contract_path = smart_contract_path.replace('.py', '.nef') elif isinstance(smart_contract_path, bytes): from boa3.neo3.core.types import UInt160 smart_contract_path = UInt160(smart_contract_path) if isinstance(fake_storage, dict): test_engine.set_storage(fake_storage) for account in signer_accounts: test_engine.add_signer_account(account) result = test_engine.run(smart_contract_path, method, *arguments, reset_engine=reset_engine, rollback_on_fault=rollback_on_fault) if test_engine.vm_state is not VMState.HALT and test_engine.error is not None: raise TestExecutionException(test_engine.error) if expected_result_type is not None: if expected_result_type is not str and isinstance(result, str): result = String(result).to_bytes() if expected_result_type is bool: if isinstance(result, bytes): result = Integer.from_bytes(result, signed=True) if isinstance(result, int) and result in (False, True): result = bool(result) if expected_result_type is bytearray and isinstance(result, bytes): result = bytearray(result) return result
def run_oracle_response(self, request_id: int, oracle_response: oracleresponse.OracleResponseCode, result: bytes, reset_engine: bool = False, rollback_on_fault: bool = True) -> Any: request_ids = [x.arguments[0] if isinstance(x.arguments, (tuple, list)) and len(x.arguments) > 0 else x.arguments for x in self.get_events('OracleRequest', constants.ORACLE_SCRIPT)] assert request_id in request_ids, 'Request ID not found' self._current_tx = Transaction(b'') self._current_tx.add_attribute(oracleresponse.OracleResponse(request_id, oracle_response, result)) return self.run(UInt160(constants.ORACLE_SCRIPT), 'finish', reset_engine=reset_engine, rollback_on_fault=rollback_on_fault)
def _internal_storage_find(self, contract_script_hash: types.UInt160, key_prefix: bytes): prefix = DBPrefixes.STORAGES + contract_script_hash.to_array() + key_prefix res = {} with self._real_db.iterator(prefix=prefix, include_key=True, include_value=True) as it: for key, value in it: # strip off prefix k = storage.StorageKey.deserialize_from_bytes(key[1:]) v = storage.StorageItem.deserialize_from_bytes(value) res[k] = v # yielding outside of iterator to make sure the LevelDB iterator is closed and not leaking resources for k, v in res.items(): yield k, v
def _get_contract_hash_or_name( self, contract_id: Union[str, bytes]) -> Union[str, UInt160]: if isinstance(contract_id, str): if contract_id in self._contracts: contract = self._contracts[contract_id] return contract.name if hasattr( contract, 'name') else contract.script_hash try: return UInt160.from_string(contract_id) except BaseException as e: if not contract_id.endswith('.nef'): raise e manifest_file = contract_id.replace('.nef', '.manifest.json') if os.path.exists(manifest_file): with open(manifest_file) as manifest_output: import json manifest = json.loads(manifest_output.read()) return manifest['name'] raise e else: return UInt160(contract_id)
def test_contract_manual_interface_code_optimization(self): from boa3.model.builtin.interop.interop import Interop from boa3.neo.vm.opcode.Opcode import Opcode from boa3.neo.vm.type.Integer import Integer from boa3.neo.vm.type.String import String from boa3.neo3.core.types import UInt160 external_contract_name = 'symbol' function_name_bytes = String(external_contract_name).to_bytes() contract_script_bytes = UInt160.from_string( '21f19f84e144f91abe755efb21a6798ac95c2e70').to_array() expected_output = ( # start public method Opcode.LDSFLD0 # generated cls arg + Opcode.CALL + Integer(35).to_byte_array() + Opcode.RET # end public method # start initialize method + Opcode.INITSSLOT + b'\x01' + Opcode.PUSH1 + Opcode.NEWARRAY + Opcode.STSFLD0 + Opcode.PUSHDATA1 + Integer(len(contract_script_bytes)).to_byte_array() + contract_script_bytes + Opcode.PUSH0 + Opcode.LDSFLD0 + Opcode.REVERSE3 + Opcode.SETITEM + Opcode.RET # end initialize method # start 'symbol' class method + Opcode.INITSLOT + b'\x00\x01' + Opcode.NEWARRAY0 # arguments list + Opcode.PUSH15 # CallFlag + Opcode.PUSHDATA1 # function name + Integer(len(function_name_bytes)).to_byte_array() + function_name_bytes + Opcode.LDARG0 # contract script + Opcode.PUSH0 + Opcode.PICKITEM + Opcode.SYSCALL + Interop.CallContract.interop_method_hash + Opcode.RET # start class method ) path = self.get_contract_path( 'ContractManualInterfaceCodeOptimization.py') output, manifest = self.compile_and_save(path) self.assertEqual(expected_output, output) nep17_path = self.get_contract_path('examples', 'nep17.py') engine = TestEngine() nep17_result = self.run_smart_contract(engine, nep17_path, 'symbol') result = self.run_smart_contract(engine, path, 'nep17_symbol') self.assertEqual(nep17_result, result)
def from_json(cls, json: Dict[str, Any]) -> TestRunnerLog: """ Creates a Log object from a json. :param json: json that contains the log data :return: a Log object :rtype: TestRunnerLog """ keys = set(json.keys()) if not keys.issubset([cls._contract_key, cls._message_key]): return None script: bytes = json[ cls._contract_key] if cls._contract_key in json else b'' message: str = json[ cls._message_key] if cls._message_key in json else "" if isinstance(script, str): script = UInt160.from_string( script[2:] if script.startswith('0x') else script).to_array() return cls(script, message)
def get_contract(cls, value: Any) -> Optional[UInt160]: if cls._is_wildcard(value): return None # TODO: Permissions are not implemented yet return UInt160()
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
def from_json(cls, json: Dict[str, Any]) -> Signer: account_hex = json['account'] account = UInt160(from_hex_str(account_hex)) scopes = WitnessScope.get_from_neo_name( json['scopes']) if 'scopes' in json else WitnessScope.CalledByEntry return cls(account, scopes)
def add_signer_account(self, account_address: bytes, account_scope: WitnessScope = WitnessScope.CalledByEntry): account = Signer(UInt160(account_address), account_scope) if account not in self._accounts: self._accounts.append(account)
def from_json(cls, json: Dict[str, Any]) -> Signer: account_hex = json['account'] account = UInt160(from_hex_str(account_hex)) return cls(account)
def _internal_contract_get(self, script_hash: types.UInt160): contract_bytes = self._real_db.get(DBPrefixes.CONTRACTS + script_hash.to_array()) if contract_bytes is None: raise KeyError return storage.ContractState.deserialize_from_bytes(contract_bytes)