コード例 #1
0
 def __init__(self, gas_station: GasStation, ethereum_client: EthereumClient, redis: Redis,
              safe_valid_contract_addresses: Set[str], proxy_factory_address: str, tx_sender_private_key: str):
     self.gas_station = gas_station
     self.ethereum_client = ethereum_client
     self.redis = redis
     self.safe_valid_contract_addresses = safe_valid_contract_addresses
     self.proxy_factory = ProxyFactory(proxy_factory_address, self.ethereum_client)
     self.tx_sender_account = Account.from_key(tx_sender_private_key)
コード例 #2
0
    def deploy_create2_safe_tx(self, safe_address: str) -> SafeCreation2:
        """
        Deploys safe if SafeCreation2 exists.
        :param safe_address:
        :return: tx_hash
        """
        safe_creation2: SafeCreation2 = SafeCreation2.objects.get(
            safe=safe_address)

        if safe_creation2.tx_hash:
            logger.info('Safe=%s has already been deployed with tx-hash=%s',
                        safe_address, safe_creation2.tx_hash)
            return safe_creation2

        if safe_creation2.payment_token and safe_creation2.payment_token != NULL_ADDRESS:
            safe_balance = self.ethereum_client.erc20.get_balance(
                safe_address, safe_creation2.payment_token)
        else:
            safe_balance = self.ethereum_client.get_balance(safe_address)

        if safe_balance < safe_creation2.payment:
            message = 'Balance=%d for safe=%s with payment-token=%s. Not found ' \
                      'required=%d' % (safe_balance,
                                       safe_address,
                                       safe_creation2.payment_token,
                                       safe_creation2.payment)
            logger.info(message)
            raise NotEnoughFundingForCreation(message)

        logger.info(
            'Found %d balance for safe=%s with payment-token=%s. Required=%d',
            safe_balance, safe_address, safe_creation2.payment_token,
            safe_creation2.payment)

        setup_data = HexBytes(safe_creation2.setup_data.tobytes())

        with EthereumNonceLock(self.redis,
                               self.ethereum_client,
                               self.funder_account.address,
                               timeout=60 * 2) as tx_nonce:
            proxy_factory = ProxyFactory(safe_creation2.proxy_factory,
                                         self.ethereum_client)
            ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
                self.funder_account,
                safe_creation2.master_copy,
                setup_data,
                safe_creation2.salt_nonce,
                gas=safe_creation2.gas_estimated,
                gas_price=safe_creation2.gas_price_estimated,
                nonce=tx_nonce)
            EthereumTx.objects.create_from_tx(ethereum_tx_sent.tx,
                                              ethereum_tx_sent.tx_hash)
            safe_creation2.tx_hash = ethereum_tx_sent.tx_hash
            safe_creation2.save()
            logger.info('Deployed safe=%s with tx-hash=%s', safe_address,
                        ethereum_tx_sent.tx_hash.hex())
            return safe_creation2
コード例 #3
0
    def deploy_again_create2_safe_tx(self, safe_address: str) -> SafeCreation2:
        """
        Try to deploy Safe again with a higher gas price
        :param safe_address:
        :return: tx_hash
        """
        safe_creation2: SafeCreation2 = SafeCreation2.objects.get(
            safe=safe_address)

        if not safe_creation2.tx_hash:
            message = f"Safe={safe_address} deploy transaction does not exist"
            logger.info(message)
            raise DeployTransactionDoesNotExist(message)

        if safe_creation2.block_number is not None:
            message = (
                f"Safe={safe_address} has already been deployed with tx-hash={safe_creation2.tx_hash} "
                f"on block-number={safe_creation2.block_number}")
            logger.info(message)
            raise SafeAlreadyExistsException(message)

        ethereum_tx: EthereumTx = EthereumTx.objects.get(
            tx_hash=safe_creation2.tx_hash)
        assert ethereum_tx, "Ethereum tx cannot be missing"

        self._check_safe_balance(safe_creation2)

        setup_data = HexBytes(safe_creation2.setup_data.tobytes())
        proxy_factory = ProxyFactory(safe_creation2.proxy_factory,
                                     self.ethereum_client)
        # Increase gas price a little
        gas_price = math.ceil(
            max(self.gas_station.get_gas_prices().fast, ethereum_tx.gas_price)
            * 1.1)
        ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
            self.funder_account,
            safe_creation2.master_copy,
            setup_data,
            safe_creation2.salt_nonce,
            gas=safe_creation2.gas_estimated + 50000,  # Just in case
            gas_price=gas_price,
            nonce=ethereum_tx.nonce,
        )  # Replace old transaction
        EthereumTx.objects.create_from_tx_dict(ethereum_tx_sent.tx,
                                               ethereum_tx_sent.tx_hash)
        safe_creation2.tx_hash = ethereum_tx_sent.tx_hash.hex()
        safe_creation2.save(update_fields=["tx_hash"])
        logger.info(
            "Send again transaction to deploy Safe=%s with tx-hash=%s",
            safe_address,
            safe_creation2.tx_hash,
        )
        return safe_creation2
コード例 #4
0
 def setUpClass(cls) -> None:
     cls.ethereum_node_url = cls.ETHEREUM_NODE_URL
     cls.ethereum_client = EthereumClient(cls.ethereum_node_url)
     cls.w3 = cls.ethereum_client.w3
     cls.ethereum_test_account = Account.from_key(cls.ETHEREUM_ACCOUNT_KEY)
     cls.safe_contract_address = Safe.deploy_master_contract(cls.ethereum_client,
                                                             cls.ethereum_test_account).contract_address
     cls.safe_old_contract_address = Safe.deploy_master_contract_v1_0_0(cls.ethereum_client,
                                                                        cls.ethereum_test_account).contract_address
     cls.proxy_factory = ProxyFactory(
         ProxyFactory.deploy_proxy_factory_contract(cls.ethereum_client,
                                                    cls.ethereum_test_account).contract_address,
         cls.ethereum_client)
コード例 #5
0
    def handle(self, *args, **options):
        ethereum_client = EthereumClientProvider()
        proxy_factory_address = settings.SAFE_PROXY_FACTORY_ADDRESS
        deployer_key = options['deployer_key']
        deployer_account = Account.privateKeyToAccount(
            deployer_key) if deployer_key else self.DEFAULT_ACCOUNT

        self.stdout.write(
            self.style.SUCCESS(
                'Checking if proxy factory was already deployed on %s' %
                proxy_factory_address))
        if ethereum_client.is_contract(proxy_factory_address):
            self.stdout.write(
                self.style.NOTICE('Proxy factory was already deployed on %s' %
                                  proxy_factory_address))
        else:
            self.stdout.write(
                self.style.SUCCESS(
                    'Deploying proxy factory using deployer account, '
                    'proxy factory %s not found' % proxy_factory_address))
            proxy_factory_address = ProxyFactory.deploy_proxy_factory_contract(
                ethereum_client,
                deployer_account=deployer_account).contract_address
            self.stdout.write(
                self.style.SUCCESS('Proxy factory has been deployed on %s' %
                                   proxy_factory_address))
