Esempio n. 1
0
 def post_transaction_to_relay_service(self, safe_tx: SafeTx) -> bool:
     safe_tx.gas_token = self.gas_token
     estimation = self.safe_relay_service.get_estimation(
         self.address, safe_tx)
     safe_tx.base_gas = estimation["baseGas"]
     safe_tx.safe_tx_gas = estimation["safeTxGas"]
     safe_tx.gas_price = estimation["gasPrice"]
     last_used_nonce: Optional[int] = estimation["lastUsedNonce"]
     safe_tx.safe_nonce = 0 if last_used_nonce is None else last_used_nonce + 1
     safe_tx.refund_receiver = estimation["refundReceiver"] or NULL_ADDRESS
     safe_tx.signatures = b""  # Sign transaction again
     self.sign_transaction(safe_tx)
     if yes_or_no_question("Do you want to execute tx " + str(safe_tx)):
         try:
             call_result = safe_tx.call(self.default_sender.address)
             print_formatted_text(
                 HTML(f"Result: <ansigreen>{call_result}</ansigreen>"))
             transaction_data = self.safe_relay_service.send_transaction(
                 self.address, safe_tx)
             tx_hash = transaction_data["txHash"]
             print_formatted_text(
                 HTML(
                     f"<ansigreen>Gnosis Safe Relay has queued transaction with "
                     f"transaction-hash <b>{tx_hash}</b></ansigreen>"))
             return True
         except InvalidInternalTx as invalid_internal_tx:
             print_formatted_text(
                 HTML(
                     f"Result: <ansired>InvalidTx - {invalid_internal_tx}</ansired>"
                 ))
     return False
    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)
