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 circles_onboarding_organization_signup_task(safe_address: str) -> None: """ Check if Organization Safe is already registered in the Hub, if not, fund it :param safe_address: Address of the created safe """ assert check_checksum(safe_address) # Additional funds for organization deployments (it should at least cover # one `trust` method call) next to the `organizationSignup` method ADDITIONAL_START_FUNDS = 100000000000000 try: redis = RedisRepository().redis lock_name = f'locks:circles_onboarding_organization_signup_task:{safe_address}' with redis.lock(lock_name, blocking_timeout=1, timeout=LOCK_TIMEOUT): logger.info( 'Fund organizationSignup task for {}'.format(safe_address)) ethereum_client = EthereumClientProvider() # Do nothing if account already exists in Hub if CirclesService(ethereum_client).is_organization_deployed( safe_address): logger.info('Organization is already deployed for {}'.format( safe_address)) return # Do nothing if the signup is already funded transaction_service = TransactionServiceProvider() # Sum `organizationSignup` and additional `trust` transactions # costs as the organization needs to trust at least one user in the # beginning to receive more funds payment = transaction_service.estimate_circles_organization_signup_tx( safe_address) + ADDITIONAL_START_FUNDS safe_balance = ethereum_client.get_balance(safe_address) logger.info( 'Found %d balance for organization deployment of safe=%s. Required=%d', safe_balance, safe_address, payment) if safe_balance >= payment: logger.info( 'Organization is already funded {}'.format(safe_address)) return # Otherwise fund deployment logger.info('Fund Organization {}'.format(safe_address)) FundingServiceProvider().send_eth_to(safe_address, payment - safe_balance, gas=30000, retry=True) except LockError: pass
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 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"))
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)
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