コード例 #6
0
    def deploy_create2_safe_tx(self, safe_address: str) -> SafeCreation2:
        """
        Deploys safe if SafeCreation2 exists.
        :param safe_address:
        :return: tx_hash
        """
        safe_creation2: SafeCreation2 = SafeCreation2.objects.get(
            safe=safe_address)

        if safe_creation2.tx_hash:
            logger.info(
                "Safe=%s has already been deployed with tx-hash=%s",
                safe_address,
                safe_creation2.tx_hash,
            )
            return safe_creation2

        self._check_safe_balance(safe_creation2)

        setup_data = HexBytes(safe_creation2.setup_data.tobytes())
        proxy_factory = ProxyFactory(safe_creation2.proxy_factory,
                                     self.ethereum_client)
        with EthereumNonceLock(
                self.redis,
                self.ethereum_client,
                self.funder_account.address,
                lock_timeout=60 * 2,
        ) as tx_nonce:
            ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
                self.funder_account,
                safe_creation2.master_copy,
                setup_data,
                safe_creation2.salt_nonce,
                gas=safe_creation2.gas_estimated + 50000,  # Just in case
                gas_price=safe_creation2.gas_price_estimated,
                nonce=tx_nonce,
            )
            EthereumTx.objects.create_from_tx_dict(ethereum_tx_sent.tx,
                                                   ethereum_tx_sent.tx_hash)
            safe_creation2.tx_hash = ethereum_tx_sent.tx_hash
            safe_creation2.save(update_fields=["tx_hash"])
            logger.info(
                "Send transaction to deploy Safe=%s with tx-hash=%s",
                safe_address,
                ethereum_tx_sent.tx_hash.hex(),
            )
            return safe_creation2
コード例 #7
0
 def __init__(self, gas_station: GasStation,
              ethereum_client: EthereumClient, redis: Redis,
              safe_contract_address: str, safe_old_contract_address: str,
              proxy_factory_address: str, safe_funder_private_key: str,
              safe_fixed_creation_cost: int, safe_auto_fund: bool,
              safe_auto_approve_token: bool):
     self.gas_station = gas_station
     self.ethereum_client = ethereum_client
     self.redis = redis
     self.safe_contract_address = safe_contract_address
     self.safe_old_contract_address = safe_old_contract_address
     self.proxy_factory = ProxyFactory(proxy_factory_address,
                                       self.ethereum_client)
     self.funder_account = Account.privateKeyToAccount(
         safe_funder_private_key)
     self.safe_fixed_creation_cost = safe_fixed_creation_cost
     self.safe_auto_fund = safe_auto_fund
     self.safe_auto_approve_token = safe_auto_approve_token
コード例 #8
0
 def __init__(self, gas_station: GasStation,
              ethereum_client: EthereumClient, redis: Redis,
              safe_contract_address: str, proxy_factory_address: str,
              default_callback_handler: str, safe_funder_private_key: str,
              safe_fixed_creation_cost: int):
     self.gas_station = gas_station
     self.ethereum_client = ethereum_client
     self.redis = redis
     self.safe_contract_address = safe_contract_address
     self.proxy_factory = ProxyFactory(proxy_factory_address,
                                       self.ethereum_client)
     self.default_callback_handler = default_callback_handler
     self.funder_account = Account.from_key(safe_funder_private_key)
     self.safe_fixed_creation_cost = safe_fixed_creation_cost
