def __init__(self): if history: self.session = PromptSession( history=FileHistory(os.path.join(sys.path[0], '.history'))) else: self.session = PromptSession() self.safe_operator = SafeOperator(safe_address, node_url) self.prompt_parser = PromptParser(self.safe_operator)
class SafeCli: def __init__(self): if history: self.session = PromptSession( history=FileHistory(os.path.join(sys.path[0], '.history'))) else: self.session = PromptSession() self.safe_operator = SafeOperator(safe_address, node_url) self.prompt_parser = PromptParser(self.safe_operator) def print_startup_info(self): print_formatted_text( pyfiglet.figlet_format('Gnosis Safe CLI')) # Print fancy text print_formatted_text( HTML('<b><ansigreen>Loading Safe information...</ansigreen></b>')) self.safe_operator.print_info() def get_prompt_text(self): return HTML( f'<bold><ansiblue>{safe_address}</ansiblue><ansired> > </ansired></bold>' ) def get_bottom_toolbar(self): return HTML( f'<b><style fg="ansiyellow">network={self.safe_operator.network.name} ' f'{self.safe_operator.safe_cli_info}</style></b>') def loop(self): while True: try: command = self.session.prompt( self.get_prompt_text, auto_suggest=AutoSuggestFromHistory(), bottom_toolbar=self.get_bottom_toolbar, lexer=PygmentsLexer(SafeLexer), completer=SafeCompleter()) if not command.strip(): continue self.prompt_parser.process_command(command) except EOFError: break except KeyboardInterrupt: continue except (argparse.ArgumentError, argparse.ArgumentTypeError, SystemExit): pass
def setup_operator(self, number_owners: int = 1) -> SafeOperator: assert number_owners >= 1, 'Number of owners cannot be less than 1!' safe_address = self.deploy_test_safe(owners=[self.ethereum_test_account.address]).safe_address safe_operator = SafeOperator(safe_address, self.ethereum_node_url) safe_operator.load_cli_owners([self.ethereum_test_account.key.hex()]) for _ in range(number_owners - 1): account = Account.create() safe_operator.add_owner(account.address) safe_operator.load_cli_owners([account.key.hex()]) return safe_operator
def test_add_owner(self): safe_address = self.deploy_test_safe( owners=[self.ethereum_test_account.address]).safe_address safe_operator = SafeOperator(safe_address, self.ethereum_node_url) with self.assertRaises(ExistingOwnerException): safe_operator.add_owner(self.ethereum_test_account.address) new_owner = Account.create().address with self.assertRaises(SenderRequiredException): safe_operator.add_owner(new_owner) safe_operator.default_sender = self.ethereum_test_account with self.assertRaises(NotEnoughSignatures): safe_operator.add_owner(new_owner) safe_operator.accounts.add(self.ethereum_test_account) safe = Safe(safe_address, self.ethereum_client) self.assertTrue(safe_operator.add_owner(new_owner)) self.assertIn(self.ethereum_test_account, safe_operator.accounts) self.assertIn(new_owner, safe.retrieve_owners())
def test_approve_hash(self): safe_address = self.deploy_test_safe( owners=[self.ethereum_test_account.address]).safe_address safe_operator = SafeOperator(safe_address, self.ethereum_node_url) safe_tx_hash = Web3.keccak(text='random-test') random_account = Account.create() with self.assertRaises(AccountNotLoadedException): safe_operator.approve_hash(safe_tx_hash, random_account.address) with self.assertRaises(NonExistingOwnerException): safe_operator.accounts.add(random_account) safe_operator.approve_hash(safe_tx_hash, random_account.address) safe_operator.accounts.add(self.ethereum_test_account) safe_operator.default_sender = self.ethereum_test_account self.assertTrue( safe_operator.approve_hash(safe_tx_hash, self.ethereum_test_account.address)) with self.assertRaises(HashAlreadyApproved): safe_operator.approve_hash(safe_tx_hash, self.ethereum_test_account.address)
def test_remove_owner(self): safe_address = self.deploy_test_safe( owners=[self.ethereum_test_account.address]).safe_address safe_operator = SafeOperator(safe_address, self.ethereum_node_url) random_address = Account.create().address with self.assertRaises(NonExistingOwnerException): safe_operator.remove_owner(random_address) safe_operator.load_cli_owners([self.ethereum_test_account.key.hex()]) new_owner = Account.create().address safe = Safe(safe_address, self.ethereum_client) self.assertTrue(safe_operator.add_owner(new_owner)) self.assertIn(new_owner, safe.retrieve_owners()) self.assertTrue(safe_operator.remove_owner(new_owner)) self.assertNotIn(new_owner, safe_operator.accounts) self.assertNotIn(new_owner, safe.retrieve_owners())
def test_safe_cli_happy_path(self): accounts = [self.ethereum_test_account, Account.create()] account_addresses = [account.address for account in accounts] safe_address = self.deploy_test_safe(owners=account_addresses, threshold=2, initial_funding_wei=self.w3.toWei(1, 'ether')).safe_address safe = Safe(safe_address, self.ethereum_client) safe_operator = SafeOperator(safe_address, self.ethereum_node_url) prompt_parser = PromptParser(safe_operator) random_address = Account.create().address self.assertEqual(safe_operator.accounts, set()) prompt_parser.process_command(f'load_cli_owners {self.ethereum_test_account.key.hex()}') self.assertEqual(safe_operator.default_sender, self.ethereum_test_account) self.assertEqual(safe_operator.accounts, {self.ethereum_test_account}) prompt_parser.process_command(f'send_ether {random_address} 1') # No enough signatures self.assertEqual(self.ethereum_client.get_balance(random_address), 0) value = 123 prompt_parser.process_command(f'load_cli_owners {accounts[1].key.hex()}') prompt_parser.process_command(f'send_ether {random_address} {value}') self.assertEqual(self.ethereum_client.get_balance(random_address), value) # Change threshold self.assertEqual(safe_operator.safe_cli_info.threshold, 2) self.assertEqual(safe.retrieve_threshold(), 2) prompt_parser.process_command('change_threshold 1') self.assertEqual(safe_operator.safe_cli_info.threshold, 1) self.assertEqual(safe.retrieve_threshold(), 1) # Approve Hash safe_tx_hash = Web3.keccak(text='hola') self.assertFalse(safe_operator.safe.retrieve_is_hash_approved(accounts[0].address, safe_tx_hash)) prompt_parser.process_command(f'approve_hash {safe_tx_hash.hex()} {accounts[0].address}') self.assertTrue(safe_operator.safe.retrieve_is_hash_approved(accounts[0].address, safe_tx_hash)) # Remove owner self.assertEqual(len(safe_operator.safe_cli_info.owners), 2) self.assertEqual(len(safe.retrieve_owners()), 2) prompt_parser.process_command(f'remove_owner {accounts[1].address}') self.assertEqual(safe_operator.safe_cli_info.owners, [self.ethereum_test_account.address]) self.assertEqual(safe.retrieve_owners(), [self.ethereum_test_account.address])
from safe_cli.safe_completer import SafeCompleter from safe_cli.safe_lexer import SafeLexer from safe_cli.safe_operator import SafeOperator parser = argparse.ArgumentParser() parser.add_argument('safe_address', help='Address of Safe to use') parser.add_argument('node_url', help='Ethereum node url') args = parser.parse_args() safe_address = args.safe_address node_url = args.node_url session = PromptSession() if __name__ == '__main__': safe_operator = SafeOperator(safe_address, node_url) print_formatted_text( pyfiglet.figlet_format('Gnosis Safe CLI')) # Print fancy text print_formatted_text( HTML(f'<b><ansigreen>Loading Safe information...</ansigreen></b>')) safe_operator.print_info() prompt_parser = PromptParser(safe_operator) def get_prompt_text(): return HTML( f'<bold><ansiblue>{safe_address}</ansiblue><ansired> > </ansired></bold>' ) def bottom_toolbar(): return HTML( f'<b><style fg="ansiyellow">network={safe_operator.network_name} '
def test_load_cli_owner(self): random_address = Account.create().address safe_operator = SafeOperator(random_address, self.ethereum_node_url) random_accounts = [Account.create() for _ in range(3)] random_accounts_keys = [ account.key.hex() for account in random_accounts ] self.assertFalse(safe_operator.accounts) safe_operator.load_cli_owners(random_accounts_keys) self.assertEqual(len(safe_operator.accounts), len(random_accounts_keys)) # Test accounts are not duplicated safe_operator.load_cli_owners(random_accounts_keys) self.assertEqual(len(safe_operator.accounts), len(random_accounts_keys)) # Test invalid accounts don't make the method break safe_operator.load_cli_owners( ['aloha', Account.create().key.hex(), 'bye']) self.assertEqual(len(safe_operator.accounts), len(random_accounts_keys) + 1) # TODO Test setting default sender, mock getBalance # Test unload cli owner safe_operator.default_sender = random_accounts[0] number_of_accounts = len(safe_operator.accounts) safe_operator.unload_cli_owners( ['aloha', random_accounts[0].address, 'bye']) self.assertEqual(len(safe_operator.accounts), number_of_accounts - 1) self.assertFalse(safe_operator.default_sender)