Esempio n. 3
0
    def sign_transaction(self, safe_tx: SafeTx) -> NoReturn:
        owners = self.safe_cli_info.owners
        threshold = self.safe_cli_info.threshold
        selected_accounts: List[Account] = [
        ]  # Some accounts that are not an owner can be loaded
        for account in self.accounts:
            if account.address in owners:
                selected_accounts.append(account)
                threshold -= 1
                if threshold == 0:
                    break

        if threshold > 0:
            raise NotEnoughSignatures(threshold)

        for selected_account in selected_accounts:
            safe_tx.sign(selected_account.key)
        """
Esempio n. 4
0
    def sign_transaction(self, safe_tx: SafeTx) -> SafeTx:
        permitted_signers = self.get_permitted_signers()
        threshold = self.safe_cli_info.threshold
        selected_accounts: List[Account] = [
        ]  # Some accounts that are not an owner can be loaded
        for account in self.accounts:
            if account.address in permitted_signers:
                selected_accounts.append(account)
                threshold -= 1
                if threshold == 0:
                    break

        if self.require_all_signatures and threshold > 0:
            raise NotEnoughSignatures(threshold)

        for selected_account in selected_accounts:
            safe_tx.sign(selected_account.key)

        return safe_tx
    def send_safe_tx(self, safe_address: str, safe_version: str, accounts: List[Account],
                     payment_token: Optional[str] = None, wait_for_receipt: bool = True) -> bytes:
        safe_balance = self.w3.eth.getBalance(safe_address)
        tx = {
            'to': self.main_account.address,
            'value': safe_balance,
            'data': None,
            'operation': 0,  # CALL
            'gasToken': payment_token,
        }

        if payment_token:
            tx['gasToken'] = payment_token

        # We used payment * 2 to fund the safe, now we return ether to the main account
        r = requests.post(self.get_estimate_url(safe_address), json=tx)
        assert r.ok, "Estimate not working %s" % r.content
        self.stdout.write(self.style.SUCCESS('Estimation=%s for tx=%s' % (r.json(), tx)))
        # estimate_gas = r.json()['safeTxGas'] + r.json()['dataGas'] + r.json()['operationalGas']
        # fees = r.json()['gasPrice'] * estimate_gas

        if payment_token:
            # We can transfer the full amount as we are paying fees with a token
            tx['value'] = safe_balance
        else:
            estimate_gas = r.json()['safeTxGas'] + r.json()['dataGas'] + r.json()['operationalGas']
            fees = r.json()['gasPrice'] * estimate_gas
            tx['value'] = safe_balance - fees

        tx['dataGas'] = r.json()['dataGas'] + r.json()['operationalGas']
        tx['gasPrice'] = r.json()['gasPrice']
        tx['safeTxGas'] = r.json()['safeTxGas']
        tx['nonce'] = 0 if r.json()['lastUsedNonce'] is None else r.json()['lastUsedNonce'] + 1
        tx['refundReceiver'] = None

        # Sign the tx
        safe_tx_hash = SafeTx(None, safe_address, tx['to'], tx['value'], tx['data'],
                              tx['operation'], tx['safeTxGas'], tx['dataGas'],
                              tx['gasPrice'], tx['gasToken'], tx['refundReceiver'],
                              safe_nonce=tx['nonce'], safe_version=safe_version).safe_tx_hash

        signatures = [account.signHash(safe_tx_hash) for account in accounts[:2]]
        curated_signatures = [{'r': signature['r'], 's': signature['s'], 'v': signature['v']}
                              for signature in signatures]
        tx['signatures'] = curated_signatures

        self.stdout.write(self.style.SUCCESS('Sending multisig tx to return some funds to the main owner %s' % tx))
        r = requests.post(self.get_tx_url(safe_address), json=tx)
        assert r.ok, "Error sending tx %s" % r.content

        multisig_tx_hash = r.json()['txHash']
        self.stdout.write(self.style.SUCCESS('Tx with tx-hash=%s was successful' % multisig_tx_hash))
        if wait_for_receipt:
            self.w3.eth.waitForTransactionReceipt(multisig_tx_hash, timeout=500)
        return multisig_tx_hash
Esempio n. 6
0
    def test_decode_execute_transaction(self):
        owners = [Account.create() for _ in range(2)]
        owner_addresses = [owner.address for owner in owners]
        threshold = 1
        safe_creation = self.deploy_test_safe(owners=owner_addresses, threshold=threshold,
                                              initial_funding_wei=self.w3.toWei(0.1, 'ether'))
        safe_address = safe_creation.safe_address
        to = Account().create().address
        value = self.w3.toWei(0.01, 'ether')
        safe_tx_gas = 200000
        data_gas = 100000

        safe_tx = SafeTx(self.ethereum_client, safe_address, to, value, b'', 0, safe_tx_gas, data_gas, self.gas_price,
                         None, None, safe_nonce=0)

        safe_tx.sign(owners[0].privateKey)

        self.assertEqual(safe_tx.call(tx_sender_address=self.ethereum_test_account.address), 1)
        tx_hash, _ = safe_tx.execute(tx_sender_private_key=self.ethereum_test_account.privateKey)
        self.ethereum_client.get_transaction_receipt(tx_hash, timeout=60)
        self.assertEqual(self.ethereum_client.get_balance(to), value)

        tx_decoder = TxDecoder()
        function_name, arguments = tx_decoder.decode_transaction(safe_tx.tx['data'])
        self.assertEqual(function_name, 'execTransaction')
        self.assertIn('baseGas', arguments)
Esempio n. 7
0
 def execute_safe_transaction(self, safe_tx: SafeTx):
     try:
         call_result = safe_tx.call(self.default_sender.address)
         print_formatted_text(
             HTML(f"Result: <ansigreen>{call_result}</ansigreen>"))
         if yes_or_no_question("Do you want to execute tx " + str(safe_tx)):
             tx_hash, tx = safe_tx.execute(self.default_sender.key,
                                           eip1559_speed=TxSpeed.NORMAL)
             self.executed_transactions.append(tx_hash.hex())
             print_formatted_text(
                 HTML(
                     f"<ansigreen>Sent tx with tx-hash {tx_hash.hex()} "
                     f"and safe-nonce {safe_tx.safe_nonce}, waiting for receipt</ansigreen>"
                 ))
             tx_receipt = self.ethereum_client.get_transaction_receipt(
                 tx_hash, timeout=120)
             if tx_receipt:
                 fees = self.ethereum_client.w3.fromWei(
                     tx_receipt["gasUsed"] * tx_receipt.get(
                         "effectiveGasPrice", tx.get("gasPrice", 0)),
                     "ether",
                 )
                 print_formatted_text(
                     HTML(
                         f"<ansigreen>Tx was executed on block-number={tx_receipt['blockNumber']}, fees "
                         f"deducted={fees}</ansigreen>"))
                 self.safe_cli_info.nonce += 1
                 return True
             else:
                 print_formatted_text(
                     HTML(
                         f"<ansired>Tx with tx-hash {tx_hash.hex()} still not mined</ansired>"
                     ))
     except InvalidInternalTx as invalid_internal_tx:
         print_formatted_text(
             HTML(
                 f"Result: <ansired>InvalidTx - {invalid_internal_tx}</ansired>"
             ))
     return False
Esempio n. 8
0
    def test_decode_old_execute_transaction(self):
        safe_address = Account.create().address
        to = Account().create().address
        value = self.w3.toWei(0.01, 'ether')
        safe_tx_gas = 200000
        data_gas = 100000
        safe_tx = SafeTx(self.ethereum_client, safe_address, to, value, b'', 0, safe_tx_gas, data_gas, self.gas_price,
                         None, None, safe_nonce=0, safe_version='0.0.1')

        tx_decoder = TxDecoder()
        data = safe_tx.w3_tx.buildTransaction()['data']
        function_name, arguments = tx_decoder.decode_transaction(data)
        self.assertEqual(function_name, 'execTransaction')
        # self.assertIn('dataGas', arguments)
        self.assertIn('baseGas', arguments)  # Signature of the tx is the same
Esempio n. 9
0
 def get_safe_tx(self, ethereum_client: EthereumClient) -> SafeTx:
     return SafeTx(
         ethereum_client,
         self.safe_id,
         self.to,
         self.value,
         self.data.tobytes() if self.data else b"",
         self.operation,
         self.safe_tx_gas,
         self.data_gas,
         self.gas_price,
         self.gas_token,
         self.refund_receiver,
         signatures=self.signatures.tobytes() if self.signatures else b"",
         safe_nonce=self.nonce,
     )
Esempio n. 10
0
 def get_safe_transaction(
     self, safe_tx_hash: bytes
 ) -> Tuple[SafeTx, Optional[HexBytes]]:
     """
     :param safe_tx_hash:
     :return: SafeTx and `tx-hash` if transaction was executed
     """
     safe_tx_hash = HexBytes(safe_tx_hash).hex()
     response = self._get_request(f"/api/v1/multisig-transactions/{safe_tx_hash}/")
     if not response.ok:
         raise BaseAPIException(
             f"Cannot get transaction with safe-tx-hash={safe_tx_hash}: {response.content}"
         )
     else:
         result = response.json()
         # TODO return tx-hash if executed
         signatures = self.parse_signatures(result)
         return (
             SafeTx(
                 self.ethereum_client,
                 result["safe"],
                 result["to"],
                 int(result["value"]),
                 HexBytes(result["data"]) if result["data"] else b"",
                 int(result["operation"]),
                 int(result["safeTxGas"]),
                 int(result["baseGas"]),
                 int(result["gasPrice"]),
                 result["gasToken"],
                 result["refundReceiver"],
                 signatures=signatures if signatures else b"",
                 safe_nonce=int(result["nonce"]),
             ),
             HexBytes(result["transactionHash"])
             if result["transactionHash"]
             else None,
         )
Esempio n. 11
0
    def test_safe_multisig_tx_post_gas_token(self):
        # Create Safe ------------------------------------------------
        w3 = self.ethereum_client.w3
        safe_balance = w3.toWei(0.01, 'ether')
        owner_account = self.create_account()
        owner = owner_account.address
        threshold = 1

        safe_creation = self.deploy_test_safe(owners=[owner],
                                              threshold=threshold,
                                              initial_funding_wei=safe_balance)
        my_safe_address = safe_creation.safe_address
        self.assertEqual(self.w3.eth.getBalance(my_safe_address), safe_balance)
        SafeContractFactory(address=my_safe_address)

        # Get tokens for the safe
        safe_token_balance = int(1e18)
        erc20_contract = self.deploy_example_erc20(safe_token_balance,
                                                   my_safe_address)

        # Safe prepared --------------------------------------------
        to = Account.create().address
        value = safe_balance
        tx_data = None
        operation = SafeOperation.CALL.value
        refund_receiver = None
        nonce = 0
        gas_token = erc20_contract.address

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
            "gasToken": gas_token
        }

        # Get estimation for gas. Token does not exist
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        self.assertEqual('InvalidGasToken: %s' % gas_token,
                         response.json()['exception'])

        # Create token
        token_model = TokenFactory(address=gas_token)
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        estimation_json = response.json()

        safe_tx_gas = estimation_json['safeTxGas'] + estimation_json[
            'operationalGas']
        data_gas = estimation_json['dataGas']
        gas_price = estimation_json['gasPrice']
        gas_token = estimation_json['gasToken']

        multisig_tx_hash = SafeTx(None,
                                  my_safe_address,
                                  to,
                                  value,
                                  tx_data,
                                  operation,
                                  safe_tx_gas,
                                  data_gas,
                                  gas_price,
                                  gas_token,
                                  refund_receiver,
                                  safe_nonce=nonce).safe_tx_hash

        signatures = [
            w3.eth.account.signHash(multisig_tx_hash, private_key)
            for private_key in [owner_account.key]
        ]
        signatures_json = [{
            'v': s['v'],
            'r': s['r'],
            's': s['s']
        } for s in signatures]

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
            "safe_tx_gas": safe_tx_gas,
            "data_gas": data_gas,
            "gas_price": gas_price,
            "gas_token": gas_token,
            "nonce": nonce,
            "signatures": signatures_json
        }

        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        tx_hash = response.json()['transactionHash'][2:]  # Remove leading 0x
        safe_multisig_tx = SafeMultisigTx.objects.get(
            ethereum_tx__tx_hash=tx_hash)
        self.assertEqual(safe_multisig_tx.to, to)
        self.assertEqual(safe_multisig_tx.value, value)
        self.assertEqual(safe_multisig_tx.data, tx_data)
        self.assertEqual(safe_multisig_tx.operation, operation)
        self.assertEqual(safe_multisig_tx.safe_tx_gas, safe_tx_gas)
        self.assertEqual(safe_multisig_tx.data_gas, data_gas)
        self.assertEqual(safe_multisig_tx.gas_price, gas_price)
        self.assertEqual(safe_multisig_tx.gas_token, gas_token)
        self.assertEqual(safe_multisig_tx.nonce, nonce)
Esempio n. 12
0
    def test_safe_multisig_tx_post(self):
        # Create Safe ------------------------------------------------
        w3 = self.ethereum_client.w3
        safe_balance = w3.toWei(0.01, 'ether')
        accounts = [self.create_account(), self.create_account()]
        # Signatures must be sorted!
        accounts.sort(key=lambda account: account.address.lower())
        owners = [x.address for x in accounts]
        threshold = len(accounts)

        safe_creation = self.deploy_test_safe(owners=owners,
                                              threshold=threshold,
                                              initial_funding_wei=safe_balance)
        my_safe_address = safe_creation.safe_address
        SafeContractFactory(address=my_safe_address)

        self.assertEqual(self.ethereum_client.get_balance(my_safe_address),
                         safe_balance)

        # Safe prepared --------------------------------------------
        to = Account.create().address
        value = safe_balance // 2
        tx_data = None
        operation = SafeOperation.CALL.value

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
        }

        # Get estimation for gas
        response = self.client.post(reverse('v1:safe-multisig-tx-estimate',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        estimation_json = response.json()

        safe_tx_gas = estimation_json['safeTxGas'] + estimation_json[
            'operationalGas']
        data_gas = estimation_json['dataGas']
        gas_price = estimation_json['gasPrice']
        gas_token = estimation_json['gasToken']
        refund_receiver = None
        nonce = 0

        multisig_tx_hash = SafeTx(None,
                                  my_safe_address,
                                  to,
                                  value,
                                  tx_data,
                                  operation,
                                  safe_tx_gas,
                                  data_gas,
                                  gas_price,
                                  gas_token,
                                  refund_receiver,
                                  safe_nonce=nonce).safe_tx_hash

        signatures = [
            account.signHash(multisig_tx_hash) for account in accounts
        ]
        signatures_json = [{
            'v': s['v'],
            'r': s['r'],
            's': s['s']
        } for s in signatures]

        data = {
            "to": to,
            "value": value,
            "data": tx_data,
            "operation": operation,
            "safe_tx_gas": safe_tx_gas,
            "data_gas": data_gas,
            "gas_price": gas_price,
            "gas_token": gas_token,
            "nonce": nonce,
            "signatures": signatures_json
        }

        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        tx_hash = response.json()['transactionHash'][2:]  # Remove leading 0x
        safe_multisig_tx = SafeMultisigTx.objects.get(
            ethereum_tx__tx_hash=tx_hash)
        self.assertEqual(safe_multisig_tx.to, to)
        self.assertEqual(safe_multisig_tx.value, value)
        self.assertEqual(safe_multisig_tx.data, tx_data)
        self.assertEqual(safe_multisig_tx.operation, operation)
        self.assertEqual(safe_multisig_tx.safe_tx_gas, safe_tx_gas)
        self.assertEqual(safe_multisig_tx.data_gas, data_gas)
        self.assertEqual(safe_multisig_tx.gas_price, gas_price)
        self.assertEqual(safe_multisig_tx.gas_token, None)
        self.assertEqual(safe_multisig_tx.nonce, nonce)
        signature_pairs = [(s['v'], s['r'], s['s']) for s in signatures]
        signatures_packed = signatures_to_bytes(signature_pairs)
        self.assertEqual(bytes(safe_multisig_tx.signatures), signatures_packed)

        # Send the same tx again
        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        self.assertTrue('exists' in response.data['exception'])

        # Send with a Safe not created via the service
        safe_creation = self.deploy_test_safe(owners=owners,
                                              threshold=threshold,
                                              initial_funding_wei=safe_balance)
        my_safe_address = safe_creation.safe_address
        multisig_tx_hash = SafeTx(None,
                                  my_safe_address,
                                  to,
                                  value,
                                  tx_data,
                                  operation,
                                  safe_tx_gas,
                                  data_gas,
                                  gas_price,
                                  gas_token,
                                  refund_receiver,
                                  safe_nonce=nonce).safe_tx_hash
        signatures = [
            account.signHash(multisig_tx_hash) for account in accounts
        ]
        signatures_json = [{
            'v': s['v'],
            'r': s['r'],
            's': s['s']
        } for s in signatures]
        data['signatures'] = signatures_json

        with self.assertRaises(SafeContract.DoesNotExist):
            SafeContract.objects.get(address=my_safe_address)

        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue(
            SafeContract.objects.filter(address=my_safe_address).exists())
        self.assertEqual(
            SafeMultisigTx.objects.filter(safe_id=my_safe_address).count(), 1)

        # Send the same tx again
        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        self.assertTrue('exists' in response.data['exception'])
        self.assertEqual(
            SafeMultisigTx.objects.filter(safe_id=my_safe_address).count(), 1)

        # Send tx with not existing Safe
        my_safe_address = Account.create().address
        response = self.client.post(reverse('v1:safe-multisig-txs',
                                            args=(my_safe_address, )),
                                    data=data,
                                    format='json')
        self.assertEqual(response.status_code,
                         status.HTTP_422_UNPROCESSABLE_ENTITY)
        self.assertTrue('InvalidProxyContract' in response.data['exception'])
    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
        """
        function_name = internal_tx_decoded.function_name
        arguments = internal_tx_decoded.arguments
        internal_tx = internal_tx_decoded.internal_tx
        contract_address = internal_tx._from
        master_copy = internal_tx.to
        processed_successfully = True
        if function_name == 'setup' and contract_address != NULL_ADDRESS:
            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.ethereum_tx.block_id
                    safe_contract.save(update_fields=['ethereum_tx', 'erc20_block_number'])
            except SafeContract.DoesNotExist:
                SafeContract.objects.create(address=contract_address,
                                            ethereum_tx=internal_tx.ethereum_tx,
                                            erc20_block_number=max(internal_tx.ethereum_tx.block_id - 5760, 0))
                logger.info('Found new Safe=%s', contract_address)

            SafeStatus.objects.create(internal_tx=internal_tx,
                                      address=contract_address, owners=owners, threshold=threshold,
                                      nonce=nonce, master_copy=master_copy, fallback_handler=fallback_handler)
        elif function_name in ('addOwnerWithThreshold', 'removeOwner', 'removeOwnerWithThreshold'):
            logger.debug('Processing owner/threshold modification')
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.threshold = arguments['_threshold']
            owner = arguments['owner']
            try:
                if function_name == 'addOwnerWithThreshold':
                    safe_status.owners.append(owner)
                else:  # removeOwner, removeOwnerWithThreshold
                    safe_status.owners.remove(owner)
            except ValueError:
                logger.error('Error processing trace=%s for contract=%s with tx-hash=%s',
                             internal_tx.trace_address, contract_address,
                             internal_tx.ethereum_tx_id)
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'swapOwner':
            logger.debug('Processing owner swap')
            old_owner = arguments['oldOwner']
            new_owner = arguments['newOwner']
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.owners.remove(old_owner)
            safe_status.owners.append(new_owner)
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'changeThreshold':
            logger.debug('Processing threshold change')
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.threshold = arguments['_threshold']
            self.store_new_safe_status(safe_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
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.master_copy = arguments['_masterCopy']
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'setFallbackHandler':
            logger.debug('Setting FallbackHandler')
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.fallback_handler = arguments['handler']
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'enableModule':
            logger.debug('Enabling Module')
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.enabled_modules.append(arguments['module'])
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'disableModule':
            logger.debug('Disabling Module')
            safe_status = self.get_last_safe_status_for_address(contract_address)
            safe_status.enabled_modules.remove(arguments['module'])
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'execTransactionFromModule':
            logger.debug('Executing Tx from Module')
            ethereum_tx = internal_tx.ethereum_tx
            module_internal_tx = internal_tx.get_previous_module_trace()
            module_address = module_internal_tx.to if module_internal_tx else NULL_ADDRESS
            module_data = HexBytes(arguments['data'])
            failed = self.is_module_failed(ethereum_tx, module_address)
            ModuleTransaction.objects.get_or_create(
                internal_tx=internal_tx,
                defaults={
                    'created': internal_tx.ethereum_tx.block.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
            # TODO Check previous trace is not a delegate call
            owner = internal_tx.get_previous_trace()._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={
                                                                 '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')
            safe_status = self.get_last_safe_status_for_address(contract_address)
            nonce = safe_status.nonce
            if 'baseGas' in arguments:  # `dataGas` was renamed to `baseGas` in v1.0.0
                base_gas = arguments['baseGas']
                safe_version = '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)
            safe_tx_hash = safe_tx.safe_tx_hash

            ethereum_tx = internal_tx.ethereum_tx

            # Remove existing transaction with same nonce in case of bad indexing (one of the master copies can be
            # outdated and a tx with a wrong nonce could be indexed)
            # MultisigTransaction.objects.filter(
            #    ethereum_tx=ethereum_tx,
            #    nonce=safe_tx.safe_nonce,
            #    safe=contract_address
            # ).exclude(
            #     safe_tx_hash=safe_tx_hash
            # ).delete()

            # Remove old txs not used
            # MultisigTransaction.objects.filter(
            #     ethereum_tx=None,
            #     nonce__lt=safe_tx.safe_nonce,
            #     safe=contract_address
            # ).delete()

            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.ethereum_tx.block.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,
                })
            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={
                        '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_status.nonce = nonce + 1
            self.store_new_safe_status(safe_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.debug('End processing')
        return processed_successfully
Esempio n. 14
0
    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
        """
        function_name = internal_tx_decoded.function_name
        arguments = internal_tx_decoded.arguments
        internal_tx = internal_tx_decoded.internal_tx
        contract_address = internal_tx._from
        master_copy = internal_tx.to
        processed_successfully = True
        if function_name == 'setup' and contract_address != NULL_ADDRESS:
            owners = arguments['_owners']
            threshold = arguments['_threshold']
            _, created = SafeContract.objects.get_or_create(address=contract_address,
                                                            defaults={
                                                                'ethereum_tx': internal_tx.ethereum_tx,
                                                                'erc20_block_number': internal_tx.ethereum_tx.block_id,
                                                            })
            if created:
                logger.info('Found new Safe=%s', contract_address)
            SafeStatus.objects.create(internal_tx=internal_tx,
                                      address=contract_address, owners=owners, threshold=threshold,
                                      nonce=0, master_copy=master_copy)
        elif function_name in ('addOwnerWithThreshold', 'removeOwner', 'removeOwnerWithThreshold'):
            safe_status = SafeStatus.objects.last_for_address(contract_address)
            safe_status.threshold = arguments['_threshold']
            owner = arguments['owner']
            try:
                if function_name == 'addOwnerWithThreshold':
                    safe_status.owners.append(owner)
                else:  # removeOwner, removeOwnerWithThreshold
                    safe_status.owners.remove(owner)
            except ValueError:
                logger.error('Error processing trace=%s for contract=%s with tx-hash=%s',
                             internal_tx.trace_address, contract_address,
                             internal_tx.ethereum_tx_id)
            safe_status.store_new(internal_tx)
        elif function_name == 'swapOwner':
            old_owner = arguments['oldOwner']
            new_owner = arguments['newOwner']
            safe_status = SafeStatus.objects.last_for_address(contract_address)
            safe_status.owners.remove(old_owner)
            safe_status.owners.append(new_owner)
            safe_status.store_new(internal_tx)
        elif function_name == 'changeThreshold':
            safe_status = SafeStatus.objects.last_for_address(contract_address)
            safe_status.threshold = arguments['_threshold']
            safe_status.store_new(internal_tx)
        elif function_name == 'changeMasterCopy':
            # TODO Ban address if it doesn't have a valid master copy
            safe_status = SafeStatus.objects.last_for_address(contract_address)
            safe_status.master_copy = arguments['_masterCopy']
            safe_status.store_new(internal_tx)
        elif function_name == 'execTransaction':
            safe_status = SafeStatus.objects.last_for_address(contract_address)
            nonce = safe_status.nonce
            if 'baseGas' in arguments:  # `dataGas` was renamed to `baseGas` in v1.0.0
                base_gas = arguments['baseGas']
                safe_version = '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)
            safe_tx_hash = safe_tx.safe_tx_hash

            ethereum_tx = internal_tx.ethereum_tx

            # Remove existing transaction with same nonce in case of bad indexing (one of the master copies can be
            # outdated and a tx with a wrong nonce could be indexed)
            MultisigTransaction.objects.filter(
                ethereum_tx=ethereum_tx,
                nonce=safe_tx.safe_nonce,
                safe=contract_address
            ).exclude(
                safe_tx_hash=safe_tx_hash
            ).delete()

            # Remove old txs not used
            MultisigTransaction.objects.filter(
                ethereum_tx=None,
                nonce__lt=safe_tx.safe_nonce,
                safe=contract_address
            )

            multisig_tx, created = MultisigTransaction.objects.get_or_create(
                safe_tx_hash=safe_tx_hash,
                defaults={
                    '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,
                })
            if not created and not multisig_tx.ethereum_tx:
                multisig_tx.ethereum_tx = ethereum_tx
                multisig_tx.signatures = HexBytes(arguments['signatures'])
                multisig_tx.save(update_fields=['ethereum_tx', 'signatures'])

            for safe_signature in SafeSignature.parse_signatures(safe_tx.signatures, safe_tx_hash):
                multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                    multisig_transaction_hash=safe_tx_hash,
                    owner=safe_signature.owner,
                    defaults={
                        'ethereum_tx': None,
                        'multisig_transaction': multisig_tx,
                        'signature': safe_signature.signature,
                    }
                )
                if multisig_confirmation.signature != safe_signature.signature:
                    multisig_confirmation.signature = safe_signature.signature
                    multisig_confirmation.save(update_fields=['signature'])

            safe_status.nonce = nonce + 1
            safe_status.store_new(internal_tx)
        elif function_name == 'approveHash':
            multisig_transaction_hash = arguments['hashToApprove']
            ethereum_tx = internal_tx.ethereum_tx
            owner = internal_tx.get_previous_trace()._from
            (multisig_confirmation,
             created) = MultisigConfirmation.objects.get_or_create(multisig_transaction_hash=multisig_transaction_hash,
                                                                   owner=owner,
                                                                   defaults={
                                                                       'ethereum_tx': ethereum_tx,
                                                                   })
            if not created and not multisig_confirmation.ethereum_tx_id:
                multisig_confirmation.ethereum_tx = ethereum_tx
                multisig_confirmation.save()

        elif function_name == 'execTransactionFromModule':
            # No side effects or nonce increasing, but trace will be set as processed
            pass
        else:
            processed_successfully = False
        internal_tx_decoded.set_processed()
        return processed_successfully
Esempio n. 15
0
    def send_multiple_txs(
        self,
        safe_address: str,
        safe_version: str,
        accounts: List[Account],
        payment_token: Optional[str] = None,
        number_txs: int = 100,
    ) -> List[bytes]:
        tx_hash = self.ethereum_client.send_eth_to(
            self.main_account.key,
            safe_address,
            self.ethereum_client.w3.eth.gasPrice,
            self.w3.toWei(1, "ether"),
            nonce=self.main_account_nonce,
        )
        self.main_account_nonce += 1

        self.stdout.write(
            self.style.SUCCESS(
                "Sent 1 ether for testing sending multiple txs, "
                "waiting for receipt with tx-hash=%s" % tx_hash.hex()))
        self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=500)

        self.stdout.write(
            self.style.SUCCESS("Sending %d txs of 1 wei" % number_txs))
        safe_nonce = None
        tx_hashes = []
        for _ in range(number_txs):
            tx = {
                "to": self.main_account.address,
                "value": 1,  # Send 1 wei
                "data": None,
                "operation": 0,  # CALL
                "gasToken": payment_token,
            }

            if payment_token:
                tx["gasToken"] = payment_token

            # We used payment * 2 to fund the safe, now we return ether to the main account
            r = requests.post(self.get_estimate_url(safe_address), json=tx)
            assert r.ok, "Estimate not working %s" % r.content
            self.stdout.write(
                self.style.SUCCESS("Estimation=%s for tx=%s" % (r.json(), tx)))
            # estimate_gas = r.json()['safeTxGas'] + r.json()['dataGas'] + r.json()['operationalGas']
            # fees = r.json()['gasPrice'] * estimate_gas

            tx["dataGas"] = r.json()["dataGas"]
            tx["gasPrice"] = r.json()["gasPrice"]
            tx["safeTxGas"] = r.json()["safeTxGas"]
            if safe_nonce is not None:
                safe_nonce += 1
            else:
                safe_nonce = (0 if r.json()["lastUsedNonce"] is None else
                              r.json()["lastUsedNonce"] + 1)
            tx["nonce"] = safe_nonce
            tx["refundReceiver"] = None

            # Sign the tx
            safe_tx_hash = SafeTx(
                None,
                safe_address,
                tx["to"],
                tx["value"],
                tx["data"],
                tx["operation"],
                tx["safeTxGas"],
                tx["dataGas"],
                tx["gasPrice"],
                tx["gasToken"],
                tx["refundReceiver"],
                tx["nonce"],
                safe_version=safe_version,
            ).safe_tx_hash

            signatures = [
                account.signHash(safe_tx_hash) for account in accounts[:2]
            ]
            curated_signatures = [{
                "r": signature["r"],
                "s": signature["s"],
                "v": signature["v"]
            } for signature in signatures]
            tx["signatures"] = curated_signatures

            self.stdout.write(
                self.style.SUCCESS("Sending tx to stress test the server %s" %
                                   tx))
            r = requests.post(self.get_tx_url(safe_address), json=tx)
            assert r.ok, "Error sending tx %s" % r.content

            tx_hash = r.json()["txHash"]
            tx_hashes.append(tx_hash)

            tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash,
                                                                  timeout=500)
            assert tx_receipt.status == 1, "Error with tx %s" % tx_hash.hex()

        for tx_hash in tx_hashes:
            tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash,
                                                                  timeout=500)
            assert tx_receipt.status == 1, "Error with tx %s" % tx_hash.hex()
            self.stdout.write(
                self.style.SUCCESS("Tx with tx-hash=%s was successful" %
                                   tx_hash))
        return tx_hashes
Esempio n. 16
0
    def send_safe_tx(
        self,
        safe_address: str,
        safe_version: str,
        accounts: List[Account],
        payment_token: Optional[str] = None,
        wait_for_receipt: bool = True,
    ) -> bytes:
        safe_balance = self.w3.eth.get_balance(safe_address)
        tx = {
            "to": self.main_account.address,
            "value": safe_balance,
            "data": None,
            "operation": 0,  # CALL
            "gasToken": payment_token,
        }

        if payment_token:
            tx["gasToken"] = payment_token

        # We used payment * 2 to fund the safe, now we return ether to the main account
        r = requests.post(self.get_estimate_url(safe_address), json=tx)
        assert r.ok, "Estimate not working %s" % r.content
        self.stdout.write(
            self.style.SUCCESS("Estimation=%s for tx=%s" % (r.json(), tx)))
        # estimate_gas = r.json()['safeTxGas'] + r.json()['dataGas'] + r.json()['operationalGas']
        # fees = r.json()['gasPrice'] * estimate_gas

        if payment_token:
            # We can transfer the full amount as we are paying fees with a token
            tx["value"] = safe_balance
        else:
            estimate_gas = (r.json()["safeTxGas"] + r.json()["dataGas"] +
                            r.json()["operationalGas"])
            fees = r.json()["gasPrice"] * estimate_gas
            tx["value"] = safe_balance - fees

        tx["dataGas"] = r.json()["dataGas"] + r.json()["operationalGas"]
        tx["gasPrice"] = r.json()["gasPrice"]
        tx["safeTxGas"] = r.json()["safeTxGas"]
        tx["nonce"] = (0 if r.json()["lastUsedNonce"] is None else
                       r.json()["lastUsedNonce"] + 1)
        tx["refundReceiver"] = None

        # Sign the tx
        safe_tx_hash = SafeTx(
            None,
            safe_address,
            tx["to"],
            tx["value"],
            tx["data"],
            tx["operation"],
            tx["safeTxGas"],
            tx["dataGas"],
            tx["gasPrice"],
            tx["gasToken"],
            tx["refundReceiver"],
            safe_nonce=tx["nonce"],
            safe_version=safe_version,
        ).safe_tx_hash

        signatures = [
            account.signHash(safe_tx_hash) for account in accounts[:2]
        ]
        curated_signatures = [{
            "r": signature["r"],
            "s": signature["s"],
            "v": signature["v"]
        } for signature in signatures]
        tx["signatures"] = curated_signatures

        self.stdout.write(
            self.style.SUCCESS(
                "Sending multisig tx to return some funds to the main owner %s"
                % tx))
        r = requests.post(self.get_tx_url(safe_address), json=tx)
        assert r.ok, "Error sending tx %s" % r.content

        multisig_tx_hash = r.json()["txHash"]
        self.stdout.write(
            self.style.SUCCESS("Tx with tx-hash=%s was successful" %
                               multisig_tx_hash))
        if wait_for_receipt:
            self.w3.eth.wait_for_transaction_receipt(multisig_tx_hash,
                                                     timeout=500)
        return multisig_tx_hash
Esempio n. 17
0
    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
        """
        function_name = internal_tx_decoded.function_name
        arguments = internal_tx_decoded.arguments
        internal_tx = internal_tx_decoded.internal_tx
        contract_address = internal_tx._from
        master_copy = internal_tx.to
        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`
            return False

        processed_successfully = True
        logger.debug(
            'Start processing InternalTxDecoded in tx-hash=%s',
            HexBytes(internal_tx_decoded.internal_tx.ethereum_tx_id).hex())
        if function_name == 'setup' and contract_address != NULL_ADDRESS:
            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.ethereum_tx.block_id
                    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.ethereum_tx.block_id - blocks_one_day, 0))
                logger.info('Found new Safe=%s', contract_address)

            SafeStatus.objects.create(internal_tx=internal_tx,
                                      address=contract_address,
                                      owners=owners,
                                      threshold=threshold,
                                      nonce=nonce,
                                      master_copy=master_copy,
                                      fallback_handler=fallback_handler)
            self.clear_cache(contract_address)
        elif function_name in ('addOwnerWithThreshold', 'removeOwner',
                               'removeOwnerWithThreshold'):
            logger.debug('Processing owner/threshold modification')
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.threshold = arguments['_threshold']
            owner = arguments['owner']
            if function_name == 'addOwnerWithThreshold':
                safe_status.owners.append(owner)
            else:  # removeOwner, removeOwnerWithThreshold
                self.remove_owner(internal_tx, safe_status, owner)
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'swapOwner':
            logger.debug('Processing owner swap')
            old_owner = arguments['oldOwner']
            new_owner = arguments['newOwner']
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            self.remove_owner(internal_tx, safe_status, old_owner)
            safe_status.owners.append(new_owner)
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'changeThreshold':
            logger.debug('Processing threshold change')
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.threshold = arguments['_threshold']
            self.store_new_safe_status(safe_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
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.master_copy = arguments['_masterCopy']
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'setFallbackHandler':
            logger.debug('Setting FallbackHandler')
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.fallback_handler = arguments['handler']
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'setGuard':
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.guard = arguments[
                'guard'] if arguments['guard'] != NULL_ADDRESS else None
            if safe_status.guard:
                logger.debug('Setting Guard')
            else:
                logger.debug('Unsetting Guard')
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'enableModule':
            logger.debug('Enabling Module')
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.enabled_modules.append(arguments['module'])
            self.store_new_safe_status(safe_status, internal_tx)
        elif function_name == 'disableModule':
            logger.debug('Disabling Module')
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.enabled_modules.remove(arguments['module'])
            self.store_new_safe_status(safe_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
            # 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 go at least 2 traces back
            previous_trace = self.ethereum_client.parity.get_previous_trace(
                internal_tx.ethereum_tx_id,
                internal_tx.trace_address_as_list,
                number_traces=2,
                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)
            module_internal_tx = InternalTx.objects.build_from_trace(
                previous_trace, internal_tx.ethereum_tx)
            module_address = module_internal_tx.to if module_internal_tx else NULL_ADDRESS
            module_data = HexBytes(arguments['data'])
            failed = self.is_module_failed(ethereum_tx, module_address,
                                           contract_address)
            ModuleTransaction.objects.get_or_create(
                internal_tx=internal_tx,
                defaults={
                    'created': internal_tx.ethereum_tx.block.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
            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.ethereum_tx.block.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')
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            nonce = safe_status.nonce
            if 'baseGas' in arguments:  # `dataGas` was renamed to `baseGas` in v1.0.0
                base_gas = arguments['baseGas']
                safe_version = '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)
            safe_tx_hash = safe_tx.safe_tx_hash

            ethereum_tx = internal_tx.ethereum_tx

            # Remove existing transaction with same nonce in case of bad indexing (one of the master copies can be
            # outdated and a tx with a wrong nonce could be indexed)
            # MultisigTransaction.objects.filter(
            #    ethereum_tx=ethereum_tx,
            #    nonce=safe_tx.safe_nonce,
            #    safe=contract_address
            # ).exclude(
            #     safe_tx_hash=safe_tx_hash
            # ).delete()

            # Remove old txs not used
            # MultisigTransaction.objects.filter(
            #     ethereum_tx=None,
            #     nonce__lt=safe_tx.safe_nonce,
            #     safe=contract_address
            # ).delete()

            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.ethereum_tx.block.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,
                })
            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.ethereum_tx.block.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_status.nonce = nonce + 1
            self.store_new_safe_status(safe_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.debug('End processing')
        return processed_successfully
Esempio n. 18
0
    def send_multiple_txs(self,
                          safe_address: str,
                          safe_version: str,
                          accounts: List[Account],
                          payment_token: Optional[str] = None,
                          number_txs: int = 100) -> List[bytes]:
        tx_hash = send_eth(self.w3,
                           self.main_account,
                           safe_address,
                           self.w3.toWei(1, 'ether'),
                           nonce=self.main_account_nonce)
        self.main_account_nonce += 1

        self.stdout.write(
            self.style.SUCCESS(
                'Sent 1 ether for testing sending multiple txs, '
                'waiting for receipt with tx-hash=%s' % tx_hash.hex()))
        self.w3.eth.waitForTransactionReceipt(tx_hash, timeout=500)

        self.stdout.write(
            self.style.SUCCESS('Sending %d txs of 1 wei' % number_txs))
        safe_nonce = None
        tx_hashes = []
        for _ in range(number_txs):
            tx = {
                'to': self.main_account.address,
                'value': 1,  # Send 1 wei
                'data': None,
                'operation': 0,  # CALL
                'gasToken': payment_token,
            }

            if payment_token:
                tx['gasToken'] = payment_token

            # We used payment * 2 to fund the safe, now we return ether to the main account
            r = requests.post(self.get_estimate_url(safe_address), json=tx)
            assert r.ok, "Estimate not working %s" % r.content
            self.stdout.write(
                self.style.SUCCESS('Estimation=%s for tx=%s' % (r.json(), tx)))
            # estimate_gas = r.json()['safeTxGas'] + r.json()['dataGas'] + r.json()['operationalGas']
            # fees = r.json()['gasPrice'] * estimate_gas

            tx['dataGas'] = r.json()['dataGas']
            tx['gasPrice'] = r.json()['gasPrice']
            tx['safeTxGas'] = r.json()['safeTxGas']
            if safe_nonce is not None:
                safe_nonce += 1
            else:
                safe_nonce = 0 if r.json(
                )['lastUsedNonce'] is None else r.json()['lastUsedNonce'] + 1
            tx['nonce'] = safe_nonce
            tx['refundReceiver'] = None

            # Sign the tx
            safe_tx_hash = SafeTx(None,
                                  safe_address,
                                  tx['to'],
                                  tx['value'],
                                  tx['data'],
                                  tx['operation'],
                                  tx['safeTxGas'],
                                  tx['dataGas'],
                                  tx['gasPrice'],
                                  tx['gasToken'],
                                  tx['refundReceiver'],
                                  tx['nonce'],
                                  safe_version=safe_version).safe_tx_hash

            signatures = [
                account.signHash(safe_tx_hash) for account in accounts[:2]
            ]
            curated_signatures = [{
                'r': signature['r'],
                's': signature['s'],
                'v': signature['v']
            } for signature in signatures]
            tx['signatures'] = curated_signatures

            self.stdout.write(
                self.style.SUCCESS('Sending tx to stress test the server %s' %
                                   tx))
            r = requests.post(self.get_tx_url(safe_address), json=tx)
            assert r.ok, "Error sending tx %s" % r.content

            tx_hash = r.json()['txHash']
            tx_hashes.append(tx_hash)

            tx_receipt = self.w3.eth.waitForTransactionReceipt(tx_hash,
                                                               timeout=500)
            assert tx_receipt.status == 1, 'Error with tx %s' % tx_hash.hex()

        for tx_hash in tx_hashes:
            tx_receipt = self.w3.eth.waitForTransactionReceipt(tx_hash,
                                                               timeout=500)
            assert tx_receipt.status == 1, 'Error with tx %s' % tx_hash.hex()
            self.stdout.write(
                self.style.SUCCESS('Tx with tx-hash=%s was successful' %
                                   tx_hash))
        return tx_hashes
    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