コード例 #9
0
class TransactionService:
    def __init__(self, gas_station: GasStation, ethereum_client: EthereumClient, redis: Redis,
                 safe_valid_contract_addresses: Set[str], proxy_factory_address: str, tx_sender_private_key: str):
        self.gas_station = gas_station
        self.ethereum_client = ethereum_client
        self.redis = redis
        self.safe_valid_contract_addresses = safe_valid_contract_addresses
        self.proxy_factory = ProxyFactory(proxy_factory_address, self.ethereum_client)
        self.tx_sender_account = Account.from_key(tx_sender_private_key)

    def _check_refund_receiver(self, refund_receiver: str) -> bool:
        """
        Support tx.origin or relay tx sender as refund receiver.
        This would prevent that anybody can front-run our service
        :param refund_receiver: Payment refund receiver as Ethereum checksummed address
        :return: True if refund_receiver is ok, False otherwise
        """
        return refund_receiver in (NULL_ADDRESS, self.tx_sender_account.address)

    @staticmethod
    def _is_valid_gas_token(address: Optional[str]) -> float:
        """
        :param address: Token address
        :return: bool if gas token, false otherwise
        """
        address = address or NULL_ADDRESS
        if address == NULL_ADDRESS:
            return True
        try:
            Token.objects.get(address=address, gas=True)
            return True
        except Token.DoesNotExist:
            logger.warning('Cannot retrieve gas token from db: Gas token %s not valid', address)
            return False

    def _check_safe_gas_price(self, gas_token: Optional[str], safe_gas_price: int) -> bool:
        """
        Check that `safe_gas_price` is not too low, so that the relay gets a full refund
        for the tx. Gas_price must be always > 0, if not refunding would be disabled
        If a `gas_token` is used we need to calculate the `gas_price` in Eth
        Gas price must be at least >= _minimum_gas_price_ > 0
        :param gas_token: Address of token is used, `NULL_ADDRESS` or `None` if it's ETH
        :return:
        :exception GasPriceTooLow
        :exception InvalidGasToken
        """
        if safe_gas_price < 1:
            raise RefundMustBeEnabled('Tx internal gas price cannot be 0 or less, it was %d' % safe_gas_price)

        minimum_accepted_gas_price = self._get_minimum_gas_price()
        estimated_gas_price = self._estimate_tx_gas_price(minimum_accepted_gas_price, gas_token)
        if safe_gas_price < estimated_gas_price:
            raise GasPriceTooLow('Required gas-price>=%d with gas-token=%s' % (estimated_gas_price, gas_token))
        return True

    def _estimate_tx_gas_price(self, base_gas_price: int, gas_token: Optional[str] = None) -> int:
        if gas_token and gas_token != NULL_ADDRESS:
            try:
                gas_token_model = Token.objects.get(address=gas_token, gas=True)
                estimated_gas_price = gas_token_model.calculate_gas_price(base_gas_price)
            except Token.DoesNotExist:
                raise InvalidGasToken('Gas token %s not found' % gas_token)
        else:
            estimated_gas_price = base_gas_price

        # FIXME Remove 2 / 3, workaround to prevent frontrunning
        return int(estimated_gas_price * 2 / 3)

    def _get_configured_gas_price(self) -> int:
        """
        :return: Gas price for txs
        """
        return self.gas_station.get_gas_prices().fast

    def _get_minimum_gas_price(self) -> int:
        """
        :return: Minimum gas price accepted for txs set by the user
        """
        return self.gas_station.get_gas_prices().standard

    def get_last_used_nonce(self, safe_address: str) -> Optional[int]:
        safe = Safe(safe_address, self.ethereum_client)
        last_used_nonce = SafeMultisigTx.objects.get_last_nonce_for_safe(safe_address)
        last_used_nonce = last_used_nonce if last_used_nonce is not None else -1
        try:
            blockchain_nonce = safe.retrieve_nonce()
            last_used_nonce = max(last_used_nonce, blockchain_nonce - 1)
            if last_used_nonce < 0:  # There's no last_used_nonce
                last_used_nonce = None
            return last_used_nonce
        except BadFunctionCallOutput:  # If Safe does not exist
            raise SafeDoesNotExist(f'Safe={safe_address} does not exist')

    def estimate_tx(self, safe_address: str, to: str, value: int, data: bytes, operation: int,
                    gas_token: Optional[str]) -> TransactionEstimationWithNonce:
        """
        :return: TransactionEstimation with costs using the provided gas token and last used nonce of the Safe
        :raises: InvalidGasToken: If Gas Token is not valid
        """
        if not self._is_valid_gas_token(gas_token):
            raise InvalidGasToken(gas_token)

        last_used_nonce = self.get_last_used_nonce(safe_address)
        safe = Safe(safe_address, self.ethereum_client)
        safe_tx_gas = safe.estimate_tx_gas(to, value, data, operation)
        safe_tx_base_gas = safe.estimate_tx_base_gas(to, value, data, operation, gas_token, safe_tx_gas)

        # For Safe contracts v1.0.0 operational gas is not used (`base_gas` has all the related costs already)
        safe_version = safe.retrieve_version()
        if Version(safe_version) >= Version('1.0.0'):
            safe_tx_operational_gas = 0
        else:
            safe_tx_operational_gas = safe.estimate_tx_operational_gas(len(data) if data else 0)

        # Can throw RelayServiceException
        gas_price = self._estimate_tx_gas_price(self._get_configured_gas_price(), gas_token)
        return TransactionEstimationWithNonce(safe_tx_gas, safe_tx_base_gas, safe_tx_base_gas, safe_tx_operational_gas,
                                              gas_price, gas_token or NULL_ADDRESS, last_used_nonce,
                                              self.tx_sender_account.address)

    def estimate_tx_for_all_tokens(self, safe_address: str, to: str, value: int, data: bytes,
                                   operation: int) -> TransactionEstimationWithNonceAndGasTokens:
        """
        :return: TransactionEstimation with costs using ether and every gas token supported by the service,
        with the last used nonce of the Safe
        :raises: InvalidGasToken: If Gas Token is not valid
        """
        safe = Safe(safe_address, self.ethereum_client)
        last_used_nonce = self.get_last_used_nonce(safe_address)
        safe_tx_gas = safe.estimate_tx_gas(to, value, data, operation)

        safe_version = safe.retrieve_version()
        if Version(safe_version) >= Version('1.0.0'):
            safe_tx_operational_gas = 0
        else:
            safe_tx_operational_gas = safe.estimate_tx_operational_gas(len(data) if data else 0)

        # Calculate `base_gas` for ether and calculate for tokens using the ether token price
        ether_safe_tx_base_gas = safe.estimate_tx_base_gas(to, value, data, operation, NULL_ADDRESS, safe_tx_gas)
        base_gas_price = self._get_configured_gas_price()
        gas_price = self._estimate_tx_gas_price(base_gas_price, NULL_ADDRESS)
        gas_token_estimations = [TransactionGasTokenEstimation(ether_safe_tx_base_gas, gas_price, NULL_ADDRESS)]
        token_gas_difference = 50000  # 50K gas more expensive than ether
        for token in Token.objects.gas_tokens():
            try:
                gas_price = self._estimate_tx_gas_price(base_gas_price, token.address)
                gas_token_estimations.append(
                    TransactionGasTokenEstimation(ether_safe_tx_base_gas + token_gas_difference,
                                                  gas_price, token.address)
                )
            except CannotGetTokenPriceFromApi:
                logger.error('Cannot get price for token=%s', token.address)

        return TransactionEstimationWithNonceAndGasTokens(last_used_nonce, safe_tx_gas, safe_tx_operational_gas,
                                                          gas_token_estimations)

    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,
                           safe_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.not_failed().filter(safe=safe_contract, nonce=safe_nonce).exists():
            raise SafeMultisigTxExists(f'Tx with safe-nonce={safe_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,
                safe_nonce,
                signatures_packed
            )
        except SafeServiceException as exc:
            raise TransactionServiceException(str(exc)) from exc

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

        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=safe_nonce,
                signatures=signatures_packed,
                safe_tx_hash=safe_tx_hash,
            )
        except IntegrityError as exc:
            raise SafeMultisigTxExists(f'Tx with safe_tx_hash={safe_tx_hash.hex()} already exists in DB') from exc

    def _send_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,
                          safe_nonce: int,
                          signatures: bytes,
                          block_identifier='latest') -> Tuple[bytes, bytes, Dict[str, Any]]:
        """
        This function calls the `send_multisig_tx` of the Safe, but has some limitations to prevent abusing
        the relay
        :return: Tuple(tx_hash, safe_tx_hash, tx)
        :raises: InvalidMultisigTx: If user tx cannot go through the Safe
        """

        safe = Safe(safe_address, self.ethereum_client)
        data = data or b''
        gas_token = gas_token or NULL_ADDRESS
        refund_receiver = refund_receiver or NULL_ADDRESS
        to = to or NULL_ADDRESS

        # Make sure refund receiver is set to 0x0 so that the contract refunds the gas costs to tx.origin
        if not self._check_refund_receiver(refund_receiver):
            raise InvalidRefundReceiver(refund_receiver)

        self._check_safe_gas_price(gas_token, gas_price)

        # Make sure proxy contract is ours
        if not self.proxy_factory.check_proxy_code(safe_address):
            raise InvalidProxyContract(safe_address)

        # Make sure master copy is valid
        safe_master_copy_address = safe.retrieve_master_copy_address()
        if safe_master_copy_address not in self.safe_valid_contract_addresses:
            raise InvalidMasterCopyAddress(safe_master_copy_address)

        # Check enough funds to pay for the gas
        if not safe.check_funds_for_tx_gas(safe_tx_gas, base_gas, gas_price, gas_token):
            raise NotEnoughFundsForMultisigTx

        threshold = safe.retrieve_threshold()
        number_signatures = len(signatures) // 65  # One signature = 65 bytes
        if number_signatures < threshold:
            raise SignaturesNotFound('Need at least %d signatures' % threshold)

        safe_tx_gas_estimation = safe.estimate_tx_gas(to, value, data, operation)
        safe_base_gas_estimation = safe.estimate_tx_base_gas(to, value, data, operation, gas_token,
                                                             safe_tx_gas_estimation)
        if safe_tx_gas < safe_tx_gas_estimation or base_gas < safe_base_gas_estimation:
            raise InvalidGasEstimation("Gas should be at least equal to safe-tx-gas=%d and base-gas=%d. Current is "
                                       "safe-tx-gas=%d and base-gas=%d" %
                                       (safe_tx_gas_estimation, safe_base_gas_estimation, safe_tx_gas, base_gas))

        # We use fast tx gas price, if not txs could be stuck
        tx_gas_price = self._get_configured_gas_price()
        tx_sender_private_key = self.tx_sender_account.key
        tx_sender_address = Account.from_key(tx_sender_private_key).address

        safe_tx = safe.build_multisig_tx(
            to,
            value,
            data,
            operation,
            safe_tx_gas,
            base_gas,
            gas_price,
            gas_token,
            refund_receiver,
            signatures,
            safe_nonce=safe_nonce,
            safe_version=safe.retrieve_version()
        )

        owners = safe.retrieve_owners()
        signers = safe_tx.signers
        if set(signers) - set(owners):  # All the signers must be owners
            raise InvalidOwners('Signers=%s are not valid owners of the safe. Owners=%s', safe_tx.signers, owners)

        if signers != safe_tx.sorted_signers:
            raise SignaturesNotSorted('Safe-tx-hash=%s - Signatures are not sorted by owner: %s' %
                                      (safe_tx.safe_tx_hash.hex(), safe_tx.signers))

        if banned_signers := BannedSigner.objects.filter(address__in=signers):
            raise SignerIsBanned(f'Signers {list(banned_signers)} are banned')

        logger.info('Safe=%s safe-nonce=%d Check `call()` before sending transaction', safe_address, safe_nonce)
        # Set `gasLimit` for `call()`. It will use the same that it will be used later for execution
        tx_gas = safe_tx.recommended_gas()
        safe_tx.call(tx_sender_address=tx_sender_address, tx_gas=tx_gas, block_identifier=block_identifier)
        with EthereumNonceLock(self.redis, self.ethereum_client, self.tx_sender_account.address,
                               lock_timeout=60 * 2) as tx_nonce:
            logger.info('Safe=%s safe-nonce=%d `call()` was successful', safe_address, safe_nonce)
            tx_hash, tx = safe_tx.execute(tx_sender_private_key, tx_gas=tx_gas, tx_gas_price=tx_gas_price,
                                          tx_nonce=tx_nonce, block_identifier=block_identifier)
            logger.info('Safe=%s, Sent transaction with nonce=%d tx-hash=%s for safe-tx-hash=%s safe-nonce=%d',
                        safe_address, tx_nonce, tx_hash.hex(), safe_tx.safe_tx_hash.hex(), safe_tx.safe_nonce)
            return tx_hash, safe_tx.safe_tx_hash, tx
