Esempio n. 1
0
 def handle(self, *args, **options):
     ethereum_client = EthereumClientProvider()
     mismatchs = 0
     for safe_contract in SafeContract.objects.deployed():
         blockchain_balance = ethereum_client.get_balance(
             safe_contract.address)
         internal_tx_balance = safe_contract.get_balance()
         if blockchain_balance != internal_tx_balance:
             mismatchs += 1
             self.stdout.write(
                 self.style.NOTICE(
                     f'safe={safe_contract.address} '
                     f'blockchain-balance={blockchain_balance} does not match '
                     f'internal-tx-balance={internal_tx_balance}'))
     if mismatchs:
         self.stdout.write(
             self.style.NOTICE(
                 f"{mismatchs} Safes don't match blockchain balance"))
     else:
         self.stdout.write(
             self.style.SUCCESS('All Safes match blockchain balance'))
Esempio n. 2
0
    def handle(self, *args, **options):
        ethereum_client = EthereumClientProvider()
        deployer_key = options['deployer_key']
        deployer_account = Account.from_key(deployer_key) if deployer_key else self.DEFAULT_ACCOUNT

        master_copies_with_deploy_fn = {
            settings.SAFE_CONTRACT_ADDRESS: Safe.deploy_master_contract,
            settings.SAFE_V1_0_0_CONTRACT_ADDRESS: Safe.deploy_old_master_contract,
            settings.SAFE_PROXY_FACTORY_ADDRESS: ProxyFactory.deploy_proxy_factory_contract,
        }

        for master_copy_address, deploy_fn in master_copies_with_deploy_fn.items():
            self.stdout.write(self.style.SUCCESS(f'Checking if contract was already deployed on '
                                                 f'{master_copy_address}'))
            if ethereum_client.is_contract(master_copy_address):
                self.stdout.write(self.style.NOTICE(f'Master copy was already deployed on {master_copy_address}'))
            else:
                self.stdout.write(self.style.SUCCESS(f'Deploying contract using deployer account, '
                                                     f'address={master_copy_address} not found'))
                master_copy_address = deploy_fn(ethereum_client, deployer_account=deployer_account).contract_address
                self.stdout.write(self.style.SUCCESS(f'Contract has been deployed on {master_copy_address}'))
    def handle(self, *args, **options):
        every_contract = options['all']
        scraper = options['scraper']
        if scraper:
            etherscan_client = EtherscanClient()

        ethereum_client = EthereumClientProvider()
        network = ethereum_client.get_network()

        contract_queryset = Contract.objects.all()
        if not every_contract:
            contract_queryset = contract_queryset.filter(contract_abi=None)

        for contract in contract_queryset:
            if not scraper:
                if contract.sync_abi_from_api(network=network):
                    self.stdout.write(
                        self.style.SUCCESS(
                            f'Synced contract {contract.address} - {contract.name}'
                        ))
            else:
                try:
                    contract_info = etherscan_client.get_contract_info(
                        contract.address)
                    if contract_info:
                        contract_abi, _ = ContractAbi.objects.update_or_create(
                            abi=contract_info.abi,
                            defaults={'description': contract_info.name})
                        contract, _ = Contract.objects.update_or_create(
                            address=contract_info.abi,
                            defaults={
                                'name': contract_info.name,
                                'abi': contract_abi
                            })
                        self.stdout.write(
                            self.style.SUCCESS(
                                f'Synced contract {contract.address} - {contract.name}'
                            ))
                except EtherscanClientException:
                    time.sleep(5)
Esempio n. 4
0
    def handle(self, *args, **options):
        # We need to add `address` to the logs, so we exclude empty logs and logs already containing `address`
        ethereum_client = EthereumClientProvider()
        queryset = EthereumTx.objects.exclude(logs__0__has_key="address").exclude(
            logs=[]
        )
        total = queryset.count()
        processed = 200
        self.stdout.write(
            self.style.SUCCESS(f"Fixing ethereum logs. {total} remaining to be fixed")
        )
        while True:
            ethereum_txs = queryset[:processed]
            if not ethereum_txs:
                break

            tx_hashes = [ethereum_tx.tx_hash for ethereum_tx in ethereum_txs]
            try:
                tx_receipts = ethereum_client.get_transaction_receipts(tx_hashes)
                for ethereum_tx, tx_receipt in zip(ethereum_txs, tx_receipts):
                    ethereum_tx.logs = [
                        clean_receipt_log(log) for log in tx_receipt["logs"]
                    ]
                    ethereum_tx.save(update_fields=["logs"])
                    total -= 1

                self.stdout.write(
                    self.style.SUCCESS(
                        f"Fixed {processed} ethereum logs. {total} remaining to be fixed"
                    )
                )
            except IOError:
                self.stdout.write(
                    self.style.WARNING(
                        "Node connection error when retrieving tx receipts"
                    )
                )
        self.stdout.write(
            self.style.SUCCESS(f"End fixing txs. {total} have been fixed")
        )
