def create_multisig_tx(self, safe_address: str, to: str, value: int,
                           data: bytes, operation: int, safe_tx_gas: int,
                           base_gas: int, gas_price: int, gas_token: str,
                           refund_receiver: str, nonce: int,
                           signatures: List[Dict[str, int]]) -> SafeMultisigTx:
        """
        :return: Database model of SafeMultisigTx
        :raises: SafeMultisigTxExists: If Safe Multisig Tx with nonce already exists
        :raises: InvalidGasToken: If Gas Token is not valid
        :raises: TransactionServiceException: If Safe Tx is not valid (not sorted owners, bad signature, bad nonce...)
        """

        safe_contract, _ = SafeContract.objects.get_or_create(
            address=safe_address, defaults={'master_copy': NULL_ADDRESS})
        created = timezone.now()

        if SafeMultisigTx.objects.filter(safe=safe_contract,
                                         nonce=nonce).exists():
            raise SafeMultisigTxExists(
                f'Tx with nonce={nonce} for safe={safe_address} already exists in DB'
            )

        signature_pairs = [(s['v'], s['r'], s['s']) for s in signatures]
        signatures_packed = signatures_to_bytes(signature_pairs)

        try:
            tx_hash, safe_tx_hash, tx = self._send_multisig_tx(
                safe_address, to, value, data, operation, safe_tx_gas,
                base_gas, gas_price, gas_token, refund_receiver, nonce,
                signatures_packed)
        except SafeServiceException as exc:
            raise TransactionServiceException(str(exc)) from exc

        ethereum_tx = EthereumTx.objects.create_from_tx(tx, tx_hash)

        # Fix race conditions for tx being created at the same time
        try:
            return SafeMultisigTx.objects.create(
                created=created,
                safe=safe_contract,
                ethereum_tx=ethereum_tx,
                to=to,
                value=value,
                data=data,
                operation=operation,
                safe_tx_gas=safe_tx_gas,
                data_gas=base_gas,
                gas_price=gas_price,
                gas_token=None if gas_token == NULL_ADDRESS else gas_token,
                refund_receiver=refund_receiver,
                nonce=nonce,
                signatures=signatures_packed,
                safe_tx_hash=safe_tx_hash,
            )
        except IntegrityError as exc:
            raise SafeMultisigTxExists(
                f'Tx with nonce={nonce} for safe={safe_address} already exists in DB'
            ) from exc
    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'])