コード例 #10
0
ファイル: safe_creator.py プロジェクト: shayanb/safe-cli
            f'Sender {account.address} - Balance: {ether_account_balance}Ξ')

    if not ethereum_client.w3.eth.getCode(safe_contract_address) \
            or not ethereum_client.w3.eth.getCode(proxy_factory_address):
        print_formatted_text('Network not supported')
        sys.exit(1)

    salt_nonce = secrets.SystemRandom().randint(0, 2**256 -
                                                1)  # TODO Add support for CPK
    print_formatted_text(
        f'Creating new Safe with owners={owners} threshold={threshold} and sat-nonce={salt_nonce}'
    )
    gas_price = 0
    safe_creation_tx = Safe.build_safe_create2_tx(
        ethereum_client,
        safe_contract_address,
        proxy_factory_address,
        salt_nonce,
        owners,
        threshold,
        gas_price,
        fallback_handler=callback_handler_address,
        payment_token=None)
    proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client)
    ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
        account, safe_contract_address, safe_creation_tx.safe_setup_data,
        safe_creation_tx.salt_nonce)
    print_formatted_text(
        f'Tx with tx-hash={ethereum_tx_sent.tx_hash.hex()} '
        f'will create safe={ethereum_tx_sent.contract_address}')
コード例 #11
0
class TransactionService:
    def __init__(self, gas_station: GasStation,
                 ethereum_client: EthereumClient, redis: Redis,
                 safe_valid_contract_addresses: Set[str],
                 proxy_factory_address: str, tx_sender_private_key: str):
        self.gas_station = gas_station
        self.ethereum_client = ethereum_client
        self.redis = redis
        self.safe_valid_contract_addresses = safe_valid_contract_addresses
        self.proxy_factory = ProxyFactory(proxy_factory_address,
                                          self.ethereum_client)
        self.tx_sender_account = Account.privateKeyToAccount(
            tx_sender_private_key)

    @staticmethod
    def _check_refund_receiver(refund_receiver: str) -> bool:
        """
        We only support tx.origin as refund receiver right now
        In the future we can also accept transactions where it is set to our service account to receive the payments.
        This would prevent that anybody can front-run our service
        """
        return refund_receiver == NULL_ADDRESS

    @staticmethod
    def _is_valid_gas_token(address: Optional[str]) -> float:
        """
        :param address: Token address
        :return: bool if gas token, false otherwise
        """
        address = address or NULL_ADDRESS
        if address == NULL_ADDRESS:
            return True
        try:
            Token.objects.get(address=address, gas=True)
            return True
        except Token.DoesNotExist:
            logger.warning(
                'Cannot retrieve gas token from db: Gas token %s not valid',
                address)
            return False

    def _check_safe_gas_price(self, gas_token: Optional[str],
                              safe_gas_price: int) -> bool:
        """
        Check that `safe_gas_price` is not too low, so that the relay gets a full refund
        for the tx. Gas_price must be always > 0, if not refunding would be disabled
        If a `gas_token` is used we need to calculate the `gas_price` in Eth
        Gas price must be at least >= _current standard gas price_ > 0
        :param gas_token: Address of token is used, `NULL_ADDRESS` or `None` if it's ETH
        :return:
        :exception GasPriceTooLow
        :exception InvalidGasToken
        """
        if safe_gas_price < 1:
            raise RefundMustBeEnabled(
                'Tx internal gas price cannot be 0 or less, it was %d' %
                safe_gas_price)

        minimum_accepted_gas_price = self._get_minimum_gas_price()
        if gas_token and gas_token != NULL_ADDRESS:
            try:
                gas_token_model = Token.objects.get(address=gas_token,
                                                    gas=True)
                estimated_gas_price = gas_token_model.calculate_gas_price(
                    minimum_accepted_gas_price)
                if safe_gas_price < estimated_gas_price:
                    raise GasPriceTooLow(
                        'Required gas-price>=%d to use gas-token' %
                        estimated_gas_price)
                # We use gas station tx gas price. We cannot use internal tx's because is calculated
                # based on the gas token
            except Token.DoesNotExist:
                logger.warning(
                    'Cannot retrieve gas token from db: Gas token %s not valid',
                    gas_token)
                raise InvalidGasToken('Gas token %s not valid' % gas_token)
        else:
            if safe_gas_price < minimum_accepted_gas_price:
                raise GasPriceTooLow('Required gas-price>=%d' %
                                     minimum_accepted_gas_price)
        return True

    def _estimate_tx_gas_price(self, gas_token: Optional[str] = None):
        gas_price_fast = self._get_configured_gas_price()
        if gas_token and gas_token != NULL_ADDRESS:
            try:
                gas_token_model = Token.objects.get(address=gas_token,
                                                    gas=True)
                return gas_token_model.calculate_gas_price(gas_price_fast)
            except Token.DoesNotExist:
                raise InvalidGasToken('Gas token %s not found' % gas_token)
        else:
            return gas_price_fast

    def _get_configured_gas_price(self) -> int:
        """
        :return: Gas price for txs
        """
        return self.gas_station.get_gas_prices().standard

    def _get_minimum_gas_price(self) -> int:
        """
        :return: Minimum gas price accepted for txs set by the user
        """
        return self.gas_station.get_gas_prices().safe_low

    def estimate_tx(
            self, safe_address: str, to: str, value: int, data: str,
            operation: int,
            gas_token: Optional[str]) -> TransactionEstimationWithNonce:
        """
        :return: TransactionEstimation with costs and last used nonce of safe
        :raises: InvalidGasToken: If Gas Token is not valid
        """
        if not self._is_valid_gas_token(gas_token):
            raise InvalidGasToken(gas_token)
        last_used_nonce = SafeMultisigTx.objects.get_last_nonce_for_safe(
            safe_address)
        safe = Safe(safe_address, self.ethereum_client)
        safe_tx_gas = safe.estimate_tx_gas(to, value, data, operation)
        safe_tx_base_gas = safe.estimate_tx_base_gas(to, value, data,
                                                     operation, gas_token,
                                                     safe_tx_gas)

        # For Safe contracts v1.0.0 operational gas is not used (`base_gas` has all the related costs already)
        safe_version = safe.retrieve_version()
        if Version(safe_version) >= Version('1.0.0'):
            safe_tx_operational_gas = 0
        else:
            safe_tx_operational_gas = safe.estimate_tx_operational_gas(
                len(data) if data else 0)

        # Can throw RelayServiceException
        gas_price = self._estimate_tx_gas_price(gas_token)
        return TransactionEstimationWithNonce(safe_tx_gas, safe_tx_base_gas,
                                              safe_tx_base_gas,
                                              safe_tx_operational_gas,
                                              gas_price, gas_token
                                              or NULL_ADDRESS, last_used_nonce)

    def estimate_tx_for_all_tokens(
            self, safe_address: str, to: str, value: int, data: str,
            operation: int) -> TransactionEstimationWithNonceAndGasTokens:
        last_used_nonce = SafeMultisigTx.objects.get_last_nonce_for_safe(
            safe_address)
        safe = Safe(safe_address, self.ethereum_client)
        safe_tx_gas = safe.estimate_tx_gas(to, value, data, operation)

        safe_version = safe.retrieve_version()
        if Version(safe_version) >= Version('1.0.0'):
            safe_tx_operational_gas = 0
        else:
            safe_tx_operational_gas = safe.estimate_tx_operational_gas(
                len(data) if data else 0)

        # Calculate `base_gas` for ether and calculate for tokens using the ether token price
        ether_safe_tx_base_gas = safe.estimate_tx_base_gas(
            to, value, data, operation, NULL_ADDRESS, safe_tx_gas)
        gas_price = self._estimate_tx_gas_price(NULL_ADDRESS)
        gas_token_estimations = [
            TransactionGasTokenEstimation(ether_safe_tx_base_gas, gas_price,
                                          NULL_ADDRESS)
        ]
        token_gas_difference = 50000  # 50K gas more expensive than ether
        for token in Token.objects.gas_tokens():
            try:
                gas_price = self._estimate_tx_gas_price(token.address)
                gas_token_estimations.append(
                    TransactionGasTokenEstimation(
                        ether_safe_tx_base_gas + token_gas_difference,
                        gas_price, token.address))
            except CannotGetTokenPriceFromApi:
                logger.error('Cannot get price for token=%s', token.address)

        return TransactionEstimationWithNonceAndGasTokens(
            last_used_nonce, safe_tx_gas, safe_tx_operational_gas,
            gas_token_estimations)

    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(address=safe_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 _send_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,
            safe_nonce: int,
            signatures: bytes,
            tx_gas=None,
            block_identifier='latest') -> Tuple[bytes, bytes, Dict[str, Any]]:
        """
        This function calls the `send_multisig_tx` of the Safe, but has some limitations to prevent abusing
        the relay
        :return: Tuple(tx_hash, safe_tx_hash, tx)
        :raises: InvalidMultisigTx: If user tx cannot go through the Safe
        """

        safe = Safe(safe_address, self.ethereum_client)
        data = data or b''
        gas_token = gas_token or NULL_ADDRESS
        refund_receiver = refund_receiver or NULL_ADDRESS
        to = to or NULL_ADDRESS

        # Make sure refund receiver is set to 0x0 so that the contract refunds the gas costs to tx.origin
        if not self._check_refund_receiver(refund_receiver):
            raise InvalidRefundReceiver(refund_receiver)

        self._check_safe_gas_price(gas_token, gas_price)

        # Make sure proxy contract is ours
        if not self.proxy_factory.check_proxy_code(safe_address):
            raise InvalidProxyContract(safe_address)

        # Make sure master copy is valid
        safe_master_copy_address = safe.retrieve_master_copy_address()
        if safe_master_copy_address not in self.safe_valid_contract_addresses:
            raise InvalidMasterCopyAddress(safe_master_copy_address)

        # Check enough funds to pay for the gas
        if not safe.check_funds_for_tx_gas(safe_tx_gas, base_gas, gas_price,
                                           gas_token):
            raise NotEnoughFundsForMultisigTx

        threshold = safe.retrieve_threshold()
        number_signatures = len(signatures) // 65  # One signature = 65 bytes
        if number_signatures < threshold:
            raise SignaturesNotFound('Need at least %d signatures' % threshold)

        safe_tx_gas_estimation = safe.estimate_tx_gas(to, value, data,
                                                      operation)
        safe_base_gas_estimation = safe.estimate_tx_base_gas(
            to, value, data, operation, gas_token, safe_tx_gas_estimation)
        if safe_tx_gas < safe_tx_gas_estimation or base_gas < safe_base_gas_estimation:
            raise InvalidGasEstimation(
                "Gas should be at least equal to safe-tx-gas=%d and data-gas=%d. Current is "
                "safe-tx-gas=%d and data-gas=%d" %
                (safe_tx_gas_estimation, safe_base_gas_estimation, safe_tx_gas,
                 base_gas))

        # We use fast tx gas price, if not txs could be stuck
        tx_gas_price = self._get_configured_gas_price()
        tx_sender_private_key = self.tx_sender_account.privateKey
        tx_sender_address = Account.privateKeyToAccount(
            tx_sender_private_key).address

        safe_tx = safe.build_multisig_tx(to,
                                         value,
                                         data,
                                         operation,
                                         safe_tx_gas,
                                         base_gas,
                                         gas_price,
                                         gas_token,
                                         refund_receiver,
                                         signatures,
                                         safe_nonce=safe_nonce,
                                         safe_version=safe.retrieve_version())

        if safe_tx.signers != safe_tx.sorted_signers:
            raise SignaturesNotSorted(
                'Safe-tx-hash=%s - Signatures are not sorted by owner: %s' %
                (safe_tx.safe_tx_hash.hex(), safe_tx.signers))

        safe_tx.call(tx_sender_address=tx_sender_address,
                     block_identifier=block_identifier)

        with EthereumNonceLock(self.redis,
                               self.ethereum_client,
                               self.tx_sender_account.address,
                               timeout=60 * 2) as tx_nonce:
            tx_hash, tx = safe_tx.execute(tx_sender_private_key,
                                          tx_gas=tx_gas,
                                          tx_gas_price=tx_gas_price,
                                          tx_nonce=tx_nonce,
                                          block_identifier=block_identifier)
            return tx_hash, safe_tx.tx_hash, tx

    def get_pending_multisig_transactions(
            self, older_than: int) -> List[SafeMultisigTx]:
        """
        Get multisig txs that have not been mined after X seconds
        :param older_than: Time in seconds for a tx to be considered pending, if 0 all will be returned
        """
        return SafeMultisigTx.objects.filter(
            Q(ethereum_tx__block=None) | Q(ethereum_tx=None)).filter(
                created__lte=timezone.now() - timedelta(seconds=older_than), )
