Example #1
0
    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)
Example #5
0
    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)
Example #6
0
 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)
Example #7
0
    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)