Esempio n. 5
0
    def handle(self, *args, **options):
        self.stdout.write(self.style.SUCCESS("Removing old tasks"))
        PeriodicTask.objects.filter(
            task__startswith="safe_transaction_service"
        ).delete()
        self.stdout.write(self.style.SUCCESS("Old tasks were removed"))

        for task in TASKS:
            _, created = task.create_task()
            if created:
                self.stdout.write(
                    self.style.SUCCESS("Created Periodic Task %s" % task.name)
                )
            else:
                self.stdout.write(
                    self.style.SUCCESS("Task %s was already created" % task.name)
                )

        self.stdout.write(self.style.SUCCESS("Setting up Safe Contract Addresses"))
        ethereum_client = EthereumClientProvider()
        ethereum_network = ethereum_client.get_network()
        if ethereum_network in MASTER_COPIES:
            self.stdout.write(
                self.style.SUCCESS(f"Setting up {ethereum_network.name} safe addresses")
            )
            self._setup_safe_master_copies(MASTER_COPIES[ethereum_network])
        if ethereum_network in PROXY_FACTORIES:
            self.stdout.write(
                self.style.SUCCESS(
                    f"Setting up {ethereum_network.name} proxy factory addresses"
                )
            )
            self._setup_safe_proxy_factories(PROXY_FACTORIES[ethereum_network])

        if not (
            ethereum_network in MASTER_COPIES and ethereum_network in PROXY_FACTORIES
        ):
            self.stdout.write(
                self.style.WARNING("Cannot detect a valid ethereum-network")
            )
    def validate(self, data):
        super().validate(data)

        if not SafeContract.objects.filter(address=data['safe']).exists():
            raise ValidationError(
                f"Safe={data['safe']} does not exist or it's still not indexed"
            )

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier='pending')
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier='latest')

        signature = data['signature']
        delegate = data['delegate']  # Delegate address to be added

        # Tries to find a valid delegator using multiple strategies
        for operation_hash in (
                DelegateSignatureHelper.calculate_hash(delegate),
                DelegateSignatureHelper.calculate_hash(delegate,
                                                       eth_sign=True),
                DelegateSignatureHelper.calculate_hash(delegate,
                                                       previous_topt=True),
                DelegateSignatureHelper.calculate_hash(delegate,
                                                       eth_sign=True,
                                                       previous_topt=True)):
            delegator = self.check_signature(signature, operation_hash,
                                             safe_owners)
            if delegator:
                break

        if not delegator:
            raise ValidationError('Signing owner is not an owner of the Safe')

        data['delegator'] = delegator
        return data
Esempio n. 7
0
    def handle(self, *args, **options):
        tokens = options["tokens"]
        no_prompt = options["no_prompt"]
        ethereum_client = EthereumClientProvider()

        for token_address in tokens:
            token_address = ethereum_client.w3.toChecksumAddress(token_address)
            try:
                token = Token.objects.get(address=token_address)
                self.stdout.write(
                    self.style.WARNING(
                        f"Token {token.name} - {token.symbol} with address "
                        f"{token_address} already exists"))
                continue
            except Token.DoesNotExist:
                pass
            try:
                info = ethereum_client.erc20.get_info(token_address)
                if no_prompt:
                    response = "y"
                else:
                    response = (
                        input(f"Do you want to create a token {info} (y/n) "
                              ).strip().lower())
                if response == "y":
                    Token.objects.create(
                        address=token_address,
                        name=info.name,
                        symbol=info.symbol,
                        decimals=info.decimals,
                    )
                    self.stdout.write(
                        self.style.SUCCESS(
                            f"Created token {info.name} on address {token_address}"
                        ))
            except InvalidERC20Info:
                self.stdout.write(
                    self.style.ERROR(
                        f"Token with address {token_address} is not valid"))
    def handle(self, *args, **options):
        for task in self.tasks:
            _, created = task.create_task()
            if created:
                self.stdout.write(
                    self.style.SUCCESS('Created Periodic Task %s' % task.name))
            else:
                self.stdout.write(
                    self.style.SUCCESS('Task %s was already created' %
                                       task.name))

        self.stdout.write(
            self.style.SUCCESS('Setting up Safe Contract Addresses'))
        ethereum_client = EthereumClientProvider()
        ethereum_network = ethereum_client.get_network()
        if ethereum_network == EthereumNetwork.MAINNET:
            self.stdout.write(
                self.style.SUCCESS(
                    f'Setting up {ethereum_network.name} addresses'))
            self.setup_mainnet()
        elif ethereum_network == EthereumNetwork.RINKEBY:
            self.stdout.write(
                self.style.SUCCESS(
                    f'Setting up {ethereum_network.name} addresses'))
            self.setup_rinkeby()
        elif ethereum_network == EthereumNetwork.GOERLI:
            self.stdout.write(
                self.style.SUCCESS(
                    f'Setting up {ethereum_network.name} addresses'))
            self.setup_goerli()
        elif ethereum_network == EthereumNetwork.KOVAN:
            self.stdout.write(
                self.style.SUCCESS(
                    f'Setting up {ethereum_network.name} addresses'))
            self.setup_kovan()
        else:
            self.stdout.write(
                self.style.WARNING('Cannot detect a valid ethereum-network'))
    def handle(self, *args, **options):
        ethereum_client = EthereumClientProvider()
        app_name = apps.get_app_config("relay").verbose_name
        network_name = ethereum_client.get_network().name.capitalize()
        startup_message = f"Starting {app_name} version {__version__} on {network_name}"
        self.stdout.write(self.style.SUCCESS(startup_message))

        if settings.SLACK_API_WEBHOOK:
            try:
                r = requests.post(settings.SLACK_API_WEBHOOK,
                                  json={"text": startup_message})
                if r.ok:
                    self.stdout.write(
                        self.style.SUCCESS(
                            f'Slack configured, "{startup_message}" sent'))
                else:
                    raise RequestException()
            except RequestException:
                self.stdout.write(
                    self.style.ERROR("Cannot send slack notification"))
        else:
            self.stdout.write(
                self.style.SUCCESS("Slack not configured, ignoring"))