コード例 #12
0
def main(*args, **kwargs):
    parser = setup_argument_parser()
    print_formatted_text(
        pyfiglet.figlet_format("Gnosis Safe Creator"))  # Print fancy text
    args = parser.parse_args()
    node_url: URI = args.node_url
    account: LocalAccount = Account.from_key(args.private_key)
    owners: List[str] = args.owners if args.owners else [account.address]
    threshold: int = args.threshold
    salt_nonce: int = args.salt_nonce
    to = NULL_ADDRESS
    data = b""
    payment_token = NULL_ADDRESS
    payment = 0
    payment_receiver = NULL_ADDRESS

    if len(owners) < threshold:
        print_formatted_text(
            "Threshold cannot be bigger than the number of unique owners")
        sys.exit(1)

    safe_contract_address = args.safe_contract or (
        LAST_SAFE_L2_CONTRACT if args.l2 else LAST_SAFE_CONTRACT)
    proxy_factory_address = args.proxy_factory
    fallback_handler = args.callback_handler
    ethereum_client = EthereumClient(node_url)
    ethereum_network = ethereum_client.get_network()

    if not ethereum_client.is_contract(safe_contract_address):
        print_formatted_text(
            f"Safe contract address {safe_contract_address} "
            f"does not exist on network {ethereum_network.name}")
        sys.exit(1)
    elif not ethereum_client.is_contract(proxy_factory_address):
        print_formatted_text(
            f"Proxy contract address {proxy_factory_address} "
            f"does not exist on network {ethereum_network.name}")
        sys.exit(1)
    elif fallback_handler != NULL_ADDRESS and not ethereum_client.is_contract(
            fallback_handler):
        print_formatted_text(
            f"Fallback handler address {fallback_handler} "
            f"does not exist on network {ethereum_network.name}")
        sys.exit(1)

    account_balance: int = ethereum_client.get_balance(account.address)
    if not account_balance:
        print_formatted_text(
            "Client does not have any funds. Let's try anyway in case it's a network without gas costs"
        )
    else:
        ether_account_balance = round(
            ethereum_client.w3.fromWei(account_balance, "ether"), 6)
        print_formatted_text(
            f"Network {ethereum_client.get_network().name} - Sender {account.address} - "
            f"Balance: {ether_account_balance}Ξ")

    if not ethereum_client.w3.eth.getCode(
            safe_contract_address) or not ethereum_client.w3.eth.getCode(
                proxy_factory_address):
        print_formatted_text("Network not supported")
        sys.exit(1)

    print_formatted_text(
        f"Creating new Safe with owners={owners} threshold={threshold} salt-nonce={salt_nonce}"
    )
    print_formatted_text(
        f"Proxy factory={proxy_factory_address} safe-master-copy={safe_contract_address} and "
        f"fallback-handler={fallback_handler}")
    if yes_or_no_question("Do you want to continue?"):
        safe_contract = get_safe_V1_3_0_contract(ethereum_client.w3,
                                                 safe_contract_address)
        safe_creation_tx_data = HexBytes(
            safe_contract.functions.setup(
                owners,
                threshold,
                to,
                data,
                fallback_handler,
                payment_token,
                payment,
                payment_receiver,
            ).buildTransaction({
                "gas": 1,
                "gasPrice": 1
            })["data"])

        proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client)
        ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
            account, safe_contract_address, safe_creation_tx_data, salt_nonce)
        print_formatted_text(
            f"Tx with tx-hash={ethereum_tx_sent.tx_hash.hex()} "
            f"will create safe={ethereum_tx_sent.contract_address}")
        print_formatted_text(f"Tx paramters={ethereum_tx_sent.tx}")
