def update_version(self) -> Optional[bool]: """ Update Safe Master Copy and Fallback handler to the last version :return: """ if self.is_version_updated(): raise SafeAlreadyUpdatedException() multisend = MultiSend(LAST_MULTISEND_CONTRACT, self.ethereum_client) tx_params = {'from': self.address, 'gas': 0, 'gasPrice': 0} multisend_txs = [ MultiSendTx(MultiSendOperation.CALL, self.address, 0, data) for data in (self.safe_contract.functions.changeMasterCopy( LAST_SAFE_CONTRACT).buildTransaction(tx_params)['data'], self.safe_contract.functions.setFallbackHandler( LAST_DEFAULT_CALLBACK_HANDLER).buildTransaction( tx_params)['data']) ] multisend_data = multisend.build_tx_data(multisend_txs) if self.execute_safe_transaction( multisend.address, 0, multisend_data, operation=SafeOperation.DELEGATE_CALL): self.safe_cli_info.master_copy = LAST_SAFE_CONTRACT self.safe_cli_info.fallback_handler = LAST_DEFAULT_CALLBACK_HANDLER self.safe_cli_info.version = self.safe.retrieve_version()
def batch_txs(self, safe_nonce: int, safe_tx_hashes: Sequence[bytes]) -> bool: """ Submit signatures to the tx service. It's recommended to be on Safe v1.3.0 to prevent issues with `safeTxGas` and gas estimation. :return: """ if not self.ethereum_client.is_contract( LAST_MULTISEND_CALL_ONLY_CONTRACT): print_formatted_text( HTML( f"<ansired>Multisend call only contract {LAST_MULTISEND_CALL_ONLY_CONTRACT} " f"is not deployed on this network and it's required for batching txs</ansired>" )) multisend_txs = [] for safe_tx_hash in safe_tx_hashes: safe_tx, _ = self.safe_tx_service.get_safe_transaction( safe_tx_hash) # Check if call is already a Multisend call inner_txs = MultiSend.from_transaction_data(safe_tx.data) if inner_txs: multisend_txs.extend(inner_txs) else: multisend_txs.append( MultiSendTx(MultiSendOperation.CALL, safe_tx.to, safe_tx.value, safe_tx.data)) if len(multisend_txs) > 1: multisend = MultiSend(LAST_MULTISEND_CALL_ONLY_CONTRACT, self.ethereum_client) safe_tx = SafeTx( self.ethereum_client, self.address, LAST_MULTISEND_CALL_ONLY_CONTRACT, 0, multisend.build_tx_data(multisend_txs), SafeOperation.DELEGATE_CALL.value, 0, 0, 0, None, None, safe_nonce=safe_nonce, ) else: safe_tx.safe_tx_gas = 0 safe_tx.base_gas = 0 safe_tx.gas_price = 0 safe_tx.signatures = b"" safe_tx.safe_nonce = safe_nonce # Resend single transaction safe_tx = self.sign_transaction(safe_tx) if not safe_tx.signatures: print_formatted_text( HTML("<ansired>At least one owner must be loaded</ansired>")) return False else: return self.post_transaction_to_tx_service(safe_tx)
def decode_multisend_data( self, data: Union[bytes, str]) -> List[MultisendDecoded]: """ Decodes Multisend raw data to Multisend dictionary :param data: :return: """ try: multisend_txs = MultiSend.from_transaction_data(data) return [{ "operation": multisend_tx.operation.value, "to": multisend_tx.to, "value": str(multisend_tx.value), "data": multisend_tx.data.hex() if multisend_tx.data else None, "data_decoded": self.get_data_decoded(multisend_tx.data, address=multisend_tx.to), } for multisend_tx in multisend_txs] except ValueError: logger.warning( "Problem decoding multisend transaction with data=%s", HexBytes(data).hex(), exc_info=True, )
def setUpTestData(cls): super().setUpTestData() for key, value in contract_addresses.items(): if callable(value): contract_addresses[key] = value(cls.ethereum_client, cls.ethereum_test_account).contract_address settings.SAFE_CONTRACT_ADDRESS = contract_addresses['safe'] settings.SAFE_MULTISEND_ADDRESS = contract_addresses['multi_send'] settings.SAFE_V1_0_0_CONTRACT_ADDRESS = contract_addresses['safe_V1_0_0'] settings.SAFE_V0_0_1_CONTRACT_ADDRESS = contract_addresses['safe_V0_0_1'] settings.SAFE_PROXY_FACTORY_ADDRESS = contract_addresses['proxy_factory'] settings.SAFE_PROXY_FACTORY_V1_0_0_ADDRESS = contract_addresses['proxy_factory_V1_0_0'] settings.SAFE_VALID_CONTRACT_ADDRESSES = {settings.SAFE_CONTRACT_ADDRESS, settings.SAFE_V1_0_0_CONTRACT_ADDRESS, settings.SAFE_V0_0_1_CONTRACT_ADDRESS, } cls.safe_contract_address = contract_addresses['safe'] cls.safe_contract = get_safe_contract(cls.w3, cls.safe_contract_address) cls.safe_contract_V1_0_0_address = contract_addresses['safe_V1_0_0'] cls.safe_contract_V1_0_0 = get_safe_V1_0_0_contract(cls.w3, cls.safe_contract_V1_0_0_address) cls.safe_contract_V0_0_1_address = contract_addresses['safe_V0_0_1'] cls.safe_contract_V0_0_1 = get_safe_V1_0_0_contract(cls.w3, cls.safe_contract_V0_0_1_address) cls.proxy_factory_contract_address = contract_addresses['proxy_factory'] cls.proxy_factory_contract = get_proxy_factory_contract(cls.w3, cls.proxy_factory_contract_address) cls.proxy_factory = ProxyFactory(cls.proxy_factory_contract_address, cls.ethereum_client) cls.multi_send_contract = get_multi_send_contract(cls.w3, contract_addresses['multi_send']) cls.multi_send = MultiSend(cls.multi_send_contract.address, cls.ethereum_client)
def setUpClass(cls): super().setUpClass() for key, value in _contract_addresses.items(): if callable(value): _contract_addresses[key] = value( cls.ethereum_client, cls.ethereum_test_account).contract_address settings.SAFE_DEFAULT_CALLBACK_HANDLER = _contract_addresses[ "compatibility_fallback_handler"] settings.SAFE_MULTISEND_ADDRESS = _contract_addresses["multi_send"] settings.SAFE_CONTRACT_ADDRESS = _contract_addresses["safe_v1_3_0"] settings.SAFE_V1_1_1_CONTRACT_ADDRESS = _contract_addresses[ "safe_V1_1_1"] settings.SAFE_V1_0_0_CONTRACT_ADDRESS = _contract_addresses[ "safe_V1_0_0"] settings.SAFE_V0_0_1_CONTRACT_ADDRESS = _contract_addresses[ "safe_V0_0_1"] settings.SAFE_PROXY_FACTORY_ADDRESS = _contract_addresses[ "proxy_factory"] settings.SAFE_PROXY_FACTORY_V1_0_0_ADDRESS = _contract_addresses[ "proxy_factory_V1_0_0"] settings.SAFE_VALID_CONTRACT_ADDRESSES = { settings.SAFE_CONTRACT_ADDRESS, settings.SAFE_V1_1_1_CONTRACT_ADDRESS, settings.SAFE_V1_0_0_CONTRACT_ADDRESS, settings.SAFE_V0_0_1_CONTRACT_ADDRESS, } cls.compatibility_fallback_handler = ( get_compatibility_fallback_handler_V1_3_0_contract( cls.w3, _contract_addresses["compatibility_fallback_handler"])) cls.safe_contract_address = _contract_addresses["safe_v1_3_0"] cls.safe_contract = get_safe_V1_3_0_contract(cls.w3, cls.safe_contract_address) cls.safe_contract_V1_1_1_address = _contract_addresses["safe_V1_1_1"] cls.safe_contract_V1_1_1 = get_safe_V1_1_1_contract( cls.w3, cls.safe_contract_V1_1_1_address) cls.safe_contract_V1_0_0_address = _contract_addresses["safe_V1_0_0"] cls.safe_contract_V1_0_0 = get_safe_V1_0_0_contract( cls.w3, cls.safe_contract_V1_0_0_address) cls.safe_contract_V0_0_1_address = _contract_addresses["safe_V0_0_1"] cls.safe_contract_V0_0_1 = get_safe_V1_0_0_contract( cls.w3, cls.safe_contract_V0_0_1_address) cls.proxy_factory_contract_address = _contract_addresses[ "proxy_factory"] cls.proxy_factory_contract = get_proxy_factory_contract( cls.w3, cls.proxy_factory_contract_address) cls.proxy_factory = ProxyFactory(cls.proxy_factory_contract_address, cls.ethereum_client) cls.multi_send_contract = get_multi_send_contract( cls.w3, _contract_addresses["multi_send"]) cls.multi_send = MultiSend(cls.multi_send_contract.address, cls.ethereum_client)
def multisend_from_receipts(self, receipts: List[TransactionReceipt] = None, safe_nonce: int = None) -> SafeTx: """ Convert multiple Brownie transaction receipts (or history) to a multisend Safe transaction. """ if receipts is None: receipts = history.from_sender(self.address) if safe_nonce is None: safe_nonce = self.pending_nonce() txs = [MultiSendTx(MultiSendOperation.CALL, tx.receiver, tx.value, tx.input) for tx in receipts] data = MultiSend(self.multisend, self.ethereum_client).build_tx_data(txs) return self.build_multisig_tx(self.multisend, 0, data, SafeOperation.DELEGATE_CALL.value, safe_nonce=safe_nonce)
def update_version(self) -> Optional[bool]: """ Update Safe Master Copy and Fallback handler to the last version :return: """ if self.is_version_updated(): raise SafeAlreadyUpdatedException() addresses = (LAST_SAFE_CONTRACT, LAST_DEFAULT_CALLBACK_HANDLER) if not all( self.ethereum_client.is_contract(contract) for contract in addresses): raise UpdateAddressesNotValid("Not valid addresses to update Safe", *addresses) multisend = MultiSend(LAST_MULTISEND_CONTRACT, self.ethereum_client) tx_params = {"from": self.address, "gas": 0, "gasPrice": 0} multisend_txs = [ MultiSendTx(MultiSendOperation.CALL, self.address, 0, data) for data in ( self.safe_contract_1_1_0.functions.changeMasterCopy( LAST_SAFE_CONTRACT).buildTransaction(tx_params)["data"], self.safe_contract_1_1_0.functions.setFallbackHandler( LAST_DEFAULT_CALLBACK_HANDLER).buildTransaction(tx_params) ["data"], ) ] multisend_data = multisend.build_tx_data(multisend_txs) if self.prepare_and_execute_safe_transaction( multisend.address, 0, multisend_data, operation=SafeOperation.DELEGATE_CALL): self.safe_cli_info.master_copy = LAST_SAFE_CONTRACT self.safe_cli_info.fallback_handler = LAST_DEFAULT_CALLBACK_HANDLER self.safe_cli_info.version = self.safe.retrieve_version()
def _get_data_decoded_for_multisend(self, data: Union[bytes, str]) -> List[Dict[str, Any]]: """ Return a multisend :param data: :return: """ try: multisend_txs = MultiSend.from_transaction_data(data) return [{'operation': multisend_tx.operation.value, 'to': multisend_tx.to, 'value': str(multisend_tx.value), 'data': multisend_tx.data.hex() if multisend_tx.data else None, 'data_decoded': self.get_data_decoded(multisend_tx.data), } for multisend_tx in multisend_txs] except ValueError: logger.warning('Problem decoding multisend transaction with data=%s', HexBytes(data).hex(), exc_info=True)