Esempio n. 10
0
    def handle(self, *args, **options):
        fix = options['fix']

        queryset = SafeStatus.objects.last_for_every_address()
        count = queryset.count()
        batch = 200
        ethereum_client = EthereumClientProvider()
        index_service = IndexServiceProvider()

        for i in range(0, count, batch):
            self.stdout.write(self.style.SUCCESS(f'Processed {i}/{count}'))
            safe_statuses = queryset[i:i + batch]
            addresses = []
            nonces = []
            for result in safe_statuses.values('address', 'nonce'):
                addresses.append(result['address'])
                nonces.append(result['nonce'])

            blockchain_nonce_payloads = self.build_nonce_payload(addresses)
            blockchain_nonces = ethereum_client.batch_call_custom(
                blockchain_nonce_payloads)

            addresses_to_reindex = []
            for address, nonce, blockchain_nonce in zip(
                    addresses, nonces, blockchain_nonces):
                if nonce != blockchain_nonce:
                    self.stdout.write(
                        self.style.WARNING(
                            f'Safe={address} stored nonce={nonce} is '
                            f'different from blockchain-nonce={blockchain_nonce}'
                        ))
                    addresses_to_reindex.append(address)

            if fix and addresses_to_reindex:
                self.stdout.write(
                    self.style.SUCCESS(f'Fixing Safes={addresses_to_reindex}'))
                index_service.reindex_addresses(addresses_to_reindex)
Esempio n. 11
0
def begin_circles_onboarding_task(self, safe_address: str) -> None:
    """
    Starts a multi-step onboarding task for Circles users which 1. funds
    deploys a Gnosis Safe for them 2. funds the deployment of their Token.
    :param safe_address: Address of the safe to-be-created
    """

    assert check_checksum(safe_address)

    redis = RedisRepository().redis
    lock_name = f'locks:begin_circles_onboarding_task:{safe_address}'
    try:
        with redis.lock(lock_name, blocking_timeout=1, timeout=LOCK_TIMEOUT):
            ethereum_client = EthereumClientProvider()

            # Do nothing if Token is already deployed
            if CirclesService(ethereum_client).is_token_deployed(safe_address):
                logger.info(
                    'Token is already deployed for {}'.format(safe_address))
                return

            logger.info(
                'No token found, start onboarding for Circles Safe {}'.format(
                    safe_address))
            # Deploy Safe when it does not exist yet
            safe_creation2 = SafeCreation2.objects.get(safe=safe_address)
            if not safe_creation2.tx_hash:
                logger.info(
                    'Safe does not exist yet, start deploying it {}'.format(
                        safe_address))
                circles_onboarding_safe_task.delay(safe_address)
            else:
                logger.info('Safe exists, we are done with safe {}'.format(
                    safe_address))
    except LockError:
        pass