コード例 #13
0
class SafeCreationService:
    def __init__(self, gas_station: GasStation,
                 ethereum_client: EthereumClient, redis: Redis,
                 safe_contract_address: str, safe_old_contract_address: str,
                 proxy_factory_address: str, safe_funder_private_key: str,
                 safe_fixed_creation_cost: int, safe_auto_fund: bool,
                 safe_auto_approve_token: bool):
        self.gas_station = gas_station
        self.ethereum_client = ethereum_client
        self.redis = redis
        self.safe_contract_address = safe_contract_address
        self.safe_old_contract_address = safe_old_contract_address
        self.proxy_factory = ProxyFactory(proxy_factory_address,
                                          self.ethereum_client)
        self.funder_account = Account.privateKeyToAccount(
            safe_funder_private_key)
        self.safe_fixed_creation_cost = safe_fixed_creation_cost
        self.safe_auto_fund = safe_auto_fund
        self.safe_auto_approve_token = safe_auto_approve_token

    def _get_token_eth_value_or_raise(self, address: str) -> float:
        """
        :param address: Token address
        :return: Current eth value of the token
        :raises: InvalidPaymentToken, CannotGetTokenPriceFromApi
        """
        address = address or NULL_ADDRESS
        if address == NULL_ADDRESS:
            return 1.0

        try:
            token = Token.objects.get(address=address, gas=True)
        except Token.DoesNotExist:
            if self.safe_auto_approve_token:
                # Add the token for development purposes.
                token = Token.objects.create(address=address,
                                             name="Cash",
                                             symbol="cash",
                                             decimals=2,
                                             fixed_eth_conversion=0.006,
                                             gas=True)
            else:
                raise

        return token.get_eth_value()

    def _get_configured_gas_price(self) -> int:
        """
        :return: Gas price for txs
        """
        return self.gas_station.get_gas_prices().fast

    def create_safe_tx(self, s: int, owners: List[str], threshold: int,
                       payment_token: Optional[str]) -> SafeCreation:
        """
        Prepare creation tx for a new safe using classic CREATE method. Deprecated, it's recommended
        to use `create2_safe_tx`
        :param s: Random s value for ecdsa signature
        :param owners: Owners of the new Safe
        :param threshold: Minimum number of users required to operate the Safe
        :param payment_token: Address of the payment token, if ether is not used
        :rtype: SafeCreation
        :raises: InvalidPaymentToken
        """

        payment_token = payment_token or NULL_ADDRESS
        payment_token_eth_value = self._get_token_eth_value_or_raise(
            payment_token)
        gas_price: int = self._get_configured_gas_price()
        current_block_number = self.ethereum_client.current_block_number

        logger.debug('Building safe creation tx with gas price %d' % gas_price)
        safe_creation_tx = Safe.build_safe_creation_tx(
            self.ethereum_client,
            self.safe_old_contract_address,
            s,
            owners,
            threshold,
            gas_price,
            payment_token,
            self.funder_account.address,
            payment_token_eth_value=payment_token_eth_value,
            fixed_creation_cost=self.safe_fixed_creation_cost)

        safe_contract = SafeContract.objects.create(
            address=safe_creation_tx.safe_address,
            master_copy=safe_creation_tx.master_copy)

        # Enable tx and erc20 tracing
        SafeTxStatus.objects.create(safe=safe_contract,
                                    initial_block_number=current_block_number,
                                    tx_block_number=current_block_number,
                                    erc_20_block_number=current_block_number)

        return SafeCreation.objects.create(
            deployer=safe_creation_tx.deployer_address,
            safe=safe_contract,
            master_copy=safe_creation_tx.master_copy,
            funder=safe_creation_tx.funder,
            owners=owners,
            threshold=threshold,
            payment=safe_creation_tx.payment,
            tx_hash=safe_creation_tx.tx_hash.hex(),
            gas=safe_creation_tx.gas,
            gas_price=safe_creation_tx.gas_price,
            payment_token=None if safe_creation_tx.payment_token
            == NULL_ADDRESS else safe_creation_tx.payment_token,
            value=safe_creation_tx.tx_pyethereum.value,
            v=safe_creation_tx.v,
            r=safe_creation_tx.r,
            s=safe_creation_tx.s,
            data=safe_creation_tx.tx_pyethereum.data,
            signed_tx=safe_creation_tx.tx_raw)

    def create2_safe_tx(self, salt_nonce: int, owners: Iterable[str],
                        threshold: int, payment_token: Optional[str],
                        setup_data: Optional[str], to: Optional[str],
                        callback: Optional[str]) -> SafeCreation2:
        """
        Prepare creation tx for a new safe using CREATE2 method
        :param salt_nonce: Random value for solidity `create2` salt
        :param owners: Owners of the new Safe
        :param threshold: Minimum number of users required to operate the Safe
        :param payment_token: Address of the payment token, otherwise `ether` is used
        :param setup_data: Data used for safe creation delegate call.
        :rtype: SafeCreation2
        :raises: InvalidPaymentToken
        """
        callback = callback or NULL_ADDRESS
        payment_token = payment_token or NULL_ADDRESS
        payment_token_eth_value = self._get_token_eth_value_or_raise(
            payment_token)
        gas_price: int = self._get_configured_gas_price()
        current_block_number = self.ethereum_client.current_block_number
        logger.debug('Building safe create2 tx with gas price %d', gas_price)
        safe_creation_tx = Safe.build_safe_create2_tx(
            self.ethereum_client,
            self.safe_contract_address,
            self.proxy_factory.address,
            salt_nonce,
            owners,
            threshold,
            gas_price,
            payment_token,
            payment_token_eth_value=payment_token_eth_value,
            fixed_creation_cost=self.safe_fixed_creation_cost,
            setup_data=HexBytes(setup_data if setup_data else '0x'),
            to=to,
            callback=callback)

        safe_contract, created = SafeContract.objects.get_or_create(
            address=safe_creation_tx.safe_address,
            defaults={'master_copy': safe_creation_tx.master_copy_address})

        if not created:
            raise SafeAlreadyExistsException(
                f'Safe={safe_contract.address} cannot be created, already exists'
            )

        # Enable tx and erc20 tracing
        SafeTxStatus.objects.create(safe=safe_contract,
                                    initial_block_number=current_block_number,
                                    tx_block_number=current_block_number,
                                    erc_20_block_number=current_block_number)

        return SafeCreation2.objects.create(
            safe=safe_contract,
            master_copy=safe_creation_tx.master_copy_address,
            proxy_factory=safe_creation_tx.proxy_factory_address,
            salt_nonce=salt_nonce,
            owners=owners,
            threshold=threshold,
            to=to,  # Contract address for optional delegate call
            # data # Data payload for optional delegate call
            payment_token=None if safe_creation_tx.payment_token
            == NULL_ADDRESS else safe_creation_tx.payment_token,
            payment=safe_creation_tx.payment,
            payment_receiver=safe_creation_tx.payment_receiver,
            setup_data=safe_creation_tx.safe_setup_data,
            gas_estimated=safe_creation_tx.gas,
            gas_price_estimated=safe_creation_tx.gas_price,
            callback=callback,
        )

    def deploy_create2_safe_tx(self, safe_address: str) -> SafeCreation2:
        """
        Deploys safe if SafeCreation2 exists.
        :param safe_address:
        :return: tx_hash
        """

        safe_creation2 = SafeCreation2.objects.get(safe=safe_address)

        if safe_creation2.tx_hash:
            logger.info('Safe=%s has already been deployed with tx-hash=%s',
                        safe_address, safe_creation2.tx_hash)
            return safe_creation2

        if safe_creation2.payment_token and safe_creation2.payment_token != NULL_ADDRESS:
            safe_balance = self.ethereum_client.erc20.get_balance(
                safe_address, safe_creation2.payment_token)
        else:
            safe_balance = self.ethereum_client.get_balance(safe_address)

        if safe_balance < safe_creation2.payment:
            message = 'Balance=%d for safe=%s with payment-token=%s. Not found ' \
                      'required=%d\n' % (safe_balance,
                                       safe_address,
                                       safe_creation2.payment_token,
                                       safe_creation2.payment)

            # Be sure we are actually using an erc20.
            if self.safe_auto_fund and safe_creation2.payment_token and safe_creation2.payment_token != NULL_ADDRESS:
                # Send funds from deployers address to the contract.
                # NOTE: THIS IS FOR DEVELOPMENT PURPOSES ONLY (self.safe_auto_fund should be False on production)
                amount_to_send = 1000000000000000000000
                funder_balance = self.ethereum_client.erc20.get_balance(
                    self.funder_account.address, safe_creation2.payment_token)

                if amount_to_send < funder_balance:
                    message = message + 'Sending %d from account %s to %s.' % (
                        amount_to_send,
                        self.funder_account.address,
                        safe_address,
                    )

                    self.ethereum_client.erc20.send_tokens(
                        safe_address, amount_to_send,
                        safe_creation2.payment_token,
                        self.funder_account.privateKey)

                else:
                    message = message + 'Cannot seed wallet with funds. Please faucet %s' % (
                        self.funder_account.address)

                logger.info(message)
                raise NotEnoughFundingForCreation(message)

        logger.info(
            'Found %d balance for safe=%s with payment-token=%s. Required=%d',
            safe_balance, safe_address, safe_creation2.payment_token,
            safe_creation2.payment)

        setup_data = HexBytes(safe_creation2.setup_data.tobytes())

        logger.info(setup_data)

        with EthereumNonceLock(self.redis,
                               self.ethereum_client,
                               self.funder_account.address,
                               timeout=60 * 2) as tx_nonce:
            logger.info(
                'Calling deploy_proxy_contract_with_callback with: funder=%s address=%s setup_data=%s salt_nonce=%s callback=%s',
                self.funder_account, self.safe_contract_address, setup_data,
                safe_creation2.salt_nonce, safe_creation2.callback)
            ethereum_tx_sent = self.proxy_factory.deploy_proxy_contract_with_callback(
                self.funder_account,
                self.safe_contract_address,
                setup_data,
                safe_creation2.salt_nonce,
                safe_creation2.gas_estimated,
                safe_creation2.gas_price_estimated,
                nonce=tx_nonce,
                callback=safe_creation2.callback)
            EthereumTx.objects.create_from_tx(ethereum_tx_sent.tx,
                                              ethereum_tx_sent.tx_hash)
            safe_creation2.tx_hash = ethereum_tx_sent.tx_hash
            safe_creation2.save()
            logger.info('Deployed safe=%s with tx-hash=%s', safe_address,
                        ethereum_tx_sent.tx_hash.hex())
            return safe_creation2

    def estimate_safe_creation(
            self,
            number_owners: int,
            payment_token: Optional[str] = None) -> SafeCreationEstimate:
        """
        :param number_owners:
        :param payment_token:
        :return:
        :raises: InvalidPaymentToken
        """
        payment_token = payment_token or NULL_ADDRESS
        payment_token_eth_value = self._get_token_eth_value_or_raise(
            payment_token)
        gas_price = self._get_configured_gas_price()
        fixed_creation_cost = self.safe_fixed_creation_cost
        return Safe.estimate_safe_creation(
            self.ethereum_client,
            self.safe_old_contract_address,
            number_owners,
            gas_price,
            payment_token,
            payment_token_eth_value=payment_token_eth_value,
            fixed_creation_cost=fixed_creation_cost)

    def estimate_safe_creation2(
            self,
            number_owners: int,
            payment_token: Optional[str] = None) -> SafeCreationEstimate:
        """
        :param number_owners:
        :param payment_token:
        :return:
        :raises: InvalidPaymentToken
        """
        payment_token = payment_token or NULL_ADDRESS
        payment_token_eth_value = self._get_token_eth_value_or_raise(
            payment_token)
        gas_price = self._get_configured_gas_price()
        fixed_creation_cost = self.safe_fixed_creation_cost
        return Safe.estimate_safe_creation_2(
            self.ethereum_client,
            self.safe_contract_address,
            self.proxy_factory.address,
            number_owners,
            gas_price,
            payment_token,
            payment_token_eth_value=payment_token_eth_value,
            fixed_creation_cost=fixed_creation_cost)

    def estimate_safe_creation_for_all_tokens(
            self, number_owners: int) -> List[SafeCreationEstimate]:
        # Estimate for eth, then calculate for the rest of the tokens
        ether_creation_estimate = self.estimate_safe_creation2(
            number_owners, NULL_ADDRESS)
        safe_creation_estimates = [ether_creation_estimate]
        token_gas_difference = 50000  # 50K gas more expensive than ether
        for token in Token.objects.gas_tokens():
            try:
                safe_creation_estimates.append(
                    SafeCreationEstimate(
                        gas=ether_creation_estimate.gas + token_gas_difference,
                        gas_price=ether_creation_estimate.gas_price,
                        payment=token.calculate_payment(
                            ether_creation_estimate.payment),
                        payment_token=token.address,
                    ))
            except CannotGetTokenPriceFromApi:
                logger.error('Cannot get price for token=%s', token.address)
        return safe_creation_estimates

    def retrieve_safe_info(self, address: str) -> SafeInfo:
        safe = Safe(address, self.ethereum_client)
        if not self.ethereum_client.is_contract(address):
            raise SafeNotDeployed('Safe with address=%s not deployed' %
                                  address)
        nonce = safe.retrieve_nonce()
        threshold = safe.retrieve_threshold()
        owners = safe.retrieve_owners()
        master_copy = safe.retrieve_master_copy_address()
        version = safe.retrieve_version()
        return SafeInfo(address, nonce, threshold, owners, master_copy,
                        version)