diff --git a/operate/cli.py b/operate/cli.py index 3cebfb38d..3cf562455 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -868,9 +868,7 @@ async def _withdraw_onchain(request: Request) -> JSONResponse: if balance == 0: logger.info(f"No OLAS to drain from master safe: {safe}") else: - logger.info( - f"Draining {balance} OLAS out of master safe: {safe}" - ) + logger.info(f"Draining {balance} OLAS out of master safe: {safe}") transfer_erc20_from_safe( ledger_api=ledger_api, crypto=master_wallet.crypto, @@ -885,9 +883,7 @@ async def _withdraw_onchain(request: Request) -> JSONResponse: if balance == 0: logger.info(f"No xDAI to drain from master safe: {safe}") else: - logger.info( - f"Draining {balance} xDAI out of master safe: {safe}" - ) + logger.info(f"Draining {balance} xDAI out of master safe: {safe}") master_wallet.transfer( to=withdrawal_address, amount=balance, diff --git a/operate/ledger/profiles.py b/operate/ledger/profiles.py index 911584c95..5e811e348 100644 --- a/operate/ledger/profiles.py +++ b/operate/ledger/profiles.py @@ -126,4 +126,4 @@ WXDAI = { Chain.GNOSIS: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", -} \ No newline at end of file +} diff --git a/operate/services/deployment_runner.py b/operate/services/deployment_runner.py index 51b6b9f24..d9c77c1ed 100644 --- a/operate/services/deployment_runner.py +++ b/operate/services/deployment_runner.py @@ -73,7 +73,7 @@ def _kill_process(pid: int) -> None: except OSError: return except psutil.AccessDenied: - return + return time.sleep(1) diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 4092c60ff..fe3c61f92 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -51,6 +51,10 @@ from operate.utils.gnosis import transfer_erc20_from_safe +# TODO Organize exceptions definition +class InsufficientFundsException(Exception): + pass + class MasterWallet(LocalResource): """Master wallet.""" @@ -249,6 +253,16 @@ def _transfer_erc20_from_safe( amount=amount, ) + def _transfer_erc20_from_eoa( + self, + token: str, + to: str, + amount: int, + chain: Chain, + rpc: t.Optional[str] = None, + ) -> None: + raise NotImplementedError() + def transfer( self, to: str, @@ -258,6 +272,22 @@ def transfer( rpc: t.Optional[str] = None, ) -> None: """Transfer funds to the given account.""" + if from_safe: + sender = t.cast(str, self.safes[chain]) + sender_str = f"Safe {sender}" + else: + sender = self.crypto.address + sender_str = f"EOA {sender}" + + ledger_api = self.ledger_api(chain=chain, rpc=rpc) + balance = ledger_api.get_balance(address=sender) + + if balance < amount: + raise InsufficientFundsException( + f"Cannot transfer {amount} native units from {sender_str} to {to} on chain {chain.value.capitalize()}. " + f"Balance of {sender_str} is {balance} native units on chain {chain.value.capitalize()}." + ) + if from_safe: return self._transfer_from_safe( to=to, @@ -283,15 +313,48 @@ def transfer_erc20( rpc: t.Optional[str] = None, ) -> None: """Transfer funds to the given account.""" - if not from_safe: - raise NotImplementedError() - return self._transfer_erc20_from_safe( + + if from_safe: + sender = t.cast(str, self.safes[chain]) + sender_str = f"Safe {sender}" + else: + sender = self.crypto.address + sender_str = f"EOA {sender}" + + ledger_api = self.ledger_api(chain=chain, rpc=rpc) + balance = ( + registry_contracts.erc20.get_instance( + ledger_api=ledger_api, + contract_address=token, + ) + .functions.balanceOf(sender) + .call() + ) + + tokens = {OLAS[chain]: "OLAS", USDC[chain]: "USDC"} + token_name = tokens.get(token, token) + + if balance < amount: + raise InsufficientFundsException( + f"Cannot transfer {amount} {token_name} from {sender_str} to {to} on chain {chain.value.capitalize()}. " + f"Balance of {sender_str} is {balance} {token_name} on chain {chain.value.capitalize()}." + ) + + if from_safe: + return self._transfer_erc20_from_safe( + token=token, + to=to, + amount=amount, + chain=chain, + rpc=rpc, + ) + return self._transfer_erc20_from_eoa( token=token, to=to, amount=amount, chain=chain, rpc=rpc, - ) + ) @classmethod def new( @@ -443,7 +506,7 @@ def extended_json(self) -> t.Dict: wallet_json["consistent_backup_owner"] = len(owner_sets) == 1 wallet_json["consistent_backup_owner_count"] = all( len(owner) == 1 for owner in owner_sets - ) or all(len(owner) == 0 for owner in owner_sets) + ) return wallet_json @classmethod