def signature(self, value): """Set the signature.""" self.signature_ = HexBytes(value).hex() if value is not None else None
def test_hash_safe_multisig_tx(self): # -------- Old version of the contract -------------------------- expected_hash = HexBytes( '0xc9d69a2350aede7978fdee58e702647e4bbdc82168577aa4a43b66ad815c6d1a' ) tx_hash = SafeTx(self.ethereum_client, '0x692a70d2e424a56d2c6c27aa97d1a86395877b3a', '0x5AC255889882aaB35A2aa939679E3F3d4Cea221E', 5000000, HexBytes('0x00'), 0, 50000, 100, 10000, '0x' + '0' * 40, '0x' + '0' * 40, safe_nonce=67, safe_version='0.1.0').safe_tx_hash self.assertEqual(expected_hash, tx_hash) expected_hash = HexBytes( '0x8ca8db91d72b379193f6e229eb2dff0d0621b6ef452d90638ee3206e9b7349b3' ) tx_hash = SafeTx(self.ethereum_client, '0x692a70d2e424a56d2c6c27aa97d1a86395877b3a', '0x' + '0' * 40, 80000000, HexBytes('0x562944'), 2, 54522, 773, 22000000, '0x' + '0' * 40, '0x' + '0' * 40, safe_nonce=257000, safe_version='0.1.0').safe_tx_hash self.assertEqual(expected_hash, tx_hash) # -------- New version of the contract -------------------------- expected_hash = HexBytes( '0x7c60341f3e1b4483575f38e84e97d6b332a2dd55b9290f39e6e26eef29a04fe7' ) tx_hash = SafeTx(self.ethereum_client, '0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', '0x5AC255889882aaB35A2aa939679E3F3d4Cea221E', 5000000, HexBytes('0x00'), 0, 50000, 100, 10000, '0x' + '0' * 40, '0x' + '0' * 40, safe_nonce=67, safe_version='1.0.0').safe_tx_hash self.assertEqual(expected_hash, tx_hash) expected_hash = HexBytes( '0xf585279fd867c94738096f4eab964e9e202014d2f0d5155d751099ad85cbe504' ) tx_hash = SafeTx(self.ethereum_client, '0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', '0x' + '0' * 40, 80000000, HexBytes('0x562944'), 2, 54522, 773, 22000000, '0x' + '0' * 40, '0x' + '0' * 40, safe_nonce=257000, safe_version='1.0.0').safe_tx_hash self.assertEqual(expected_hash, tx_hash) safe_create2_tx = self.deploy_test_safe() safe_address = safe_create2_tx.safe_address # Expected hash must be the same calculated by `getTransactionHash` of the contract expected_hash = get_safe_contract( self.ethereum_client.w3, safe_address).functions.getTransactionHash( '0x5AC255889882aaB35A2aa939679E3F3d4Cea221E', 5212459, HexBytes(0x00), 1, 123456, 122, 12345, '0x' + '2' * 40, '0x' + '2' * 40, 10789).call() safe_tx_hash = SafeTx(self.ethereum_client, safe_address, '0x5AC255889882aaB35A2aa939679E3F3d4Cea221E', 5212459, HexBytes(0x00), 1, 123456, 122, 12345, '0x' + '2' * 40, '0x' + '2' * 40, safe_nonce=10789, safe_version='1.0.0').safe_tx_hash self.assertEqual(HexBytes(expected_hash), safe_tx_hash)
def test_hash(test_case): expected = test_case["expected_hash"] transaction = TypedTransaction.from_dict(test_case["transaction"]) hash = transaction.hash() actual = HexBytes(hash).hex() assert actual == expected
def eip1167_initcode(_addr): addr = HexBytes(_addr) pre = HexBytes("0x602D3D8160093D39F3363d3d373d3d3d363d73") post = HexBytes("0x5af43d82803e903d91602b57fd5bf3") return HexBytes(pre + (addr + HexBytes(0) * (20 - len(addr))) + post)
def get_encrypt_test_params(): """ Params for testing Account#encrypt. Due to not being able to provide fixtures to pytest.mark.parameterize, we opt for creating the params in a non-fixture method here instead of providing fixtures for the private key and password. """ key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' key_bytes = to_bytes(hexstr=key) private_key = keys.PrivateKey(HexBytes(key)) password = '******' # 'private_key, password, kdf, iterations, expected_decrypted_key, expected_kdf' return [ ( key, password, None, None, key_bytes, 'scrypt' ), ( private_key, password, None, None, private_key.to_bytes(), 'scrypt' ), ( key, password, 'pbkdf2', None, key_bytes, 'pbkdf2' ), ( key, password, None, 1024, key_bytes, 'scrypt' ), ( key, password, 'pbkdf2', 1024, key_bytes, 'pbkdf2' ), ( key, password, 'scrypt', 1024, key_bytes, 'scrypt' ), ]
def normalize_bytecode(bytecode): if bytecode: bytecode = HexBytes(bytecode) return bytecode
def vyper_initcode(runtime_bytecode): bytecode_len_hex = hex(len(runtime_bytecode))[2:].rjust(6, "0") return HexBytes("0x62" + bytecode_len_hex + "3d81600b3d39f3") + runtime_bytecode
def __init__(self, address, account, priv_key): self._acct = account self.private_key = priv_key self.public_key = eth_keys.keys.PrivateKey( HexBytes(priv_key)).public_key super().__init__(address)
def getBlock(self, number): """Always return genesis block since this is what we care about in the tests""" genesis = ( b'\xd4\xe5g@\xf8v\xae\xf8\xc0\x10\xb8j@\xd5\xf5gE\xa1\x18\xd0\x90j4' b'\xe6\x9a\xec\x8c\r\xb1\xcb\x8f\xa3') return {'hash': HexBytes(genesis)}
def raise_for_status(self): if self.gas_limit is not None and self.ran_out_of_gas: raise OutOfGasError() elif self.status != TransactionStatusEnum.NO_ERROR: txn_hash = HexBytes(self.txn_hash).hex() raise TransactionError(message=f"Transaction '{txn_hash}' failed.")
class MockContractAgent: FAKE_TX_HASH = HexBytes(b'FAKE29890FAKE8349804') FAKE_RECEIPT = { 'transactionHash': FAKE_TX_HASH, 'gasUsed': 1, 'blockNumber': CURRENT_BLOCK.number, 'blockHash': HexBytes(b'FAKE43434343FAKE43443434') } FAKE_CALL_RESULT = 1 # Internal __COLLECTION_MARKER = "contract_api" # decorator attribute __DEFAULTS = { CONTRACT_CALL: FAKE_CALL_RESULT, CONTRACT_ATTRIBUTE: FAKE_CALL_RESULT, TRANSACTION: FAKE_RECEIPT, } _MOCK_METHODS = list() _REAL_METHODS = list() # Mock Nucypher Contract API contract = Mock() contract_address = NULL_ADDRESS # Mock Blockchain Interfaces registry = Mock() blockchain = MOCK_TESTERCHAIN def __init__(self, agent_class: Type[EthereumContractAgent]): """Bind mock agent attributes to the *subclass* with default values""" self.agent_class = agent_class self.__setup_mock(agent_class=agent_class) def __repr__(self) -> str: r = f'Mock{self.agent_class.__name__}(id={id(self)})' return r def __setup_mock(self, agent_class: Type[Agent]) -> None: api_methods: Iterable[Callable] = list( self.__collect_contract_api(agent_class=agent_class)) mock_methods, mock_properties = list(), dict() for agent_interface in api_methods: # Handle try: # TODO: #2022: This might be a method also decorated @property # Get the inner function of the property real_method: Callable = agent_interface.fget # Handle properties except AttributeError: real_method = agent_interface # Get interface = getattr(real_method, self.__COLLECTION_MARKER) default_return = self.__DEFAULTS.get(interface) # TODO: #2022 Special handling of PropertyMocks? # # Setup # if interface == CONTRACT_ATTRIBUTE: # mock = PropertyMock() # mock_properties[real_method.__name__] = mock # else: mock = Mock(return_value=default_return) # Mark setattr(mock, self.__COLLECTION_MARKER, interface) mock_methods.append(mock) # Bind setattr(self, real_method.__name__, mock) self._MOCK_METHODS = mock_methods self._REAL_METHODS = api_methods def __get_interface_calls(self, interface: Enum) -> List[Callable]: predicate = lambda method: bool(method.contract_api == interface) interface_calls = list(filter(predicate, self._MOCK_METHODS)) return interface_calls @classmethod def __is_contract_method(cls, agent_class: Type[Agent], method_name: str) -> bool: method_or_property = getattr(agent_class, method_name) try: real_method: Callable = method_or_property.fget # Property (getter) except AttributeError: real_method: Callable = method_or_property # Method contract_api: bool = hasattr(real_method, cls.__COLLECTION_MARKER) return contract_api @classmethod def __collect_contract_api( cls, agent_class: Type[Agent]) -> Generator[Callable, None, None]: agent_attrs = dir(agent_class) predicate = cls.__is_contract_method methods = (getattr(agent_class, name) for name in agent_attrs if predicate(agent_class, name)) return methods # # Test Utilities # @property def all_transactions(self) -> List[Callable]: interface = TRANSACTION transaction_functions = self.__get_interface_calls(interface=interface) return transaction_functions @property def contract_calls(self) -> List[Callable]: interface = CONTRACT_CALL transaction_functions = self.__get_interface_calls(interface=interface) return transaction_functions def get_unexpected_transactions( self, allowed: Union[Iterable[Callable], None]) -> List[Callable]: if allowed: predicate = lambda tx: tx not in allowed and tx.called else: predicate = lambda tx: tx.called unexpected_transactions = list(filter(predicate, self.all_transactions)) return unexpected_transactions def assert_only_transactions(self, allowed: Iterable[Callable]) -> None: unexpected_transactions = self.get_unexpected_transactions( allowed=allowed) assert not bool(unexpected_transactions) def assert_no_transactions(self) -> None: unexpected_transactions = self.get_unexpected_transactions( allowed=None) assert not bool(unexpected_transactions) def reset(self, clear_side_effects: bool = True, clear_return_values: bool = True) -> None: for mock in self._MOCK_METHODS: mock.reset_mock(return_value=clear_return_values, side_effect=clear_side_effects) if clear_return_values: interface = getattr(mock, self.__COLLECTION_MARKER) default_return = self.__DEFAULTS.get(interface) mock.return_value = default_return
def _get_memory(step, idx): offset = int(step['stack'][idx], 16) * 2 length = int(step['stack'][idx - 1], 16) * 2 return HexBytes("".join(step['memory'])[offset:offset + length])
def _expand_trace(self): '''Adds the following attributes to each step of the stack trace: address: The address executing this contract. contractName: The name of the contract. fn: The name of the function. jumpDepth: Number of jumps made since entering this contract. The initial value is 0. source: { filename: path to the source file for this step offset: Start and end offset associated source code } ''' if 'trace' in self.__dict__: return if self._trace is None: self._get_trace() self.trace = trace = self._trace if not trace or 'fn' in trace[0]: coverage.add(self.coverage_hash, {}) return # last_map gives a quick reference of previous values at each depth last_map = { 0: { 'address': self.receiver.address, 'contract': self.receiver, 'name': self.receiver._name, 'fn': [self._full_name()], 'jumpDepth': 0, 'pc_map': self.receiver._build['pcMap'] } } coverage_eval = {self.receiver._name: {}} active_branches = set() for i in range(len(trace)): # if depth has increased, tx has called into a different contract if trace[i]['depth'] > trace[i - 1]['depth']: # get call signature stack_idx = -4 if trace[i - 1]['op'] in {'CALL', 'CALLCODE' } else -3 offset = int(trace[i - 1]['stack'][stack_idx], 16) * 2 sig = HexBytes("".join(trace[i - 1]['memory'])[offset:offset + 8]).hex() # get contract and method name address = web3.toChecksumAddress(trace[i - 1]['stack'][-2][-40:]) contract = _contracts.find(address) # update last_map last_map[trace[i]['depth']] = { 'address': address, 'contract': contract, 'name': contract._name, 'fn': [f"{contract._name}.{contract.get_method(sig)}"], 'jumpDepth': 0, 'pc_map': contract._build['pcMap'] } if contract._name not in coverage_eval: coverage_eval[contract._name] = {} # update trace from last_map last = last_map[trace[i]['depth']] trace[i].update({ 'address': last['address'], 'contractName': last['name'], 'fn': last['fn'][-1], 'jumpDepth': last['jumpDepth'], 'source': False }) pc = last['pc_map'][trace[i]['pc']] if 'path' not in pc: continue trace[i]['source'] = { 'filename': pc['path'], 'offset': pc['offset'] } if 'fn' not in pc: continue # calculate coverage if '<string' not in pc['path']: if pc['path'] not in coverage_eval[last['name']]: coverage_eval[last['name']][pc['path']] = [ set(), set(), set() ] if 'statement' in pc: coverage_eval[last['name']][pc['path']][0].add( pc['statement']) if 'branch' in pc: if pc['op'] != "JUMPI": active_branches.add(pc['branch']) elif pc['branch'] in active_branches: # false, true key = 1 if trace[i + 1]['pc'] == trace[i]['pc'] + 1 else 2 coverage_eval[last['name']][pc['path']][key].add( pc['branch']) active_branches.remove(pc['branch']) # ignore jumps with no function - they are compiler optimizations if 'jump' not in pc: continue # jump 'i' is calling into an internal function if pc['jump'] == 'i': try: last['fn'].append(last['pc_map'][trace[i + 1]['pc']]['fn']) last['jumpDepth'] += 1 except KeyError: continue # jump 'o' is returning from an internal function elif pc['jump'] == "o" and last['jumpDepth'] > 0: del last['fn'][-1] last['jumpDepth'] -= 1 coverage.add(self.coverage_hash, dict((k, v) for k, v in coverage_eval.items() if v))
def to_json( self, include_hash=False, include_signature=True, include_exchange_address=None, for_web3=False, ): """Get a json representation of the SignedOrder. Args: include_hash (bool): whether to include the hash field (default: False) include_signature (bool): whether to include the signature (default: True) include_exchange_address (bool): whether to include the exchange_address field (default: None, which means if set to False for web3 and set to True for non-web3 use case) for_web3 (bool): whether the value types should be changed for calling 0x contracts through web3 library (default: False) """ if for_web3: if include_exchange_address is None: include_exchange_address = False order = { "makerAddress": to_checksum_address(self.maker_address_), "takerAddress": to_checksum_address(self.taker_address_), "feeRecipientAddress": to_checksum_address(self.fee_recipient_address_), "senderAddress": to_checksum_address(self.sender_address_), "makerAssetAmount": int(self.maker_asset_amount_), "takerAssetAmount": int(self.taker_asset_amount_), "makerFee": int(self.maker_fee_), "takerFee": int(self.taker_fee_), "salt": int(self.salt_), "expirationTimeSeconds": int(self.expiration_time_seconds_), "makerAssetData": HexBytes(self.maker_asset_data_), "takerAssetData": HexBytes(self.taker_asset_data_), } if include_hash: order["hash"] = HexBytes(self.hash) if include_signature: order["signature"] = HexBytes(self.signature) if include_exchange_address: order["exchangeAddress"] = HexBytes(self.exchange_address_) else: if include_exchange_address is None: include_exchange_address = True order = { "makerAddress": self.maker_address_, "takerAddress": self.taker_address_, "feeRecipientAddress": self.fee_recipient_address_, "senderAddress": self.sender_address_, "makerAssetAmount": self.maker_asset_amount_, "takerAssetAmount": self.taker_asset_amount_, "makerFee": self.maker_fee_, "takerFee": self.taker_fee_, "salt": self.salt_, "expirationTimeSeconds": self.expiration_time_seconds_, "makerAssetData": self.maker_asset_data_, "takerAssetData": self.taker_asset_data_, } if include_hash: order["hash"] = self.hash if include_signature: order["signature"] = self.signature_ if include_exchange_address: order["exchangeAddress"] = self.exchange_address_ return order
def txs_create_or_update_from_tx_hashes(self, tx_hashes: Collection[Union[str, bytes]]) -> List['EthereumTx']: # Search first in database ethereum_txs_dict = OrderedDict.fromkeys([HexBytes(tx_hash).hex() for tx_hash in tx_hashes]) db_ethereum_txs = EthereumTx.objects.filter(tx_hash__in=tx_hashes).exclude(block=None) for db_ethereum_tx in db_ethereum_txs: ethereum_txs_dict[db_ethereum_tx.tx_hash] = db_ethereum_tx # Retrieve from the node the txs missing from database tx_hashes_not_in_db = [tx_hash for tx_hash, ethereum_tx in ethereum_txs_dict.items() if not ethereum_tx] if not tx_hashes_not_in_db: return list(ethereum_txs_dict.values()) self.ethereum_client = EthereumClientProvider() # Get receipts for hashes not in db tx_receipts = [] for tx_hash, tx_receipt in zip(tx_hashes_not_in_db, self.ethereum_client.get_transaction_receipts(tx_hashes_not_in_db)): tx_receipt = tx_receipt or self.ethereum_client.get_transaction_receipt(tx_hash) # Retry fetching if failed if not tx_receipt: raise TransactionNotFoundException(f'Cannot find tx-receipt with tx-hash={HexBytes(tx_hash).hex()}') elif tx_receipt.get('blockNumber') is None: raise TransactionWithoutBlockException(f'Cannot find blockNumber for tx-receipt with ' f'tx-hash={HexBytes(tx_hash).hex()}') else: tx_receipts.append(tx_receipt) # Get transactions for hashes not in db txs = self.ethereum_client.get_transactions(tx_hashes_not_in_db) block_numbers = set() for tx_hash, tx in zip(tx_hashes_not_in_db, txs): tx = tx or self.ethereum_client.get_transaction(tx_hash) # Retry fetching if failed if not tx: raise TransactionNotFoundException(f'Cannot find tx with tx-hash={HexBytes(tx_hash).hex()}') elif tx.get('blockNumber') is None: raise TransactionWithoutBlockException(f'Cannot find blockNumber for tx with ' f'tx-hash={HexBytes(tx_hash).hex()}') block_numbers.add(tx['blockNumber']) blocks = self.ethereum_client.get_blocks(block_numbers) block_dict = {} for block_number, block in zip(block_numbers, blocks): block = block or self.ethereum_client.get_block(block_number) # Retry fetching if failed if not block: raise BlockNotFoundException(f'Block with number={block_number} was not found') assert block_number == block['number'] block_dict[block['number']] = block # Create new transactions or update them if they have no receipt current_block_number = self.ethereum_client.current_block_number for tx, tx_receipt in zip(txs, tx_receipts): block = block_dict.get(tx['blockNumber']) confirmed = (current_block_number - block['number']) >= self.eth_reorg_blocks ethereum_block: EthereumBlock = EthereumBlock.objects.get_or_create_from_block(block, confirmed=confirmed) if HexBytes(ethereum_block.block_hash) != block['hash']: raise EthereumBlockHashMismatch(f'Stored block={ethereum_block.number} ' f'with hash={ethereum_block.block_hash} ' f'is not marching retrieved hash={block["hash"].hex()}') try: ethereum_tx = EthereumTx.objects.get(tx_hash=tx['hash']) # For txs stored before being mined ethereum_tx.update_with_block_and_receipt(ethereum_block, tx_receipt) ethereum_txs_dict[ethereum_tx.tx_hash] = ethereum_tx except EthereumTx.DoesNotExist: ethereum_tx = EthereumTx.objects.create_from_tx_dict(tx, tx_receipt=tx_receipt, ethereum_block=ethereum_block) ethereum_txs_dict[HexBytes(ethereum_tx.tx_hash).hex()] = ethereum_tx return list(ethereum_txs_dict.values())
def __process_decoded_transaction( self, internal_tx_decoded: InternalTxDecoded) -> bool: """ Decode internal tx and creates needed models :param internal_tx_decoded: InternalTxDecoded to process. It will be set as `processed` :return: True if tx could be processed, False otherwise """ internal_tx = internal_tx_decoded.internal_tx logger.debug( "Start processing InternalTxDecoded in tx-hash=%s", HexBytes(internal_tx_decoded.internal_tx.ethereum_tx_id).hex(), ) if internal_tx.gas_used < 1000: # When calling a non existing function, fallback of the proxy does not return any error but we can detect # this kind of functions due to little gas used. Some of this transactions get decoded as they were # valid in old versions of the proxies, like changes to `setup` logger.debug( "Calling a non existing function, will not process it", ) return False function_name = internal_tx_decoded.function_name arguments = internal_tx_decoded.arguments contract_address = internal_tx._from master_copy = internal_tx.to processed_successfully = True if function_name == "setup" and contract_address != NULL_ADDRESS: # Index new Safes logger.debug("Processing Safe setup") owners = arguments["_owners"] threshold = arguments["_threshold"] fallback_handler = arguments.get("fallbackHandler", NULL_ADDRESS) nonce = 0 try: safe_contract: SafeContract = SafeContract.objects.get( address=contract_address) if (not safe_contract.ethereum_tx_id or not safe_contract.erc20_block_number): safe_contract.ethereum_tx = internal_tx.ethereum_tx safe_contract.erc20_block_number = internal_tx.block_number safe_contract.save( update_fields=["ethereum_tx", "erc20_block_number"]) except SafeContract.DoesNotExist: blocks_one_day = int(24 * 60 * 60 / 15) # 15 seconds block SafeContract.objects.create( address=contract_address, ethereum_tx=internal_tx.ethereum_tx, erc20_block_number=max( internal_tx.block_number - blocks_one_day, 0), ) logger.info("Found new Safe=%s", contract_address) self.store_new_safe_status( SafeLastStatus( internal_tx=internal_tx, address=contract_address, owners=owners, threshold=threshold, nonce=nonce, master_copy=master_copy, fallback_handler=fallback_handler, ), internal_tx, ) else: safe_last_status = self.get_last_safe_status_for_address( contract_address) if not safe_last_status: # Usually this happens from Safes coming from a not supported Master Copy # TODO When archive node is available, build SafeStatus from blockchain status logger.debug( "Cannot process trace as `SafeLastStatus` is not found for Safe=%s", contract_address, ) processed_successfully = False elif function_name in ( "addOwnerWithThreshold", "removeOwner", "removeOwnerWithThreshold", ): logger.debug("Processing owner/threshold modification") safe_last_status.threshold = (arguments["_threshold"] or safe_last_status.threshold ) # Event doesn't have threshold owner = arguments["owner"] if function_name == "addOwnerWithThreshold": safe_last_status.owners.insert(0, owner) else: # removeOwner, removeOwnerWithThreshold self.swap_owner(internal_tx, safe_last_status, owner, None) self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "swapOwner": logger.debug("Processing owner swap") old_owner = arguments["oldOwner"] new_owner = arguments["newOwner"] self.swap_owner(internal_tx, safe_last_status, old_owner, new_owner) self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "changeThreshold": logger.debug("Processing threshold change") safe_last_status.threshold = arguments["_threshold"] self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "changeMasterCopy": logger.debug("Processing master copy change") # TODO Ban address if it doesn't have a valid master copy old_safe_version = self.get_safe_version_from_master_copy( safe_last_status.master_copy) safe_last_status.master_copy = arguments["_masterCopy"] new_safe_version = self.get_safe_version_from_master_copy( safe_last_status.master_copy) if (old_safe_version and new_safe_version and self.is_version_breaking_signatures( old_safe_version, new_safe_version)): # Transactions queued not executed are not valid anymore MultisigTransaction.objects.queued( contract_address).delete() self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "setFallbackHandler": logger.debug("Setting FallbackHandler") safe_last_status.fallback_handler = arguments["handler"] self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "setGuard": safe_last_status.guard = (arguments["guard"] if arguments["guard"] != NULL_ADDRESS else None) if safe_last_status.guard: logger.debug("Setting Guard") else: logger.debug("Unsetting Guard") self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "enableModule": logger.debug("Enabling Module") safe_last_status.enabled_modules.append(arguments["module"]) self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "disableModule": logger.debug("Disabling Module") safe_last_status.enabled_modules.remove(arguments["module"]) self.store_new_safe_status(safe_last_status, internal_tx) elif function_name in { "execTransactionFromModule", "execTransactionFromModuleReturnData", }: logger.debug("Executing Tx from Module") # TODO Add test with previous traces for processing a module transaction ethereum_tx = internal_tx.ethereum_tx if "module" in arguments: # L2 Safe with event SafeModuleTransaction indexed using events module_address = arguments["module"] else: # Regular Safe indexed using tracing # Someone calls Module -> Module calls Safe Proxy -> Safe Proxy delegate calls Master Copy # The trace that is been processed is the last one, so indexer needs to get the previous trace previous_trace = self.ethereum_client.parity.get_previous_trace( internal_tx.ethereum_tx_id, internal_tx.trace_address_as_list, skip_delegate_calls=True, ) if not previous_trace: message = ( f"Cannot find previous trace for tx-hash={HexBytes(internal_tx.ethereum_tx_id).hex()} " f"and trace-address={internal_tx.trace_address}") logger.warning(message) raise ValueError(message) module_internal_tx = InternalTx.objects.build_from_trace( previous_trace, internal_tx.ethereum_tx) module_address = (module_internal_tx._from if module_internal_tx else NULL_ADDRESS) failed = self.is_module_failed(ethereum_tx, module_address, contract_address) module_data = HexBytes(arguments["data"]) ModuleTransaction.objects.get_or_create( internal_tx=internal_tx, defaults={ "created": internal_tx.timestamp, "safe": contract_address, "module": module_address, "to": arguments["to"], "value": arguments["value"], "data": module_data if module_data else None, "operation": arguments["operation"], "failed": failed, }, ) elif function_name == "approveHash": logger.debug("Processing hash approval") multisig_transaction_hash = arguments["hashToApprove"] ethereum_tx = internal_tx.ethereum_tx if "owner" in arguments: # Event approveHash owner = arguments["owner"] else: previous_trace = self.ethereum_client.parity.get_previous_trace( internal_tx.ethereum_tx_id, internal_tx.trace_address_as_list, skip_delegate_calls=True, ) if not previous_trace: message = ( f"Cannot find previous trace for tx-hash={HexBytes(internal_tx.ethereum_tx_id).hex()} and " f"trace-address={internal_tx.trace_address}") logger.warning(message) raise ValueError(message) previous_internal_tx = InternalTx.objects.build_from_trace( previous_trace, internal_tx.ethereum_tx) owner = previous_internal_tx._from safe_signature = SafeSignatureApprovedHash.build_for_owner( owner, multisig_transaction_hash) (multisig_confirmation, _) = MultisigConfirmation.objects.get_or_create( multisig_transaction_hash=multisig_transaction_hash, owner=owner, defaults={ "created": internal_tx.timestamp, "ethereum_tx": ethereum_tx, "signature": safe_signature.export_signature(), "signature_type": safe_signature.signature_type.value, }, ) if not multisig_confirmation.ethereum_tx_id: multisig_confirmation.ethereum_tx = ethereum_tx multisig_confirmation.save(update_fields=["ethereum_tx"]) elif function_name == "execTransaction": logger.debug("Processing transaction execution") # Events for L2 Safes store information about nonce nonce = (arguments["nonce"] if "nonce" in arguments else safe_last_status.nonce) if ("baseGas" in arguments ): # `dataGas` was renamed to `baseGas` in v1.0.0 base_gas = arguments["baseGas"] safe_version = (self.get_safe_version_from_master_copy( safe_last_status.master_copy) or "1.0.0") else: base_gas = arguments["dataGas"] safe_version = "0.0.1" safe_tx = SafeTx( None, contract_address, arguments["to"], arguments["value"], arguments["data"], arguments["operation"], arguments["safeTxGas"], base_gas, arguments["gasPrice"], arguments["gasToken"], arguments["refundReceiver"], HexBytes(arguments["signatures"]), safe_nonce=nonce, safe_version=safe_version, chain_id=self.ethereum_client.get_chain_id(), ) safe_tx_hash = safe_tx.safe_tx_hash ethereum_tx = internal_tx.ethereum_tx failed = self.is_failed(ethereum_tx, safe_tx_hash) multisig_tx, _ = MultisigTransaction.objects.get_or_create( safe_tx_hash=safe_tx_hash, defaults={ "created": internal_tx.timestamp, "safe": contract_address, "ethereum_tx": ethereum_tx, "to": safe_tx.to, "value": safe_tx.value, "data": safe_tx.data if safe_tx.data else None, "operation": safe_tx.operation, "safe_tx_gas": safe_tx.safe_tx_gas, "base_gas": safe_tx.base_gas, "gas_price": safe_tx.gas_price, "gas_token": safe_tx.gas_token, "refund_receiver": safe_tx.refund_receiver, "nonce": safe_tx.safe_nonce, "signatures": safe_tx.signatures, "failed": failed, "trusted": True, }, ) # Don't modify created if not multisig_tx.ethereum_tx_id: multisig_tx.ethereum_tx = ethereum_tx multisig_tx.failed = failed multisig_tx.signatures = HexBytes(arguments["signatures"]) multisig_tx.trusted = True multisig_tx.save(update_fields=[ "ethereum_tx", "failed", "signatures", "trusted" ]) for safe_signature in SafeSignature.parse_signature( safe_tx.signatures, safe_tx_hash): ( multisig_confirmation, _, ) = MultisigConfirmation.objects.get_or_create( multisig_transaction_hash=safe_tx_hash, owner=safe_signature.owner, defaults={ "created": internal_tx.timestamp, "ethereum_tx": None, "multisig_transaction": multisig_tx, "signature": safe_signature.export_signature(), "signature_type": safe_signature.signature_type.value, }, ) if multisig_confirmation.signature != safe_signature.signature: multisig_confirmation.signature = ( safe_signature.export_signature()) multisig_confirmation.signature_type = ( safe_signature.signature_type.value) multisig_confirmation.save( update_fields=["signature", "signature_type"]) safe_last_status.nonce = nonce + 1 self.store_new_safe_status(safe_last_status, internal_tx) elif function_name == "execTransactionFromModule": logger.debug("Not processing execTransactionFromModule") # No side effects or nonce increasing, but trace will be set as processed else: processed_successfully = False logger.warning( "Cannot process InternalTxDecoded function_name=%s and arguments=%s", function_name, arguments, ) logger.debug("End processing") return processed_successfully
def __init__(self, receipt): self.raw_receipt = receipt self.transaction_hash = receipt['transactionHash'] self.gas_used = receipt['gasUsed'] self.transfers = [] self.result = None receipt_logs = receipt['logs'] if (receipt_logs is not None) and (len(receipt_logs) > 0): self.successful = True for receipt_log in receipt_logs: if len(receipt_log['topics']) > 0: # $ seth keccak $(seth --from-ascii "Transfer(address,address,uint256)") # 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef if receipt_log['topics'][0] == HexBytes( '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' ): from pymaker.token import ERC20Token transfer_abi = [ abi for abi in ERC20Token.abi if abi.get('name') == 'Transfer' ][0] event_data = get_event_data(transfer_abi, receipt_log) self.transfers.append( Transfer( token_address=Address(event_data['address']), from_address=Address( event_data['args']['from']), to_address=Address(event_data['args']['to']), value=Wad(event_data['args']['value']))) # $ seth keccak $(seth --from-ascii "Mint(address,uint256)") # 0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885 if receipt_log['topics'][0] == HexBytes( '0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885' ): from pymaker.token import DSToken transfer_abi = [ abi for abi in DSToken.abi if abi.get('name') == 'Mint' ][0] event_data = get_event_data(transfer_abi, receipt_log) self.transfers.append( Transfer( token_address=Address(event_data['address']), from_address=Address( '0x0000000000000000000000000000000000000000' ), to_address=Address(event_data['args']['guy']), value=Wad(event_data['args']['wad']))) # $ seth keccak $(seth --from-ascii "Burn(address,uint256)") # 0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5 if receipt_log['topics'][0] == HexBytes( '0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5' ): from pymaker.token import DSToken transfer_abi = [ abi for abi in DSToken.abi if abi.get('name') == 'Burn' ][0] event_data = get_event_data(transfer_abi, receipt_log) self.transfers.append( Transfer( token_address=Address(event_data['address']), from_address=Address( event_data['args']['guy']), to_address=Address( '0x0000000000000000000000000000000000000000' ), value=Wad(event_data['args']['wad']))) else: self.successful = False
from eth_typing import ( ChecksumAddress, HexAddress, HexStr, ) from hexbytes import ( HexBytes, ) ACCEPTABLE_STALE_HOURS = 48 AUCTION_START_GAS_CONSTANT = 25000 AUCTION_START_GAS_MARGINAL = 39000 EMPTY_SHA3_BYTES = HexBytes(b'\0' * 32) EMPTY_ADDR_HEX = HexAddress(HexStr('0x' + '00' * 20)) REVERSE_REGISTRAR_DOMAIN = 'addr.reverse' ENS_MAINNET_ADDR = ChecksumAddress( HexAddress(HexStr('0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'))) # --- interface ids --- # GET_TEXT_INTERFACE_ID = HexStr("0x59d1d43c") EXTENDED_RESOLVER_INTERFACE_ID = HexStr('0x9061b923') # ENSIP-10
def test_create_from_factory( get_contract, deploy_factory_for, w3, keccak, create2_address_of, assert_tx_failed, factory_prefix, ): code = """ @external def foo() -> uint256: return 123 """ prefix_len = len(factory_prefix) deployer_code = f""" created_address: public(address) @external def test(target: address): self.created_address = create_from_factory(target, code_offset={prefix_len}) @external def test2(target: address, salt: bytes32): self.created_address = create_from_factory(target, code_offset={prefix_len}, salt=salt) """ # deploy a foo so we can compare its bytecode with factory deployed version foo_contract = get_contract(code) expected_runtime_code = w3.eth.get_code(foo_contract.address) f, FooContract = deploy_factory_for(code, initcode_prefix=factory_prefix) d = get_contract(deployer_code) d.test(f.address, transact={}) test = FooContract(d.created_address()) assert w3.eth.get_code(test.address) == expected_runtime_code assert test.foo() == 123 # extcodesize check zero_address = "0x" + "00" * 20 assert_tx_failed(lambda: d.test(zero_address)) # now same thing but with create2 salt = keccak(b"vyper") d.test2(f.address, salt, transact={}) test = FooContract(d.created_address()) assert w3.eth.get_code(test.address) == expected_runtime_code assert test.foo() == 123 # check if the create2 address matches our offchain calculation initcode = w3.eth.get_code(f.address) initcode = initcode[len(factory_prefix):] # strip the prefix assert HexBytes(test.address) == create2_address_of( d.address, salt, initcode) # can't collide addresses assert_tx_failed(lambda: d.test2(f.address, salt))
def build_webhook_payload( sender: Type[Model], instance: Union[TokenTransfer, InternalTx, MultisigConfirmation, MultisigTransaction], ) -> List[Dict[str, Any]]: """ :param sender: Sender type :param instance: Sender instance :return: A list of webhooks generated from the instance provided """ payloads: List[Dict[str, Any]] = [] if sender == MultisigConfirmation and instance.multisig_transaction_id: payloads = [{ "address": instance.multisig_transaction.safe, # This could make a db call "type": WebHookType.NEW_CONFIRMATION.name, "owner": instance.owner, "safeTxHash": HexBytes(instance.multisig_transaction.safe_tx_hash).hex(), }] elif sender == MultisigTransaction: payload = { "address": instance.safe, # 'type': None, It will be assigned later "safeTxHash": HexBytes(instance.safe_tx_hash).hex(), } if instance.executed: payload["type"] = WebHookType.EXECUTED_MULTISIG_TRANSACTION.name payload["failed"] = json.dumps( instance.failed) # Firebase only accepts strings payload["txHash"] = HexBytes(instance.ethereum_tx_id).hex() else: payload["type"] = WebHookType.PENDING_MULTISIG_TRANSACTION.name payloads = [payload] elif sender == InternalTx and instance.is_ether_transfer: # INCOMING_ETHER incoming_payload = { "address": instance.to, "type": WebHookType.INCOMING_ETHER.name, "txHash": HexBytes(instance.ethereum_tx_id).hex(), "value": str(instance.value), } outgoing_payload = dict(incoming_payload) outgoing_payload["type"] = WebHookType.OUTGOING_ETHER.name outgoing_payload["address"] = instance._from payloads = [incoming_payload, outgoing_payload] elif sender in (ERC20Transfer, ERC721Transfer): # INCOMING_TOKEN / OUTGOING_TOKEN incoming_payload = { "address": instance.to, "type": WebHookType.INCOMING_TOKEN.name, "tokenAddress": instance.address, "txHash": HexBytes(instance.ethereum_tx_id).hex(), } if isinstance(instance, ERC20Transfer): incoming_payload["value"] = str(instance.value) else: incoming_payload["tokenId"] = str(instance.token_id) outgoing_payload = dict(incoming_payload) outgoing_payload["type"] = WebHookType.OUTGOING_TOKEN.name outgoing_payload["address"] = instance._from payloads = [incoming_payload, outgoing_payload] elif sender == SafeContract: # Safe created payloads = [{ "address": instance.address, "type": WebHookType.SAFE_CREATED.name, "txHash": HexBytes(instance.ethereum_tx_id).hex(), "blockNumber": instance.created_block_number, }] elif sender == ModuleTransaction: payloads = [{ "address": instance.safe, "type": WebHookType.MODULE_TRANSACTION.name, "module": instance.module, "txHash": HexBytes(instance.internal_tx.ethereum_tx_id).hex(), }] # Add chainId to every payload for payload in payloads: payload["chainId"] = str(get_ethereum_network().value) return payloads
def test_create_from_factory_args(get_contract, deploy_factory_for, w3, keccak, create2_address_of, assert_tx_failed): code = """ struct Bar: x: String[32] FOO: immutable(String[128]) BAR: immutable(Bar) @external def __init__(foo: String[128], bar: Bar): FOO = foo BAR = bar @external def foo() -> String[128]: return FOO @external def bar() -> Bar: return BAR """ deployer_code = """ struct Bar: x: String[32] created_address: public(address) @external def test(target: address, arg1: String[128], arg2: Bar): self.created_address = create_from_factory(target, arg1, arg2) @external def test2(target: address, arg1: String[128], arg2: Bar, salt: bytes32): self.created_address = create_from_factory(target, arg1, arg2, salt=salt) @external def should_fail(target: address, arg1: String[129], arg2: Bar): self.created_address = create_from_factory(target, arg1, arg2) """ FOO = "hello!" BAR = ("world!", ) # deploy a foo so we can compare its bytecode with factory deployed version foo_contract = get_contract(code, FOO, BAR) expected_runtime_code = w3.eth.get_code(foo_contract.address) f, FooContract = deploy_factory_for(code) d = get_contract(deployer_code) initcode = w3.eth.get_code(f.address) d.test(f.address, FOO, BAR, transact={}) test = FooContract(d.created_address()) assert w3.eth.get_code(test.address) == expected_runtime_code assert test.foo() == FOO assert test.bar() == BAR # extcodesize check assert_tx_failed(lambda: d.test("0x" + "00" * 20, FOO, BAR)) # now same thing but with create2 salt = keccak(b"vyper") d.test2(f.address, FOO, BAR, salt, transact={}) test = FooContract(d.created_address()) assert w3.eth.get_code(test.address) == expected_runtime_code assert test.foo() == FOO assert test.bar() == BAR encoded_args = encode_single("(string,(string))", (FOO, BAR)) assert HexBytes(test.address) == create2_address_of( d.address, salt, initcode + encoded_args) # can't collide addresses assert_tx_failed(lambda: d.test2(f.address, FOO, BAR, salt)) # but creating a contract with different args is ok FOO = "bar" d.test2(f.address, FOO, BAR, salt, transact={}) # just for kicks assert FooContract(d.created_address()).foo() == FOO assert FooContract(d.created_address()).bar() == BAR # Foo constructor should fail FOO = b"\x01" * 129 BAR = ("", ) assert_tx_failed(lambda: d.should_fail(f.address, FOO, BAR))
class EthModuleTest(object): def test_eth_protocolVersion(self, web3): protocol_version = web3.version.ethereum assert is_string(protocol_version) assert protocol_version.isdigit() def test_eth_syncing(self, web3): syncing = web3.eth.syncing assert is_boolean(syncing) or is_dict(syncing) if is_boolean(syncing): assert syncing is False elif is_dict(syncing): assert 'startingBlock' in syncing assert 'currentBlock' in syncing assert 'highestBlock' in syncing assert is_integer(syncing['startingBlock']) assert is_integer(syncing['currentBlock']) assert is_integer(syncing['highestBlock']) def test_eth_coinbase(self, web3): coinbase = web3.eth.coinbase assert is_checksum_address(coinbase) def test_eth_mining(self, web3): mining = web3.eth.mining assert is_boolean(mining) def test_eth_hashrate(self, web3): hashrate = web3.eth.hashrate assert is_integer(hashrate) assert hashrate >= 0 def test_eth_gasPrice(self, web3): gas_price = web3.eth.gasPrice assert is_integer(gas_price) assert gas_price > 0 def test_eth_accounts(self, web3): accounts = web3.eth.accounts assert is_list_like(accounts) assert len(accounts) != 0 assert all((is_checksum_address(account) for account in accounts)) assert web3.eth.coinbase in accounts def test_eth_blockNumber(self, web3): block_number = web3.eth.blockNumber assert is_integer(block_number) assert block_number >= 0 def test_eth_getBalance(self, web3): coinbase = web3.eth.coinbase with pytest.raises(InvalidAddress): web3.eth.getBalance(coinbase.lower()) balance = web3.eth.getBalance(coinbase) assert is_integer(balance) assert balance >= 0 def test_eth_getStorageAt(self, web3): coinbase = web3.eth.coinbase with pytest.raises(InvalidAddress): web3.eth.getStorageAt(coinbase.lower(), 0) def test_eth_getTransactionCount(self, web3): coinbase = web3.eth.coinbase transaction_count = web3.eth.getTransactionCount(coinbase) with pytest.raises(InvalidAddress): web3.eth.getTransactionCount(coinbase.lower()) assert is_integer(transaction_count) assert transaction_count >= 0 def test_eth_getBlockTransactionCountByHash_empty_block( self, web3, empty_block): transaction_count = web3.eth.getBlockTransactionCount( empty_block['hash']) assert is_integer(transaction_count) assert transaction_count == 0 def test_eth_getBlockTransactionCountByNumber_empty_block( self, web3, empty_block): transaction_count = web3.eth.getBlockTransactionCount( empty_block['number']) assert is_integer(transaction_count) assert transaction_count == 0 def test_eth_getBlockTransactionCountByHash_block_with_txn( self, web3, block_with_txn): transaction_count = web3.eth.getBlockTransactionCount( block_with_txn['hash']) assert is_integer(transaction_count) assert transaction_count >= 1 def test_eth_getBlockTransactionCountByNumber_block_with_txn( self, web3, block_with_txn): transaction_count = web3.eth.getBlockTransactionCount( block_with_txn['number']) assert is_integer(transaction_count) assert transaction_count >= 1 def test_eth_getUncleCountByBlockHash(self, web3, empty_block): uncle_count = web3.eth.getUncleCount(empty_block['hash']) assert is_integer(uncle_count) assert uncle_count == 0 def test_eth_getUncleCountByBlockNumber(self, web3, empty_block): uncle_count = web3.eth.getUncleCount(empty_block['number']) assert is_integer(uncle_count) assert uncle_count == 0 def test_eth_getCode(self, web3, math_contract): code = web3.eth.getCode(math_contract.address) with pytest.raises(InvalidAddress): code = web3.eth.getCode(math_contract.address.lower()) assert is_string(code) assert len(code) > 2 def test_eth_sign(self, web3, unlocked_account): signature = web3.eth.sign(unlocked_account, text='Message tö sign. Longer than hash!') assert is_bytes(signature) assert len(signature) == 32 + 32 + 1 # test other formats hexsign = web3.eth.sign( unlocked_account, hexstr= '0x4d6573736167652074c3b6207369676e2e204c6f6e676572207468616e206861736821' ) assert hexsign == signature intsign = web3.eth.sign( unlocked_account, 0x4d6573736167652074c3b6207369676e2e204c6f6e676572207468616e206861736821 ) assert intsign == signature bytessign = web3.eth.sign( unlocked_account, b'Message t\xc3\xb6 sign. Longer than hash!') assert bytessign == signature new_signature = web3.eth.sign(unlocked_account, text='different message is different') assert new_signature != signature def test_eth_sendTransaction_addr_checksum_required( self, web3, unlocked_account): non_checksum_addr = unlocked_account.lower() txn_params = { 'from': unlocked_account, 'to': unlocked_account, 'value': 1, 'gas': 21000, 'gasPrice': web3.eth.gasPrice, } with pytest.raises(InvalidAddress): invalid_params = dict(txn_params, **{'from': non_checksum_addr}) web3.eth.sendTransaction(invalid_params) with pytest.raises(InvalidAddress): invalid_params = dict(txn_params, **{'to': non_checksum_addr}) web3.eth.sendTransaction(invalid_params) def test_eth_sendTransaction(self, web3, unlocked_account): txn_params = { 'from': unlocked_account, 'to': unlocked_account, 'value': 1, 'gas': 21000, 'gasPrice': web3.eth.gasPrice, } txn_hash = web3.eth.sendTransaction(txn_params) txn = web3.eth.getTransaction(txn_hash) assert is_same_address(txn['from'], txn_params['from']) assert is_same_address(txn['to'], txn_params['to']) assert txn['value'] == 1 assert txn['gas'] == 21000 assert txn['gasPrice'] == txn_params['gasPrice'] def test_eth_sendTransaction_with_nonce(self, web3, unlocked_account): txn_params = { 'from': unlocked_account, 'to': unlocked_account, 'value': 1, 'gas': 21000, # Increased gas price to ensure transaction hash different from other tests 'gasPrice': web3.eth.gasPrice * 2, 'nonce': web3.eth.getTransactionCount(unlocked_account), } txn_hash = web3.eth.sendTransaction(txn_params) txn = web3.eth.getTransaction(txn_hash) assert is_same_address(txn['from'], txn_params['from']) assert is_same_address(txn['to'], txn_params['to']) assert txn['value'] == 1 assert txn['gas'] == 21000 assert txn['gasPrice'] == txn_params['gasPrice'] assert txn['nonce'] == txn_params['nonce'] @pytest.mark.parametrize( 'raw_transaction, expected_hash', [ ( # address 0x39EEed73fb1D3855E90Cbd42f348b3D7b340aAA6 '0xf8648085174876e8008252089439eeed73fb1d3855e90cbd42f348b3d7b340aaa601801ba0ec1295f00936acd0c2cb90ab2cdaacb8bf5e11b3d9957833595aca9ceedb7aada05dfc8937baec0e26029057abd3a1ef8c505dca2cdc07ffacb046d090d2bea06a', # noqa: E501 '0x1f80f8ab5f12a45be218f76404bda64d37270a6f4f86ededd0eb599f80548c13', ), ( # private key 0x3c2ab4e8f17a7dea191b8c991522660126d681039509dc3bb31af7c9bdb63518 # This is an unfunded account, but the transaction has a 0 gas price, so is valid. # It never needs to be mined, we just want the transaction hash back to confirm. HexBytes( '0xf85f808082c35094d898d5e829717c72e7438bad593076686d7d164a80801ba005c2e99ecee98a12fbf28ab9577423f42e9e88f2291b3acc8228de743884c874a077d6bc77a47ad41ec85c96aac2ad27f05a039c4787fca8a1e5ee2d8c7ec1bb6a' ), # noqa: E501 '0x98eeadb99454427f6aad7b558bac13e9d225512a6f5e5c11cf48e8d4067e51b5', ), ]) def test_eth_sendRawTransaction(self, web3, raw_transaction, funded_account_for_raw_txn, expected_hash): txn_hash = web3.eth.sendRawTransaction(raw_transaction) assert txn_hash == web3.toBytes(hexstr=expected_hash) def test_eth_call(self, web3, math_contract): coinbase = web3.eth.coinbase txn_params = math_contract._prepare_transaction( fn_name='add', fn_args=(7, 11), transaction={ 'from': coinbase, 'to': math_contract.address }, ) call_result = web3.eth.call(txn_params) assert is_string(call_result) result = decode_single('uint256', call_result) assert result == 18 def test_eth_call_with_0_result(self, web3, math_contract): coinbase = web3.eth.coinbase txn_params = math_contract._prepare_transaction( fn_name='add', fn_args=(0, 0), transaction={ 'from': coinbase, 'to': math_contract.address }, ) call_result = web3.eth.call(txn_params) assert is_string(call_result) result = decode_single('uint256', call_result) assert result == 0 def test_eth_estimateGas(self, web3): coinbase = web3.eth.coinbase gas_estimate = web3.eth.estimateGas({ 'from': coinbase, 'to': coinbase, 'value': 1, }) assert is_integer(gas_estimate) assert gas_estimate > 0 def test_eth_getBlockByHash(self, web3, empty_block): block = web3.eth.getBlock(empty_block['hash']) assert block['hash'] == empty_block['hash'] def test_eth_getBlockByHash_not_found(self, web3, empty_block): block = web3.eth.getBlock(UNKNOWN_HASH) assert block is None def test_eth_getBlockByNumber_with_integer(self, web3, empty_block): block = web3.eth.getBlock(empty_block['number']) assert block['number'] == empty_block['number'] def test_eth_getBlockByNumber_latest(self, web3, empty_block): current_block_number = web3.eth.blockNumber block = web3.eth.getBlock('latest') assert block['number'] == current_block_number def test_eth_getBlockByNumber_not_found(self, web3, empty_block): block = web3.eth.getBlock(12345) assert block is None def test_eth_getBlockByNumber_pending(self, web3, empty_block): current_block_number = web3.eth.blockNumber block = web3.eth.getBlock('pending') assert block['number'] == current_block_number + 1 def test_eth_getBlockByNumber_earliest(self, web3, empty_block): genesis_block = web3.eth.getBlock(0) block = web3.eth.getBlock('earliest') assert block['number'] == 0 assert block['hash'] == genesis_block['hash'] def test_eth_getBlockByNumber_full_transactions(self, web3, block_with_txn): block = web3.eth.getBlock(block_with_txn['number'], True) transaction = block['transactions'][0] assert transaction['hash'] == block_with_txn['transactions'][0] def test_eth_getTransactionByHash(self, web3, mined_txn_hash): transaction = web3.eth.getTransaction(mined_txn_hash) assert is_dict(transaction) assert transaction['hash'] == HexBytes(mined_txn_hash) def test_eth_getTransactionByHash_contract_creation( self, web3, math_contract_deploy_txn_hash): transaction = web3.eth.getTransaction(math_contract_deploy_txn_hash) assert is_dict(transaction) assert transaction['to'] is None def test_eth_getTransactionByBlockHashAndIndex(self, web3, block_with_txn, mined_txn_hash): transaction = web3.eth.getTransactionFromBlock(block_with_txn['hash'], 0) assert is_dict(transaction) assert transaction['hash'] == HexBytes(mined_txn_hash) def test_eth_getTransactionByBlockNumberAndIndex(self, web3, block_with_txn, mined_txn_hash): transaction = web3.eth.getTransactionFromBlock( block_with_txn['number'], 0) assert is_dict(transaction) assert transaction['hash'] == HexBytes(mined_txn_hash) def test_eth_getTransactionReceipt_mined(self, web3, block_with_txn, mined_txn_hash): receipt = web3.eth.getTransactionReceipt(mined_txn_hash) assert is_dict(receipt) assert receipt['blockNumber'] == block_with_txn['number'] assert receipt['blockHash'] == block_with_txn['hash'] assert receipt['transactionIndex'] == 0 assert receipt['transactionHash'] == HexBytes(mined_txn_hash) def test_eth_getTransactionReceipt_unmined(self, web3, unlocked_account): txn_hash = web3.eth.sendTransaction({ 'from': unlocked_account, 'to': unlocked_account, 'value': 1, 'gas': 21000, 'gasPrice': web3.eth.gasPrice, }) receipt = web3.eth.getTransactionReceipt(txn_hash) assert receipt is None def test_eth_getTransactionReceipt_with_log_entry(self, web3, block_with_txn_with_log, emitter_contract, txn_hash_with_log): receipt = web3.eth.getTransactionReceipt(txn_hash_with_log) assert is_dict(receipt) assert receipt['blockNumber'] == block_with_txn_with_log['number'] assert receipt['blockHash'] == block_with_txn_with_log['hash'] assert receipt['transactionIndex'] == 0 assert receipt['transactionHash'] == HexBytes(txn_hash_with_log) assert len(receipt['logs']) == 1 log_entry = receipt['logs'][0] assert log_entry['blockNumber'] == block_with_txn_with_log['number'] assert log_entry['blockHash'] == block_with_txn_with_log['hash'] assert log_entry['logIndex'] == 0 assert is_same_address(log_entry['address'], emitter_contract.address) assert log_entry['transactionIndex'] == 0 assert log_entry['transactionHash'] == HexBytes(txn_hash_with_log) def test_eth_getUncleByBlockHashAndIndex(self, web3): # TODO: how do we make uncles.... pass def test_eth_getUncleByBlockNumberAndIndex(self, web3): # TODO: how do we make uncles.... pass def test_eth_getCompilers(self, web3): # TODO: do we want to test this? pass def test_eth_compileSolidity(self, web3): # TODO: do we want to test this? pass def test_eth_compileLLL(self, web3): # TODO: do we want to test this? pass def test_eth_compileSerpent(self, web3): # TODO: do we want to test this? pass def test_eth_newFilter(self, web3): filter = web3.eth.filter({}) changes = web3.eth.getFilterChanges(filter.filter_id) assert is_list_like(changes) assert not changes logs = web3.eth.getFilterLogs(filter.filter_id) assert is_list_like(logs) assert not logs result = web3.eth.uninstallFilter(filter.filter_id) assert result is True def test_eth_newBlockFilter(self, web3): filter = web3.eth.filter('latest') assert is_string(filter.filter_id) changes = web3.eth.getFilterChanges(filter.filter_id) assert is_list_like(changes) assert not changes # TODO: figure out why this fails in go-ethereum # logs = web3.eth.getFilterLogs(filter.filter_id) # assert is_list_like(logs) # assert not logs result = web3.eth.uninstallFilter(filter.filter_id) assert result is True def test_eth_newPendingTransactionFilter(self, web3): filter = web3.eth.filter('pending') assert is_string(filter.filter_id) changes = web3.eth.getFilterChanges(filter.filter_id) assert is_list_like(changes) assert not changes # TODO: figure out why this fails in go-ethereum # logs = web3.eth.getFilterLogs(filter.filter_id) # assert is_list_like(logs) # assert not logs result = web3.eth.uninstallFilter(filter.filter_id) assert result is True def test_eth_getLogs_without_logs(self, web3, block_with_txn_with_log): # Test with block range filter_params = { "fromBlock": 0, "toBlock": block_with_txn_with_log['number'] - 1, } result = web3.eth.getLogs(filter_params) assert len(result) == 0 # the range is wrong filter_params = { "fromBlock": block_with_txn_with_log['number'], "toBlock": block_with_txn_with_log['number'] - 1, } result = web3.eth.getLogs(filter_params) assert len(result) == 0 # Test with `address` # filter with other address filter_params = { "fromBlock": 0, "address": UNKNOWN_ADDRESS, } result = web3.eth.getLogs(filter_params) assert len(result) == 0 def test_eth_getLogs_with_logs(self, web3, block_with_txn_with_log, emitter_contract, txn_hash_with_log): def assert_contains_log(result): assert len(result) == 1 log_entry = result[0] assert log_entry['blockNumber'] == block_with_txn_with_log[ 'number'] assert log_entry['blockHash'] == block_with_txn_with_log['hash'] assert log_entry['logIndex'] == 0 assert is_same_address(log_entry['address'], emitter_contract.address) assert log_entry['transactionIndex'] == 0 assert log_entry['transactionHash'] == HexBytes(txn_hash_with_log) # Test with block range # the range includes the block where the log resides in filter_params = { "fromBlock": block_with_txn_with_log['number'], "toBlock": block_with_txn_with_log['number'], } result = web3.eth.getLogs(filter_params) assert_contains_log(result) # specify only `from_block`. by default `to_block` should be 'latest' filter_params = { "fromBlock": 0, } result = web3.eth.getLogs(filter_params) assert_contains_log(result) # Test with `address` # filter with emitter_contract.address filter_params = { "fromBlock": 0, "address": emitter_contract.address, } result = web3.eth.getLogs(filter_params) assert_contains_log(result) def test_eth_uninstallFilter(self, web3): filter = web3.eth.filter({}) assert is_string(filter.filter_id) success = web3.eth.uninstallFilter(filter.filter_id) assert success is True failure = web3.eth.uninstallFilter(filter.filter_id) assert failure is False
"nonce": 0, "gasPrice": 1000000000000, "gas": 10000, "to": "", "value": 0, "data": "6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f2", # noqa: 501 "unsigned": "f83f8085e8d4a510008227108080af6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f2808080", # noqa: 501 "signed": "f87f8085e8d4a510008227108080af6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f21ba05afed0244d0da90b67cf8979b0f246432a5112c0d31e8d5eedd2bc17b171c694a0bb1035c834677c2e1185b8dc90ca6d1fa585ab3d7ef23707e1a497a98e752d1b" # noqa: 501 }, { "chainId": None, "key": "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", "nonce": 0, "gasPrice": 1000000000000, "gas": 10000, "to": HexBytes("0x13978aee95f38490e9769C39B2773Ed763d9cd5F"), "value": 10000000000000000, "data": "", "unsigned": "eb8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc1000080808080", # noqa: 501 "signed": "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1" # noqa: 501 }, ] PRIVATE_KEY_AS_BYTES = b'unicorns' * 4 PRIVATE_KEY_AS_HEXSTR = '0x756e69636f726e73756e69636f726e73756e69636f726e73756e69636f726e73' PRIVATE_KEY_AS_INT = 0x756e69636f726e73756e69636f726e73756e69636f726e73756e69636f726e73 PRIVATE_KEY_AS_OBJ = keys.PrivateKey(PRIVATE_KEY_AS_BYTES) ACCT_ADDRESS = '0xa79F6f349C853F9Ea0B29636779ae3Cb4E3BA729' PRIVATE_KEY_AS_BYTES_ALT = b'rainbows' * 4
def test_eth_getTransactionByHash(self, web3, mined_txn_hash): transaction = web3.eth.getTransaction(mined_txn_hash) assert is_dict(transaction) assert transaction['hash'] == HexBytes(mined_txn_hash)
15655399131600894366408541311673616702363115109327707006109616887384920764603, ) msg_hash = b'\xbb\r\x8a\xba\x9f\xf7\xa1<N,s{i\x81\x86r\x83{\xba\x9f\xe2\x1d\xaa\xdd\xb3\xd6\x01\xda\x00\xb7)\xa1' # noqa: E501 from_account = acct.recoverHash(msg_hash, vrs=(v, r, s)) assert from_account == '0xFeC2079e80465cc8C687fFF9EE6386ca447aFec4' @pytest.mark.parametrize( 'message, key, expected_bytes, expected_hash, v, r, s, signature', ( ( 'Some data', '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', b'Some data', HexBytes( '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655' ), 28, 83713930994764734002432606962255364472443135907807238282514898577139886061053, 43435997768575461196683613590576722655951133545204789519877940758262837256233, HexBytes( '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c' ), # noqa: E501 ), ( '10284', '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', b'10284', HexBytes( '0x0a162a5efbba02f38db3114531c8acba39fe676f09f7e471d93e8a06c471821c' ),
def test_eth_getTransactionByBlockHashAndIndex(self, web3, block_with_txn, mined_txn_hash): transaction = web3.eth.getTransactionFromBlock(block_with_txn['hash'], 0) assert is_dict(transaction) assert transaction['hash'] == HexBytes(mined_txn_hash)
def contract_call_string(call_result): length = int(call_result[66:130], 16) * 2 data = call_result[130:130 + length] ret = HexBytes(data).decode() return ret.lower()
class Web3ModuleTest: def test_web3_clientVersion(self, web3: Web3) -> None: client_version = web3.clientVersion self._check_web3_clientVersion(client_version) def _check_web3_clientVersion(self, client_version: str) -> NoReturn: raise NotImplementedError("Must be implemented by subclasses") # Contract that calculated test values can be found at # https://kovan.etherscan.io/address/0xb9be06f5b99372cf9afbccadbbb9954ccaf7f4bb#code @pytest.mark.parametrize( 'types,values,expected', ( ( ['bool'], [True], HexBytes("0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2"), ), ( ['uint8', 'uint8', 'uint8'], [97, 98, 99], HexBytes("0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"), ), ( ['uint248'], [30], HexBytes("0x30f95d210785601eb33ae4d53d405b26f920e765dff87cca8e9a4aec99f82671"), ), ( ['bool', 'uint16'], [True, 299], HexBytes("0xed18599ccd80ee9fae9a28b0e34a5573c3233d7468f808fd659bc171cf0b43bd"), ), ( ['int256'], [-10], HexBytes("0xd6fb717f7e270a360f5093ce6a7a3752183e89c9a9afe5c0cb54b458a304d3d5"), ), ( ['int256'], [10], HexBytes("0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8"), ), ( ['int8', 'uint8'], [-10, 18], HexBytes("0x5c6ab1e634c08d9c0f4df4d789e8727943ef010dd7ca8e3c89de197a26d148be"), ), ( ['address'], ["0x49eddd3769c0712032808d86597b84ac5c2f5614"], InvalidAddress, ), ( ['address'], ["0x49EdDD3769c0712032808D86597B84ac5c2F5614"], HexBytes("0x2ff37b5607484cd4eecf6d13292e22bd6e5401eaffcc07e279583bc742c68882"), ), ( ['bytes2'], ['0x5402'], HexBytes("0x4ed9171bda52fca71ab28e7f452bd6eacc3e5a568a47e0fa53b503159a9b8910"), ), ( ['bytes3'], ['0x5402'], HexBytes("0x4ed9171bda52fca71ab28e7f452bd6eacc3e5a568a47e0fa53b503159a9b8910"), ), ( ['bytes'], [ '0x636865636b6c6f6e6762797465737472696e676167' '61696e7374736f6c6964697479736861336861736866756e6374696f6e' ], HexBytes("0xd78a84d65721b67e4011b10c99dafdedcdcd7cb30153064f773e210b4762e22f"), ), ( ['string'], ['testing a string!'], HexBytes("0xe8c275c0b4070a5ec6cfcb83f0ba394b30ddd283de785d43f2eabfb04bd96747"), ), ( ['string', 'bool', 'uint16', 'bytes2', 'address'], [ 'testing a string!', False, 299, '0x5402', "0x49eddd3769c0712032808d86597b84ac5c2f5614", ], InvalidAddress, ), ( ['string', 'bool', 'uint16', 'bytes2', 'address'], [ 'testing a string!', False, 299, '0x5402', "0x49EdDD3769c0712032808D86597B84ac5c2F5614", ], HexBytes("0x8cc6eabb25b842715e8ca39e2524ed946759aa37bfb7d4b81829cf5a7e266103"), ), ( ['bool[2][]'], [[[True, False], [False, True]]], HexBytes("0x1eef261f2eb51a8c736d52be3f91ff79e78a9ec5df2b7f50d0c6f98ed1e2bc06"), ), ( ['bool[]'], [[True, False, True]], HexBytes("0x5c6090c0461491a2941743bda5c3658bf1ea53bbd3edcde54e16205e18b45792"), ), ( ['uint24[]'], [[1, 0, 1]], HexBytes("0x5c6090c0461491a2941743bda5c3658bf1ea53bbd3edcde54e16205e18b45792"), ), ( ['uint8[2]'], [[8, 9]], HexBytes("0xc7694af312c4f286114180fd0ba6a52461fcee8a381636770b19a343af92538a"), ), ( ['uint256[2]'], [[8, 9]], HexBytes("0xc7694af312c4f286114180fd0ba6a52461fcee8a381636770b19a343af92538a"), ), ( ['uint8[]'], [[8]], HexBytes("0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3"), ), ( ['address[]'], [[ "0x49EdDD3769c0712032808D86597B84ac5c2F5614", "0xA6b759bBbf4B59D24acf7E06e79f3a5D104fdCE5", ]], HexBytes("0xb98565c0c26a962fd54d93b0ed6fb9296e03e9da29d2281ed3e3473109ef7dde"), ), ( ['address[]'], [[ "0x49EdDD3769c0712032808D86597B84ac5c2F5614", "0xa6b759bbbf4b59d24acf7e06e79f3a5d104fdce5", ]], InvalidAddress, ), ), ) def test_solidityKeccak( self, web3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): with pytest.raises(expected): web3.solidityKeccak(types, values) return actual = web3.solidityKeccak(types, values) assert actual == expected @pytest.mark.parametrize( 'types, values, expected', ( ( ['address'], ['one.eth'], HexBytes("0x2ff37b5607484cd4eecf6d13292e22bd6e5401eaffcc07e279583bc742c68882"), ), ( ['address[]'], [['one.eth', 'two.eth']], HexBytes("0xb98565c0c26a962fd54d93b0ed6fb9296e03e9da29d2281ed3e3473109ef7dde"), ), ), ) def test_solidityKeccak_ens( self, web3: "Web3", types: Sequence[TypeStr], values: Sequence[str], expected: HexBytes ) -> None: with ens_addresses(web3, { 'one.eth': ChecksumAddress( HexAddress(HexStr("0x49EdDD3769c0712032808D86597B84ac5c2F5614")) ), 'two.eth': ChecksumAddress( HexAddress(HexStr("0xA6b759bBbf4B59D24acf7E06e79f3a5D104fdCE5")) ), }): # when called as class method, any name lookup attempt will fail with pytest.raises(InvalidAddress): Web3.solidityKeccak(types, values) # when called as instance method, ens lookups can succeed actual = web3.solidityKeccak(types, values) assert actual == expected @pytest.mark.parametrize( 'types,values', ( (['address'], ['0xA6b759bBbf4B59D24acf7E06e79f3a5D104fdCE5', True]), (['address', 'bool'], ['0xA6b759bBbf4B59D24acf7E06e79f3a5D104fdCE5']), ([], ['0xA6b759bBbf4B59D24acf7E06e79f3a5D104fdCE5']), ) ) def test_solidityKeccak_same_number_of_types_and_values( self, web3: "Web3", types: Sequence[TypeStr], values: Sequence[Any] ) -> None: with pytest.raises(ValueError): web3.solidityKeccak(types, values) def test_is_connected(self, web3: "Web3") -> None: assert web3.isConnected()
def test_encode(test_case): expected = test_case["expected_raw_transaction"] transaction = TypedTransaction.from_dict(test_case["transaction"]) raw_transaction = transaction.encode() actual = HexBytes(raw_transaction).hex() assert actual == expected
def revert_contract(web3, RevertContract, address_conversion_func): return deploy(web3, RevertContract, address_conversion_func) @pytest.fixture() def call_transaction(): return { 'data': '0x61bc221a', 'to': '0xc305c901078781C232A2a521C2aF7980f8385ee9' } @pytest.fixture(params=[ '0x0406040604060406040604060406040604060406040604060406040604060406', '0406040604060406040604060406040604060406040604060406040604060406', HexBytes('0406040604060406040604060406040604060406040604060406040604060406'), ]) def bytes32_contract(web3, Bytes32Contract, request, address_conversion_func): if is_text(request.param) and request.param[:2] != '0x': with pytest.warns(DeprecationWarning): return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param]) else: return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param]) @pytest.fixture() def undeployed_math_contract(web3, MathContract, address_conversion_func): empty_address = address_conversion_func("0x000000000000000000000000000000000000dEaD") _undeployed_math_contract = MathContract(address=empty_address) return _undeployed_math_contract