Esempio n. 12
0
def check_balance_of_accounts_task() -> bool:
    """
    Checks if balance of relayer accounts (tx sender, safe funder) are less than the configured threshold
    :return: True if every account have enough ether, False otherwise
    """
    balance_warning_wei = settings.SAFE_ACCOUNTS_BALANCE_WARNING
    addresses = (
        FundingServiceProvider().funder_account.address,
        TransactionServiceProvider().tx_sender_account.address,
    )

    ethereum_client = EthereumClientProvider()
    result = True
    for address in addresses:
        balance_wei = ethereum_client.get_balance(address)
        if balance_wei <= balance_warning_wei:
            logger.error(
                "Relayer account=%s current balance=%d . Balance must be greater than %d",
                address,
                balance_wei,
                balance_warning_wei,
            )
            result = False
    return result
 def create_or_update_from_tx_hash(self, tx_hash: str) -> 'EthereumTx':
     ethereum_client = EthereumClientProvider()
     try:
         ethereum_tx = self.get(tx_hash=tx_hash)
         # For txs stored before being mined
         if ethereum_tx.block is None:
             tx_receipt = ethereum_client.get_transaction_receipt(tx_hash)
             ethereum_tx.block = EthereumBlock.objects.get_or_create_from_block_number(
                 tx_receipt['blockNumber'])
             ethereum_tx.gas_used = tx_receipt['gasUsed']
             ethereum_tx.status = tx_receipt.get('status')
             ethereum_tx.transaction_index = tx_receipt['transactionIndex']
             ethereum_tx.save(update_fields=[
                 'block', 'gas_used', 'status', 'transaction_index'
             ])
         return ethereum_tx
     except self.model.DoesNotExist:
         tx_receipt = ethereum_client.get_transaction_receipt(tx_hash)
         ethereum_block = EthereumBlock.objects.get_or_create_from_block_number(
             tx_receipt['blockNumber'])
         tx = ethereum_client.get_transaction(tx_hash)
         return self.create_from_tx_dict(tx,
                                         tx_receipt=tx_receipt,
                                         ethereum_block=ethereum_block)
    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)
        safe_tx = safe.build_multisig_tx(data['to'],
                                         data['value'],
                                         data['data'],
                                         data['operation'],
                                         data['safe_tx_gas'],
                                         data['base_gas'],
                                         data['gas_price'],
                                         data['gas_token'],
                                         data['refund_receiver'],
                                         safe_nonce=data['nonce'])
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data['contract_transaction_hash']:
            raise ValidationError(
                f'Contract-transaction-hash={contract_transaction_hash.hex()} '
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` or same `safeTxHash` for the same Safe.
        # We allow duplicated if existing tx is not executed
        multisig_transactions = MultisigTransaction.objects.filter(
            safe=safe.address, nonce=data['nonce']).executed()
        if multisig_transactions:
            for multisig_transaction in multisig_transactions:
                if multisig_transaction.safe_tx_hash == contract_transaction_hash.hex(
                ):
                    raise ValidationError(
                        f'Tx with safe-tx-hash={contract_transaction_hash.hex()} '
                        f'for safe={safe.address} was already executed in '
                        f'tx-hash={multisig_transaction.ethereum_tx_id}')

            raise ValidationError(
                f'Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} '
                f'already executed in tx-hash={multisig_transactions[0].ethereum_tx_id}'
            )

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier='pending')
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier='latest')

        data['safe_owners'] = safe_owners

        delegates = SafeContractDelegate.objects.get_delegates_for_safe(
            safe.address)
        allowed_senders = safe_owners + delegates
        if not data['sender'] in allowed_senders:
            raise ValidationError(
                f'Sender={data["sender"]} is not an owner or delegate. '
                f'Current owners={safe_owners}. Delegates={delegates}')

        signature_owners = []
        # TODO Make signature mandatory
        signature = data.get('signature', b'')
        parsed_signatures = SafeSignature.parse_signature(
            signature, contract_transaction_hash)
        data['parsed_signatures'] = parsed_signatures
        for safe_signature in parsed_signatures:
            owner = safe_signature.owner
            if not safe_signature.is_valid(ethereum_client, safe.address):
                raise ValidationError(
                    f'Signature={safe_signature.signature.hex()} for owner={owner} is not valid'
                )

            if owner in delegates and len(parsed_signatures) > 1:
                raise ValidationError(
                    f'Just one signature is expected if using delegates')
            if owner not in allowed_senders:
                raise ValidationError(
                    f'Signer={owner} is not an owner or delegate. '
                    f'Current owners={safe_owners}. Delegates={delegates}')
            if owner in signature_owners:
                raise ValidationError(
                    f'Signature for owner={owner} is duplicated')

            signature_owners.append(owner)

        # TODO Make signature mandatory. len(signature_owners) must be >= 1
        if signature_owners and data['sender'] not in signature_owners:
            raise ValidationError(
                f'Signature does not match sender={data["sender"]}. '
                f'Calculated owners={signature_owners}')

        return data
    def txs_create_or_update_from_tx_hashes(self, tx_hashes: List[Union[str, bytes]]) -> List['EthereumTx']:
        # Search first in database
        ethereum_txs_dict = OrderedDict.fromkeys([HexBytes(tx_hash).hex() for tx_hash in tx_hashes])
        db_ethereum_txs = EthereumTx.objects.filter(tx_hash__in=tx_hashes).exclude(block=None)
        for db_ethereum_tx in db_ethereum_txs:
            ethereum_txs_dict[db_ethereum_tx.tx_hash] = db_ethereum_tx

        # Retrieve from the node the txs missing from database
        tx_hashes_not_in_db = [tx_hash for tx_hash, ethereum_tx in ethereum_txs_dict.items() if not ethereum_tx]
        if not tx_hashes_not_in_db:
            return list(ethereum_txs_dict.values())

        self.ethereum_client = EthereumClientProvider()

        # Get receipts for hashes not in db
        tx_receipts = []
        for tx_hash, tx_receipt in zip(tx_hashes_not_in_db,
                                       self.ethereum_client.get_transaction_receipts(tx_hashes_not_in_db)):
            tx_receipt = tx_receipt or self.ethereum_client.get_transaction_receipt(tx_hash)  # Retry fetching if failed
            if not tx_receipt:
                raise TransactionNotFoundException(f'Cannot find tx-receipt with tx-hash={HexBytes(tx_hash).hex()}')
            elif tx_receipt.get('blockNumber') is None:
                raise TransactionWithoutBlockException(f'Cannot find blockNumber for tx-receipt with '
                                                       f'tx-hash={HexBytes(tx_hash).hex()}')
            else:
                tx_receipts.append(tx_receipt)

        # Get transactions for hashes not in db
        txs = self.ethereum_client.get_transactions(tx_hashes_not_in_db)
        block_numbers = set()
        for tx_hash, tx in zip(tx_hashes_not_in_db, txs):
            tx = tx or self.ethereum_client.get_transaction(tx_hash)  # Retry fetching if failed
            if not tx:
                raise TransactionNotFoundException(f'Cannot find tx with tx-hash={HexBytes(tx_hash).hex()}')
            elif tx.get('blockNumber') is None:
                raise TransactionWithoutBlockException(f'Cannot find blockNumber for tx with '
                                                       f'tx-hash={HexBytes(tx_hash).hex()}')
            block_numbers.add(tx['blockNumber'])

        blocks = self.ethereum_client.get_blocks(block_numbers)
        block_dict = {}
        for block_number, block in zip(block_numbers, blocks):
            block = block or self.ethereum_client.get_block(block_number)  # Retry fetching if failed
            if not block:
                raise BlockNotFoundException(f'Block with number={block_number} was not found')
            assert block_number == block['number']
            block_dict[block['number']] = block

        # Create new transactions or update them if they have no receipt
        current_block_number = self.ethereum_client.current_block_number
        for tx, tx_receipt in zip(txs, tx_receipts):
            block = block_dict.get(tx['blockNumber'])
            confirmed = (current_block_number - block['number']) >= self.eth_reorg_blocks
            ethereum_block: EthereumBlock = EthereumBlock.objects.get_or_create_from_block(block, confirmed=confirmed)
            if HexBytes(ethereum_block.block_hash) != block['hash']:
                raise EthereumBlockHashMismatch(f'Stored block={ethereum_block.number} '
                                                f'with hash={ethereum_block.block_hash} '
                                                f'is not marching retrieved hash={block["hash"].hex()}')
            try:
                ethereum_tx = EthereumTx.objects.get(tx_hash=tx['hash'])
                # For txs stored before being mined
                ethereum_tx.update_with_block_and_receipt(ethereum_block, tx_receipt)
                ethereum_txs_dict[ethereum_tx.tx_hash] = ethereum_tx
            except EthereumTx.DoesNotExist:
                ethereum_tx = EthereumTx.objects.create_from_tx_dict(tx,
                                                                     tx_receipt=tx_receipt,
                                                                     ethereum_block=ethereum_block)
                ethereum_txs_dict[HexBytes(ethereum_tx.tx_hash).hex()] = ethereum_tx
        return list(ethereum_txs_dict.values())
Esempio n. 16
0
    def create_from_blockchain(
            self, token_address: ChecksumAddress) -> Optional["Token"]:
        ethereum_client = EthereumClientProvider()
        if token_address in ENS_CONTRACTS_WITH_TLD:  # Special case for ENS
            return self.create(
                address=token_address,
                name="Ethereum Name Service",
                symbol="ENS",
                logo="tokens/logos/ENS.png",
                decimals=None,
                trusted=True,
            )
        try:
            logger.debug("Querying blockchain for info for erc20 token=%s",
                         token_address)
            erc_info = ethereum_client.erc20.get_info(token_address)
            decimals = erc_info.decimals
        except InvalidERC20Info:
            logger.debug(
                "Erc20 token not found, querying blockchain for info for erc721 token=%s",
                token_address,
            )
            try:
                erc_info = ethereum_client.erc721.get_info(token_address)
                # Make sure ERC721 is not indexed as an ERC20 for a node misbehaving
                try:
                    decimals = ethereum_client.erc20.get_decimals(
                        token_address)
                except (ValueError, DecodingError, BadFunctionCallOutput):
                    decimals = None
            except InvalidERC721Info:
                logger.debug("Cannot find anything on blockchain for token=%s",
                             token_address)
                return None

        # Ignore tokens with empty name or symbol
        if not erc_info.name or not erc_info.symbol:
            logger.warning("Token with address=%s has not name or symbol",
                           token_address)
            return None

        name_and_symbol: List[str] = []
        for text in (erc_info.name, erc_info.symbol):
            if isinstance(text, str):
                text = text.encode()
            name_and_symbol.append(
                text.decode("utf-8",
                            errors="replace").replace("\x00", "\uFFFD"))

        name, symbol = name_and_symbol
        # If symbol is way bigger than name (by 5 characters), swap them (e.g. POAP)
        if (len(name) - len(symbol)) < -5:
            name, symbol = symbol, name

        try:
            return self.create(address=token_address,
                               name=name,
                               symbol=symbol,
                               decimals=decimals)
        except ValueError:
            logger.error(
                "Problem creating token with address=%s name=%s symbol=%s decimals=%s",
                token_address,
                name,
                symbol,
                decimals,
            )
            return None
    def __new__(cls):
        if not hasattr(cls, 'instance'):
            cls.instance = SafeService(EthereumClientProvider())

        return cls.instance
    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)
        safe_tx = safe.build_multisig_tx(data['to'],
                                         data['value'],
                                         data['data'],
                                         data['operation'],
                                         data['safe_tx_gas'],
                                         data['base_gas'],
                                         data['gas_price'],
                                         data['gas_token'],
                                         data['refund_receiver'],
                                         safe_nonce=data['nonce'])
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data['contract_transaction_hash']:
            raise ValidationError(
                f'Contract-transaction-hash={contract_transaction_hash.hex()} '
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` for the same Safe.
        # We allow duplicated if existing tx is not executed
        try:
            multisig_transaction: MultisigTransaction = MultisigTransaction.objects.exclude(
                ethereum_tx=None).exclude(
                    safe_tx_hash=contract_transaction_hash).get(
                        safe=safe.address, nonce=data['nonce'])
            if multisig_transaction.safe_tx_hash != contract_transaction_hash:
                raise ValidationError(
                    f'Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} already executed in '
                    f'tx-hash={multisig_transaction.ethereum_tx_id}')
        except MultisigTransaction.DoesNotExist:
            pass

        # Check owners and old owners, owner might be removed but that tx can still be signed by that owner
        if not safe.retrieve_is_owner(data['sender']):
            try:
                # TODO Fix this, we can use SafeStatus now
                if not safe.retrieve_is_owner(
                        data['sender'],
                        block_identifier=max(
                            0, ethereum_client.current_block_number - 20)):
                    raise ValidationError('User is not an owner')
            except BadFunctionCallOutput:  # If it didn't exist 20 blocks ago
                raise ValidationError('User is not an owner')

        signature = data.get('signature')
        if signature is not None:
            #  TODO Support signatures with multiple owners
            if len(signature) != 65:
                raise ValidationError(
                    'Signatures with more than one owner still not supported')

            safe_signature = SafeSignature(signature,
                                           contract_transaction_hash)
            #  TODO Support contract signatures and approved hashes
            if safe_signature.signature_type == SafeSignatureType.CONTRACT_SIGNATURE:
                raise ValidationError('Contract signatures not supported')
            elif safe_signature.signature_type == SafeSignatureType.APPROVED_HASH:
                # Index it automatically later
                del data['signature']

            address = safe_signature.owner
            if address != data['sender']:
                raise ValidationError(
                    f'Signature does not match sender={data["sender"]}. Calculated owner={address}'
                )

        return data
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.nonce_fn = Safe(
         NULL_ADDRESS,
         EthereumClientProvider()).get_contract().functions.nonce()
    def create_or_update_from_tx_hashes(
            self, tx_hashes: List[Union[str, bytes]]) -> List['EthereumTx']:
        # Search first in database
        ethereum_txs_dict = OrderedDict.fromkeys(
            [HexBytes(tx_hash).hex() for tx_hash in tx_hashes])
        db_ethereum_txs = self.filter(tx_hash__in=tx_hashes).exclude(
            block=None)
        for db_ethereum_tx in db_ethereum_txs:
            ethereum_txs_dict[db_ethereum_tx.tx_hash] = db_ethereum_tx

        # Retrieve from the node the txs missing from database
        tx_hashes_not_in_db = [
            tx_hash for tx_hash, ethereum_tx in ethereum_txs_dict.items()
            if not ethereum_tx
        ]
        if not tx_hashes_not_in_db:
            return list(ethereum_txs_dict.values())

        ethereum_client = EthereumClientProvider()

        tx_receipts = []
        for tx_hash, tx_receipt in zip(
                tx_hashes_not_in_db,
                ethereum_client.get_transaction_receipts(tx_hashes_not_in_db)):
            if not tx_receipt:
                tx_receipt = ethereum_client.get_transaction_receipt(
                    tx_hash)  # Retry fetching

            if not tx_receipt:
                raise TransactionNotFoundException(
                    f'Cannot find tx with tx-hash={HexBytes(tx_hash).hex()}')
            elif tx_receipt.get('blockNumber') is None:
                raise TransactionWithoutBlockException(
                    f'Cannot find block for tx with tx-hash={HexBytes(tx_hash).hex()}'
                )
            else:
                tx_receipts.append(tx_receipt)

        txs = ethereum_client.get_transactions(tx_hashes_not_in_db)
        block_numbers = []
        for tx_hash, tx in zip(tx_hashes_not_in_db, txs):
            if not tx:
                raise TransactionNotFoundException(
                    f'Cannot find tx with tx-hash={HexBytes(tx_hash).hex()}')
            elif tx.get('blockNumber') is None:
                raise TransactionWithoutBlockException(
                    f'Cannot find block for tx with tx-hash={HexBytes(tx_hash).hex()}'
                )
            block_numbers.append(tx['blockNumber'])

        blocks = ethereum_client.get_blocks(block_numbers)

        current_block_number = ethereum_client.current_block_number
        for tx, tx_receipt, block in zip(txs, tx_receipts, blocks):
            ethereum_block = EthereumBlock.objects.get_or_create_from_block(
                block, current_block_number=current_block_number)
            try:
                ethereum_tx = self.get(tx_hash=tx['hash'])
                # For txs stored before being mined
                if ethereum_tx.block is None:
                    ethereum_tx.block = ethereum_block
                    ethereum_tx.gas_used = tx_receipt['gasUsed']
                    ethereum_tx.status = tx_receipt.get('status')
                    ethereum_tx.transaction_index = tx_receipt[
                        'transactionIndex']
                    ethereum_tx.save(update_fields=[
                        'block', 'gas_used', 'status', 'transaction_index'
                    ])
                ethereum_txs_dict[HexBytes(
                    ethereum_tx.tx_hash).hex()] = ethereum_tx
            except self.model.DoesNotExist:
                ethereum_tx = self.create_from_tx_dict(
                    tx, tx_receipt=tx_receipt, ethereum_block=ethereum_block)
                ethereum_txs_dict[HexBytes(
                    ethereum_tx.tx_hash).hex()] = ethereum_tx
        return list(ethereum_txs_dict.values())
 def __new__(cls):
     if not hasattr(cls, 'instance'):
         cls.instance = PriceService(EthereumClientProvider(), get_redis())
     return cls.instance
Esempio n. 22
0
 def __init__(self):
     self.gas_price = 1  #gas price when paid in circles token
     self.ethereum_client = EthereumClientProvider()
Esempio n. 23
0
def circles_onboarding_safe_task(self, safe_address: str) -> None:
    """
    Check if create2 Safe has enough incoming trust connections to fund and
    deploy it
    :param safe_address: Address of the safe to-be-created
    """

    assert check_checksum(safe_address)

    try:
        redis = RedisRepository().redis
        lock_name = f'locks:circles_onboarding_safe_task:{safe_address}'
        with redis.lock(lock_name, blocking_timeout=1, timeout=LOCK_TIMEOUT):
            logger.info('Check deploying Safe .. {}'.format(safe_address))
            try:
                SafeCreationServiceProvider().deploy_create2_safe_tx(
                    safe_address)
            except SafeCreation2.DoesNotExist:
                pass
            except NotEnoughFundingForCreation:
                logger.info('Safe does not have enough fund for deployment, '
                            'check trust connections {}'.format(safe_address))
                # If we have enough trust connections, fund safe
                if GraphQLService().check_trust_connections(safe_address):
                    logger.info(
                        'Fund Safe deployment for {}'.format(safe_address))
                    ethereum_client = EthereumClientProvider()
                    safe_creation = SafeCreation2.objects.get(
                        safe=safe_address)
                    # Estimate costs of safe creation
                    safe_deploy_cost = safe_creation.wei_estimated_deploy_cost(
                    )
                    logger.info('Estimating %d for safe creation',
                                safe_deploy_cost)
                    # Estimate costs of token creation
                    transaction_service = TransactionServiceProvider()
                    token_deploy_cost = transaction_service.estimate_circles_signup_tx(
                        safe_address)
                    logger.info('Estimating %d for token deployment',
                                token_deploy_cost)
                    # Find total onboarding costs
                    payment = safe_deploy_cost + token_deploy_cost
                    # Get current safe balance
                    safe_balance = ethereum_client.get_balance(safe_address)
                    logger.info(
                        'Found %d balance for token deployment of safe=%s. Required=%d',
                        safe_balance, safe_address, payment)
                    if safe_balance >= payment:
                        logger.info('Onboarding is already funded {}'.format(
                            safe_address))
                        return

                    FundingServiceProvider().send_eth_to(safe_address,
                                                         payment,
                                                         gas=24000)
                    # Retry later to check for enough funding and successful deployment
                    raise self.retry(countdown=30)
                else:
                    logger.info(
                        'Not enough trust connections for funding deployment {}'
                        .format(safe_address))
    except LockError:
        pass
Esempio n. 24
0
def deploy_safes_task(retry: bool = True) -> None:
    """
    Deploy pending safes (deployer funded and tx-hash checked). Then raw creation tx is sent to the ethereum network.
    If something goes wrong (maybe a reorg), `deployer_funded` will be set False again and `check_deployer_funded_task`
    is called again.
    :param retry: if True, retries are allowed, otherwise don't retry
    """
    try:
        redis = RedisRepository().redis
        with redis.lock("tasks:deploy_safes_task", blocking_timeout=1, timeout=LOCK_TIMEOUT):
            ethereum_client = EthereumClientProvider()
            logger.debug('Starting deploy safes task')
            pending_to_deploy = SafeFunding.objects.pending_just_to_deploy()
            logger.debug('%d safes pending to deploy', len(pending_to_deploy))

            for safe_funding in pending_to_deploy:
                safe_contract = safe_funding.safe
                safe_address = safe_contract.address
                safe_creation = SafeCreation.objects.get(safe=safe_contract)
                safe_deployed_tx_hash = safe_funding.safe_deployed_tx_hash

                if not safe_deployed_tx_hash:
                    # Deploy the Safe
                    try:
                        creation_tx_hash = ethereum_client.send_raw_transaction(safe_creation.signed_tx)
                        if creation_tx_hash:
                            creation_tx_hash = creation_tx_hash.hex()
                            logger.info('Safe=%s creation tx has just been sent to the network with tx-hash=%s',
                                        safe_address, creation_tx_hash)
                            safe_funding.safe_deployed_tx_hash = creation_tx_hash
                            safe_funding.save()
                    except TransactionAlreadyImported:
                        logger.warning("Safe=%s transaction was already imported by the node", safe_address)
                        safe_funding.safe_deployed_tx_hash = safe_creation.tx_hash
                        safe_funding.save()
                    except ValueError:
                        # Usually "ValueError: {'code': -32000, 'message': 'insufficient funds for gas*price+value'}"
                        # A reorg happened
                        logger.warning("Safe=%s was affected by reorg, let's check again receipt for tx-hash=%s",
                                       safe_address, safe_funding.deployer_funded_tx_hash, exc_info=True)
                        safe_funding.deployer_funded = False
                        safe_funding.save()
                        check_deployer_funded_task.apply_async((safe_address,), {'retry': retry}, countdown=20)
                else:
                    # Check if safe proxy deploy transaction has already been sent to the network
                    logger.debug('Safe=%s creation tx has already been sent to the network with tx-hash=%s',
                                 safe_address, safe_deployed_tx_hash)

                    if ethereum_client.check_tx_with_confirmations(safe_deployed_tx_hash,
                                                                   settings.SAFE_FUNDING_CONFIRMATIONS):
                        logger.info('Safe=%s was deployed', safe_funding.safe.address)
                        safe_funding.safe_deployed = True
                        safe_funding.save()
                        # Send creation notification
                        send_create_notification.delay(safe_address, safe_creation.owners)
                    elif (safe_funding.modified + timedelta(minutes=10) < timezone.now()
                          and not ethereum_client.get_transaction_receipt(safe_deployed_tx_hash)):
                        # A reorg happened
                        logger.warning('Safe=%s deploy tx=%s was not found after 10 minutes. Trying deploying again...',
                                       safe_funding.safe.address, safe_deployed_tx_hash)
                        safe_funding.safe_deployed_tx_hash = None
                        safe_funding.save()
    except LockError:
        pass
Esempio n. 25
0
def fund_deployer_task(self, safe_address: str, retry: bool = True) -> None:
    """
    Check if user has sent enough ether or tokens to the safe account
    If every condition is met ether is sent to the deployer address and `check_deployer_funded_task`
    is called to check that that tx is mined
    If everything goes well in SafeFunding `safe_funded=True` and `deployer_funded_tx_hash=tx_hash` are set
    :param safe_address: safe account
    :param retry: if True, retries are allowed, otherwise don't retry
    """

    safe_contract = SafeContract.objects.get(address=safe_address)
    try:
        safe_creation = SafeCreation.objects.get(safe=safe_address)
    except SafeCreation.DoesNotExist:
        deploy_create2_safe_task.delay(safe_address)
        return

    deployer_address = safe_creation.deployer
    payment = safe_creation.payment

    # These asserts just to make sure we are not wasting money
    assert check_checksum(safe_address)
    assert check_checksum(deployer_address)
    assert checksum_encode(mk_contract_address(sender=deployer_address, nonce=0)) == safe_address
    assert payment > 0

    redis = RedisRepository().redis
    with redis.lock('locks:fund_deployer_task', timeout=LOCK_TIMEOUT):
        ethereum_client = EthereumClientProvider()
        safe_funding, _ = SafeFunding.objects.get_or_create(safe=safe_contract)

        # Nothing to do if everything is funded and mined
        if safe_funding.is_all_funded():
            logger.debug('Nothing to do here for safe %s. Is all funded', safe_address)
            return

        # If receipt exists already, let's check
        if safe_funding.deployer_funded_tx_hash and not safe_funding.deployer_funded:
            logger.debug('Safe %s deployer has already been funded. Checking tx_hash %s',
                         safe_address,
                         safe_funding.deployer_funded_tx_hash)
            check_deployer_funded_task.delay(safe_address)
        elif not safe_funding.deployer_funded:
            confirmations = settings.SAFE_FUNDING_CONFIRMATIONS
            last_block_number = ethereum_client.current_block_number

            assert (last_block_number - confirmations) > 0

            if safe_creation.payment_token and safe_creation.payment_token != NULL_ADDRESS:
                safe_balance = ethereum_client.erc20.get_balance(safe_address, safe_creation.payment_token)
            else:
                safe_balance = ethereum_client.get_balance(safe_address, last_block_number - confirmations)

            if safe_balance >= payment:
                logger.info('Found %d balance for safe=%s', safe_balance, safe_address)
                safe_funding.safe_funded = True
                safe_funding.save()

                # Check deployer has no eth. This should never happen
                balance = ethereum_client.get_balance(deployer_address)
                if balance:
                    logger.error('Deployer=%s for safe=%s has eth already (%d wei)',
                                 deployer_address, safe_address, balance)
                else:
                    logger.info('Safe=%s. Transferring deployment-cost=%d to deployer=%s',
                                safe_address, safe_creation.wei_deploy_cost(), deployer_address)
                    tx_hash = FundingServiceProvider().send_eth_to(deployer_address,
                                                                   safe_creation.wei_deploy_cost(),
                                                                   gas_price=safe_creation.gas_price,
                                                                   retry=True)
                    if tx_hash:
                        tx_hash = tx_hash.hex()
                        logger.info('Safe=%s. Transferred deployment-cost=%d to deployer=%s with tx-hash=%s',
                                    safe_address, safe_creation.wei_deploy_cost(), deployer_address, tx_hash)
                        safe_funding.deployer_funded_tx_hash = tx_hash
                        safe_funding.save()
                        logger.debug('Safe=%s deployer has just been funded. tx_hash=%s', safe_address, tx_hash)
                        check_deployer_funded_task.apply_async((safe_address,), countdown=20)
                    else:
                        logger.error('Cannot send payment=%d to deployer safe=%s', payment, deployer_address)
                        if retry:
                            raise self.retry(countdown=30)
            else:
                logger.info('Not found required balance=%d for safe=%s', payment, safe_address)
                if retry:
                    raise self.retry(countdown=30)
Esempio n. 26
0
 def __new__(cls):
     if not hasattr(cls, "instance"):
         cls.instance = TransactionService(EthereumClientProvider(),
                                           get_redis())
     return cls.instance
Esempio n. 27
0
 def __new__(cls):
     if not hasattr(cls, "instance"):
         cls.instance = StatsService(EthereumClientProvider(),
                                     GasStationProvider())
     return cls.instance
Esempio n. 28
0
def check_deployer_funded_task(self,
                               safe_address: str,
                               retry: bool = True) -> None:
    """
    Check the `deployer_funded_tx_hash`. If receipt can be retrieved, in SafeFunding `deployer_funded=True`.
    If not, after the number of retries `deployer_funded_tx_hash=None`
    :param safe_address: safe account
    :param retry: if True, retries are allowed, otherwise don't retry
    """
    try:
        redis = RedisRepository().redis
        with redis.lock(f"tasks:check_deployer_funded_task:{safe_address}",
                        blocking_timeout=1,
                        timeout=LOCK_TIMEOUT):
            ethereum_client = EthereumClientProvider()
            logger.debug('Starting check deployer funded task for safe=%s',
                         safe_address)
            safe_funding = SafeFunding.objects.get(safe=safe_address)
            deployer_funded_tx_hash = safe_funding.deployer_funded_tx_hash

            if safe_funding.deployer_funded:
                logger.warning('Tx-hash=%s for safe %s is already checked',
                               deployer_funded_tx_hash, safe_address)
                return
            elif not deployer_funded_tx_hash:
                logger.error('No deployer_funded_tx_hash for safe=%s',
                             safe_address)
                return

            logger.debug('Checking safe=%s deployer tx-hash=%s', safe_address,
                         deployer_funded_tx_hash)
            if ethereum_client.get_transaction_receipt(
                    deployer_funded_tx_hash):
                logger.info(
                    'Found transaction to deployer of safe=%s with receipt=%s',
                    safe_address, deployer_funded_tx_hash)
                safe_funding.deployer_funded = True
                safe_funding.save()
            else:
                logger.debug('Not found transaction receipt for tx-hash=%s',
                             deployer_funded_tx_hash)
                # If no more retries
                if not retry or (self.request.retries == self.max_retries):
                    safe_creation = SafeCreation.objects.get(safe=safe_address)
                    balance = ethereum_client.get_balance(
                        safe_creation.deployer)
                    if balance >= safe_creation.wei_deploy_cost():
                        logger.warning(
                            'Safe=%s. Deployer=%s. Cannot find transaction receipt with tx-hash=%s, '
                            'but balance is there. This should never happen',
                            safe_address, safe_creation.deployer,
                            deployer_funded_tx_hash)
                        safe_funding.deployer_funded = True
                        safe_funding.save()
                    else:
                        logger.error(
                            'Safe=%s. Deployer=%s. Transaction receipt with tx-hash=%s not mined after %d '
                            'retries. Setting `deployer_funded_tx_hash` back to `None`',
                            safe_address, safe_creation.deployer,
                            deployer_funded_tx_hash, self.request.retries)
                        safe_funding.deployer_funded_tx_hash = None
                        safe_funding.save()
                else:
                    logger.debug('Retry finding transaction receipt %s',
                                 deployer_funded_tx_hash)
                    if retry:
                        raise self.retry(countdown=self.request.retries * 10 +
                                         15)  # More countdown every retry
    except LockError:
        logger.info('check_deployer_funded_task is locked for safe=%s',
                    safe_address)
 def __new__(cls):
     if not hasattr(cls, 'instance'):
         from django.conf import settings
         cls.instance = ReorgService(EthereumClientProvider(),
                                     settings.ETH_REORG_BLOCKS)
     return cls.instance
Esempio n. 30
0
    def __new__(cls):
        if not hasattr(cls, "instance"):
            cls.instance = CollectiblesService(EthereumClientProvider(),
                                               get_redis())

        return cls.instance