def get_load(index: int, local: bool, is_arg: bool = False) -> Opcode: """ Gets the opcode to load the variable :param index: index of the variable :param local: identifies if the variable is local or global :param is_arg: identifies if the variable is an argument of a function. False if local is False. :return: the respective opcode :rtype: Opcode """ if not local: is_arg = False if 0 <= index <= 6: if is_arg: opcode_value: int = Integer.from_bytes(Opcode.LDARG0) + index elif local: opcode_value: int = Integer.from_bytes(Opcode.LDLOC0) + index else: opcode_value: int = Integer.from_bytes(Opcode.LDSFLD0) + index return Opcode(Integer(opcode_value).to_byte_array()) else: if is_arg: return Opcode.LDARG elif local: return Opcode.LDLOC else: return Opcode.LDSFLD
def test_ambiguous_integer_constant(self): byte_input = b'\x00\x80' unsigned = Integer.from_bytes(byte_input, signed=False) signed = Integer.from_bytes(byte_input, signed=True) self.assertNotEqual(unsigned, signed) unsigned_byte_input = Integer(unsigned).to_byte_array(signed=True) self.assertNotEqual(byte_input, unsigned_byte_input) unsigned_expected_output = ( Opcode.PUSHDATA1 # push the bytes + Integer(len(unsigned_byte_input)).to_byte_array(min_length=1) + unsigned_byte_input + Opcode.CONVERT # convert to integer + Type.int.stack_item) generator = self.build_code_generator() generator.convert_integer_literal(unsigned) output = generator.bytecode self.assertEqual(unsigned_expected_output, output) signed_byte_input = Integer(signed).to_byte_array(signed=True) self.assertEqual(byte_input, signed_byte_input) signed_expected_output = ( Opcode.PUSHDATA1 # push the bytes + Integer(len(signed_byte_input)).to_byte_array(min_length=1) + signed_byte_input + Opcode.CONVERT # convert to integer + Type.int.stack_item) generator = self.build_code_generator() generator.convert_integer_literal(signed) output = generator.bytecode self.assertEqual(signed_expected_output, output)
def test_ambiguous_integer_constant(self): byte_input = b'\x00\x80' unsigned = Integer.from_bytes(byte_input, signed=False) signed = Integer.from_bytes(byte_input, signed=True) self.assertNotEqual(unsigned, signed) unsigned_byte_input = Integer(unsigned).to_byte_array(signed=True, min_length=4) self.assertNotEqual(byte_input, unsigned_byte_input) unsigned_expected_output = ( Opcode.PUSHINT32 # push the bytes + unsigned_byte_input) generator = self.build_code_generator() generator.convert_integer_literal(unsigned) output = generator.bytecode self.assertEqual(unsigned_expected_output, output) signed_byte_input = Integer(signed).to_byte_array(signed=True, min_length=2) self.assertEqual(byte_input, signed_byte_input) signed_expected_output = ( Opcode.PUSHINT16 # push the bytes + signed_byte_input) generator = self.build_code_generator() generator.convert_integer_literal(signed) output = generator.bytecode self.assertEqual(signed_expected_output, output)
def get_push_and_data(integer: int) -> Tuple[Opcode, bytes]: """ Gets the push opcode and data to the respective integer :param integer: value that will be pushed :return: the respective opcode and its required data :rtype: Tuple[Opcode, bytes] """ if -1 <= integer <= 16: opcode_value: int = Integer.from_bytes(Opcode.PUSH0) + integer return Opcode(Integer(opcode_value).to_byte_array()), b'' else: data = Integer(integer).to_byte_array(signed=True, min_length=1) if len(data) == 1: opcode = Opcode.PUSHINT8 elif len(data) == 2: opcode = Opcode.PUSHINT16 elif len(data) <= 4: data = Integer(integer).to_byte_array(signed=True, min_length=4) opcode = Opcode.PUSHINT32 elif len(data) <= 8: data = Integer(integer).to_byte_array(signed=True, min_length=8) opcode = Opcode.PUSHINT64 elif len(data) <= 16: data = Integer(integer).to_byte_array(signed=True, min_length=16) opcode = Opcode.PUSHINT128 else: data = data[:32] opcode = Opcode.PUSHINT256 return opcode, data
def test_boa2_storage_test2(self): path = self.get_contract_path('StorageBoa2Test.py') engine = TestEngine() result = self.run_smart_contract(engine, path, 'main', 'sget', 100, 10000000000) if isinstance(result, str): result = String(result).to_bytes() self.assertEqual(b'', result) result = self.run_smart_contract(engine, path, 'main', 'sput', 100, 10000000000) self.assertEqual(True, result) result = self.run_smart_contract(engine, path, 'main', 'sget', 100, 10000000000) if isinstance(result, bytes): result = Integer.from_bytes(result) self.assertEqual(10000000000, result) result = self.run_smart_contract(engine, path, 'main', 'sdel', 100, 10000000000) self.assertEqual(True, result) result = self.run_smart_contract(engine, path, 'main', 'sget', 100, 10000000000) if isinstance(result, str): result = String(result).to_bytes() self.assertEqual(b'', result)
def test_while_interop_condition(self): path = self.get_contract_path('WhileWithInteropCondition.py') engine = TestEngine() result = self.run_smart_contract(engine, path, 'deploy') self.assertEqual(True, result) contract_hash = engine.executed_script_hash.to_array() result = self.run_smart_contract(engine, path, 'test_end_while_jump') self.assertEqual(True, result) # test notifications inserted into the code for validating if the code flow is correct notifications = engine.get_events('notify', origin=contract_hash) self.assertEqual(2, len(notifications)) self.assertEqual(1, len(notifications[0].arguments)) self.assertIsInstance(notifications[0].arguments[0], list) self.assertEqual(4, len(notifications[0].arguments[0])) token, executing_script_hash, fee_receiver, fee_amount = notifications[ 0].arguments[0] if isinstance(fee_receiver, str): fee_receiver = String(fee_receiver).to_bytes() if isinstance(fee_amount, str): fee_amount = String(fee_amount).to_bytes() if isinstance(fee_amount, bytes): fee_amount = Integer.from_bytes(fee_amount) self.assertEqual(constants.GAS_SCRIPT, token) self.assertEqual(contract_hash, executing_script_hash) self.assertEqual(bytes(20), fee_receiver) self.assertEqual(10, fee_amount) self.assertEqual(1, len(notifications[1].arguments)) self.assertIsInstance(notifications[1].arguments[0], list) self.assertEqual(4, len(notifications[1].arguments[0])) token, executing_script_hash, fee_receiver, fee_amount = notifications[ 1].arguments[0] if isinstance(fee_receiver, str): fee_receiver = String(fee_receiver).to_bytes() if isinstance(fee_amount, str): fee_amount = String(fee_amount).to_bytes() if isinstance(fee_amount, bytes): fee_amount = Integer.from_bytes(fee_amount) self.assertEqual(constants.NEO_SCRIPT, token) self.assertEqual(contract_hash, executing_script_hash) self.assertEqual(bytes(20), fee_receiver) self.assertEqual(20, fee_amount)
def _update_targets(self): from boa3.neo.vm.type.Integer import Integer for address, code in self.code_map.items(): if code.opcode.has_target() and code.target is None: relative = Integer.from_bytes(code.data) absolute = address + relative if absolute in self.code_map: code.set_target(self.code_map[absolute])
def get_store_from_load(load_opcode) -> Optional[Opcode]: """ Gets the store slot opcode equivalent to the given load slot opcode. :param load_opcode: load opcode :type load_opcode: Opcode :return: equivalent store opcode if the given opcode is a load slot. Otherwise, returns None :rtype: Opcode or None """ if load_opcode.is_load_slot: opcode_value: int = Integer.from_bytes(load_opcode) + 8 return Opcode(Integer(opcode_value).to_byte_array())
def test_big_integer_constant(self): byte_input = bytes(100000) + b'\x01' input = Integer.from_bytes(byte_input) expected_output = ( Opcode.PUSHINT256 # push the bytes + byte_input[:32]) generator = self.build_code_generator() generator.convert_integer_literal(input) output = generator.bytecode self.assertEqual(expected_output, output)
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 get_literal_push(integer: int) -> Optional[Opcode]: """ Gets the push opcode to the respective integer :param integer: value that will be pushed :return: the respective opcode :rtype: Opcode or None """ if -1 <= integer <= 16: opcode_value: int = Integer.from_bytes(Opcode.PUSH0) + integer return Opcode(Integer(opcode_value).to_byte_array()) else: return None
def test_big_integer_constant(self): byte_input = bytes(100000) + b'\x01' input = Integer.from_bytes(byte_input) expected_output = ( Opcode.PUSHDATA4 # push the bytes + Integer(len(byte_input)).to_byte_array(min_length=4) + byte_input + Opcode.CONVERT # convert to integer + Type.int.stack_item) generator = self.build_code_generator() generator.convert_integer_literal(input) output = generator.bytecode self.assertEqual(expected_output, output)
def deserialize_binary(reader: BinaryReader) -> Any: deserialized_items = [] underserialized = 1 while underserialized > 0: stack_type = reader.read_byte() if stack_type == StackItemType.Any: deserialized_items.append(None) elif stack_type == StackItemType.Boolean: deserialized_items.append(reader.read_bool()) elif stack_type == StackItemType.Integer: value_in_bytes = reader.read_var_bytes() deserialized_items.append(Integer.from_bytes(value_in_bytes)) elif stack_type in (StackItemType.ByteString, StackItemType.Buffer): deserialized_items.append(reader.read_var_bytes()) elif stack_type in (StackItemType.Array, StackItemType.Struct): count = reader.read_var_int() deserialized_items.append(list(range(count))) underserialized += count elif stack_type == StackItemType.Map: count = reader.read_var_int() deserialized_items.append({key: None for key in range(count)}) underserialized += count * 2 else: raise ValueError underserialized -= 1 stack_temp = [] while len(deserialized_items) > 0: item = deserialized_items.pop() if isinstance(item, list): new_item = [] for _ in range(len(item)): new_item.append(stack_temp.pop()) item = new_item elif isinstance(item, dict): new_item = {} for _ in range(0, len(item), 2): key = stack_temp.pop() value = stack_temp.pop() new_item[key] = value item = new_item stack_temp.append(item) return stack_temp.pop()
def test_mixed_inequality_operation(self): expected_output = (Opcode.INITSLOT + b'\x00' + b'\x02' + Opcode.LDARG0 + Opcode.LDARG1 + Opcode.NOTEQUAL + Opcode.RET) path = '%s/boa3_test/test_sc/relational_test/MixedInequality.py' % self.dirname output = Boa3.compile(path) self.assertEqual(expected_output, output) engine = TestEngine(self.dirname) result = self.run_smart_contract(engine, path, 'Main', 1, 'unit') self.assertEqual(True, result) result = self.run_smart_contract(engine, path, 'Main', 123, '123') self.assertEqual(True, result) result = self.run_smart_contract(engine, path, 'Main', Integer.from_bytes(b'123'), '123') self.assertEqual(True, result)
def test_mixed_equality_operation(self): expected_output = (Opcode.INITSLOT + b'\x00' + b'\x02' + Opcode.LDARG0 + Opcode.LDARG1 + Opcode.EQUAL + Opcode.RET) path = self.get_contract_path('MixedEquality.py') output = Boa3.compile(path) self.assertEqual(expected_output, output) engine = TestEngine() result = self.run_smart_contract(engine, path, 'Main', 1, 'unit') self.assertEqual(False, result) result = self.run_smart_contract(engine, path, 'Main', 123, '123') self.assertEqual(False, result) result = self.run_smart_contract(engine, path, 'Main', Integer.from_bytes(b'123'), '123') self.assertEqual(False, result)
def _filter_result(self, test_engine, expected_result_type, result) -> Any: 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 set_code_targets(self): for target, vmcodes in self._missing_target.copy().items(): if target is None: for code in vmcodes.copy(): relative_address: int = Integer.from_bytes(code.raw_data, signed=True) code_address: int = VMCodeMapping.instance( ).get_start_address(code) absolute_address = code_address + relative_address code.set_target( VMCodeMapping.instance().get_code(absolute_address)) vmcodes.remove(code) else: for code in vmcodes.copy(): code.set_target(VMCodeMapping.instance().get_code(target)) vmcodes.remove(code) if len(vmcodes) == 0: self._missing_target.pop(target)
def test_storage_between_contracts(self): path1 = self.get_contract_path('StorageGetAndPut1.py') path2 = self.get_contract_path('StorageGetAndPut2.py') self.compile_and_save(path1) self.compile_and_save(path2) key = 'example_key' value = 42 engine = TestEngine() result = self.run_smart_contract(engine, path1, 'put_value', key, value) self.assertIsVoid(result) storage_value = engine.storage_get(key, path1) self.assertIsNotNone(storage_value) self.assertEqual(value, Integer.from_bytes(storage_value)) result = self.run_smart_contract(engine, path2, 'get_value', key) self.assertEqual(0, result) result = self.run_smart_contract(engine, path1, 'get_value', key) self.assertEqual(value, result)
def add_token(self, token_script: bytes, script_hash: bytes, amount: int) -> bool: if len(token_script) != 20 or len(script_hash) != 20 or amount <= 0: return False from boa3_test.tests.test_classes.nativetokenprefix import get_native_token_data token_prefix, token_id = get_native_token_data(token_script) if token_prefix is None or token_id is None: return False balance_key = token_prefix + script_hash if balance_key in self._dict: balance = Integer.from_bytes(self[balance_key]) else: balance = 0 balance += amount from boa3_test.tests.test_classes.nativeaccountstate import NativeAccountState key = StorageKey(balance_key) key._ID = token_id self._dict[key] = StorageItem(NativeAccountState(balance).serialize()) return True
def __insert1(self, op_info: OpcodeInformation, data: bytes = None): """ Inserts one opcode into the bytecode :param op_info: info of the opcode that will be inserted :param data: data of the opcode, if needed """ vm_code = VMCode(op_info, data) if op_info.opcode.has_target(): data = vm_code.data relative_address: int = Integer.from_bytes(data, signed=True) actual_address = VMCodeMapping.instance( ).bytecode_size + relative_address if (self._can_append_target and relative_address != 0 and actual_address in VMCodeMapping.instance().code_map): vm_code.set_target( VMCodeMapping.instance().code_map[actual_address]) else: self._include_missing_target(vm_code, actual_address) self.__insert_code(vm_code) self._update_codes_with_target(vm_code)
def as_int(self) -> int: return Integer.from_bytes(self._value)