From c5ce4613ce26c8a7fecdcc56aca36229dcde61cf Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Tue, 11 Dec 2018 15:15:49 +0100 Subject: [PATCH 1/5] remove deprecated migrate command --- neo/Implementations/Wallets/peewee/UserWallet.py | 7 ------- neo/Prompt/Commands/Wallet.py | 14 -------------- 2 files changed, 21 deletions(-) diff --git a/neo/Implementations/Wallets/peewee/UserWallet.py b/neo/Implementations/Wallets/peewee/UserWallet.py index 10efb79fe..2b1d10e14 100755 --- a/neo/Implementations/Wallets/peewee/UserWallet.py +++ b/neo/Implementations/Wallets/peewee/UserWallet.py @@ -53,13 +53,6 @@ def BuildDatabase(self): except Exception as e: logger.error("Could not build database %s %s " % (e, self._path)) - def Migrate(self): - migrator = SqliteMigrator(self._db) - migrate( - migrator.drop_not_null('Contract', 'Account_id'), - migrator.add_column('Address', 'IsWatchOnly', BooleanField(default=False)), - ) - def DB(self): return self._db diff --git a/neo/Prompt/Commands/Wallet.py b/neo/Prompt/Commands/Wallet.py index f12a7be70..89194ceb6 100644 --- a/neo/Prompt/Commands/Wallet.py +++ b/neo/Prompt/Commands/Wallet.py @@ -33,7 +33,6 @@ def __init__(self): self.register_sub_command(CommandWalletOpen()) self.register_sub_command(CommandWalletClose()) self.register_sub_command(CommandWalletVerbose(), ['v', '--v']) - self.register_sub_command(CommandWalletMigrate()) self.register_sub_command(CommandWalletCreateAddress()) self.register_sub_command(CommandWalletSend()) self.register_sub_command(CommandWalletSendMany()) @@ -179,19 +178,6 @@ def command_desc(self): return CommandDesc('verbose', 'show additional wallet details') -class CommandWalletMigrate(CommandBase): - - def __init__(self): - super().__init__() - - def execute(self, arguments=None): - PromptData.Wallet.Migrate() - return True - - def command_desc(self): - return CommandDesc('migrate', 'migrate an old wallet to the new format') - - class CommandWalletCreateAddress(CommandBase): def __init__(self): From bbf472acbcd46408a27a0f446cad3836a18b3178 Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Tue, 18 Dec 2018 18:03:57 +0100 Subject: [PATCH 2/5] Add wallet token send --- neo/Prompt/Commands/Invoke.py | 2 +- neo/Prompt/Commands/Tokens.py | 124 ++++++++++++++++-- .../Commands/tests/test_token_commands.py | 96 +++++++++++--- 3 files changed, 191 insertions(+), 31 deletions(-) diff --git a/neo/Prompt/Commands/Invoke.py b/neo/Prompt/Commands/Invoke.py index 771ae2f66..017bae072 100644 --- a/neo/Prompt/Commands/Invoke.py +++ b/neo/Prompt/Commands/Invoke.py @@ -317,7 +317,7 @@ def test_invoke(script, wallet, outputs, withdrawal_tx=None, if context.Completed: wallet_tx.scripts = context.GetScripts() else: - logger.warn("Not gathering signatures for test build. For a non-test invoke that would occur here.") + logger.warning("Not gathering signatures for test build. For a non-test invoke that would occur here.") # if not gather_signatures(context, wallet_tx, owners): # return None, [], 0, None diff --git a/neo/Prompt/Commands/Tokens.py b/neo/Prompt/Commands/Tokens.py index 0e2fb647e..77c2410ef 100644 --- a/neo/Prompt/Commands/Tokens.py +++ b/neo/Prompt/Commands/Tokens.py @@ -11,13 +11,32 @@ from neo.Prompt.PromptData import PromptData from neo.Prompt.Utils import get_arg from neo.Implementations.Wallets.peewee.Models import NEP5Token as ModelNEP5Token +from neo.Core.TX.TransactionAttribute import TransactionAttributeUsage import peewee +import base58 + + +# TODO: remove this once neo-python-core is merged +def isValidPublicAddress(address: str) -> bool: + """Check if address is a valid NEO address""" + valid = False + + if len(address) == 34 and address[0] == 'A': + try: + base58.b58decode_check(address.encode()) + valid = True + except ValueError: + # checksum mismatch + valid = False + + return valid class CommandWalletToken(CommandBase): def __init__(self): super().__init__() self.register_sub_command(CommandTokenDelete()) + self.register_sub_command(CommandTokenSend()) def command_desc(self): return CommandDesc('token', 'various token operations') @@ -77,23 +96,102 @@ def command_desc(self): return CommandDesc('delete', 'remove a token from the wallet', [p1]) -def token_send(wallet, args, prompt_passwd=True): - if len(args) < 4: - print("please provide a token symbol, from address, to address, and amount") - return False +class CommandTokenSend(CommandBase): + + def __init__(self): + super().__init__() - user_tx_attributes = None - if len(args) > 4: - args, user_tx_attributes = get_tx_attr_from_args(args) + def execute(self, arguments): + wallet = PromptData.Wallet + + if len(arguments) < 4: + print("Please specify the required parameters") + return False + + if len(arguments) > 5: + # the 5th argument is the optional attributes, + print("Too many parameters supplied. Please check your command") + return False + + _, user_tx_attributes = get_tx_attr_from_args(arguments) + + token = arguments[0] + send_from = arguments[1] + send_to = arguments[2] + try: + amount = float(arguments[3]) + except ValueError: + print(f"{arguments[3]} is not a valid amount") + return + + try: + success = token_send(wallet, token, send_from, send_to, amount, user_tx_attributes) + except ValueError as e: + # occurs if arguments are invalid + print(str(e)) + success = False + + return success + + def command_desc(self): + p1 = ParameterDesc('token', 'token symbol name or script_hash') + p2 = ParameterDesc('from_addr', 'address to send token from') + p3 = ParameterDesc('to_addr', 'address to send token to') + p4 = ParameterDesc('amount', 'number of tokens to send') + p5 = ParameterDesc('--tx-attr', f"a list of transaction attributes to attach to the transaction\n\n" + f"{' ':>17} See: http://docs.neo.org/en-us/network/network-protocol.html section 4 for a description of possible attributes\n\n" # noqa: E128 ignore indentation + f"{' ':>17} Example:\n" + f"{' ':>20} --tx-attr=[{{\"usage\": ,\"data\":\"\"}}, ...]\n" + f"{' ':>20} --tx-attr=[{{\"usage\": 0x90,\"data\":\"my brief description\"}}]\n", optional=True) + + return CommandDesc('send', 'remove a token from the wallet', [p1, p2, p3, p4, p5]) + + +def token_send(wallet, token_str, send_from, send_to, amount, prompt_passwd=True, user_tx_attributes=None): + """ + + Args: + wallet (Wallet): a UserWallet instance + token_str (str): symbol name or script_hash + send_from (str): a wallet address + send_to (str): a wallet address + amount (float): the number of tokens to send + prompt_passwd (str): (optional) whether to prompt for a password before sending it to the network + user_tx_attributes (list): a list of ``TransactionAttribute``s. + + Returns: + a Transaction object if successful, False otherwise. + """ + if not user_tx_attributes: + user_tx_attributes = [] + + token = None + for t in wallet.GetTokens().values(): + if token_str == t.symbol: + token = t + break + elif token_str == t.ScriptHash.ToString(): + token = t - token = get_asset_id(wallet, args[0]) if not isinstance(token, NEP5Token): - print("The given symbol does not represent a loaded NEP5 token") - return False + raise ValueError("The given token argument does not represent a known NEP5 token") - send_from = args[1] - send_to = args[2] - amount = amount_from_string(token, args[3]) + if not isValidPublicAddress(send_from): + raise ValueError("send_from is not a valid address") + + if not isValidPublicAddress(send_to): + raise ValueError("send_to is not a valid address") + + try: + # internally this function uses the `Decimal` class which will parse the float amount to its required format. + # the name is a bit misleading /shrug + amount = amount_from_string(token, amount) + except Exception: + raise ValueError(f"{amount} is not a valid amount") + + for attr in user_tx_attributes: + if not isinstance(attr, TransactionAttribute): + raise ValueError(f"{attr} is not a valid transaction attribute") return do_token_transfer(token, wallet, send_from, send_to, amount, prompt_passwd=prompt_passwd, tx_attributes=user_tx_attributes) diff --git a/neo/Prompt/Commands/tests/test_token_commands.py b/neo/Prompt/Commands/tests/test_token_commands.py index db83fa6f3..6a48032f0 100644 --- a/neo/Prompt/Commands/tests/test_token_commands.py +++ b/neo/Prompt/Commands/tests/test_token_commands.py @@ -1,6 +1,6 @@ from neo.Utils.WalletFixtureTestCase import WalletFixtureTestCase from neo.Wallets.utils import to_aes_key -from neo.Prompt.Utils import get_asset_id +from neo.Prompt.Utils import get_asset_id, get_tx_attr_from_args from neo.Implementations.Wallets.peewee.UserWallet import UserWallet from neo.Implementations.Notifications.LevelDB.NotificationDB import NotificationDB from neo.Core.Blockchain import Blockchain @@ -115,8 +115,7 @@ def test_token_send_good(self): addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str - args = [token.symbol, addr_from, addr_to, '1300'] - send = token_send(wallet, args, prompt_passwd=True) + send = token_send(wallet, token.symbol, addr_from, addr_to, 1300, prompt_passwd=True) self.assertTrue(send) res = send.ToJson() @@ -129,9 +128,9 @@ def test_token_send_with_user_attributes(self): token = self.get_token(wallet) addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str + _, attributes = get_tx_attr_from_args(['--tx-attr=[{"usage":241,"data":"This is a remark"},{"usage":242,"data":"This is a remark 2"}]']) - args = [token.symbol, addr_from, addr_to, '1300', '--tx-attr=[{"usage":241,"data":"This is a remark"},{"usage":242,"data":"This is a remark 2"}]'] - send = token_send(wallet, args, prompt_passwd=True) + send = token_send(wallet, token.symbol, addr_from, addr_to, 1300, user_tx_attributes=attributes, prompt_passwd=True) self.assertTrue(send) res = send.ToJson() @@ -146,8 +145,8 @@ def test_token_send_bad_user_attributes(self): addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str - args = [token.symbol, addr_from, addr_to, '100', '--tx-attr=[{"usa:241,"data":"This is a remark"}]'] - send = token_send(wallet, args, prompt_passwd=True) + _, attributes = get_tx_attr_from_args(['--tx-attr=[{"usa:241,"data":"This is a remark"}]']) + send = token_send(wallet, token.symbol, addr_from, addr_to, 100, user_tx_attributes=attributes, prompt_passwd=True) self.assertTrue(send) res = send.ToJson() @@ -160,20 +159,20 @@ def test_token_send_bad_args(self): # too few args addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str - args = [token.symbol, addr_from, addr_to] - send = token_send(wallet, args, prompt_passwd=False) + with self.assertRaises(ValueError) as context: + token_send(wallet, token.symbol, addr_from, addr_to, None, prompt_passwd=False) - self.assertFalse(send) + self.assertIn("not a valid amount", str(context.exception)) def test_token_send_bad_token(self): wallet = self.GetWallet1(recreate=True) addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str - args = ["Blah", addr_from, addr_to, '1300'] - send = token_send(wallet, args, prompt_passwd=False) + with self.assertRaises(ValueError) as context: + token_send(wallet, "Blah", addr_from, addr_to, 1300, prompt_passwd=False) - self.assertFalse(send) + self.assertIn("does not represent a known NEP5 token", str(context.exception)) def test_token_send_no_tx(self): with patch('neo.Wallets.NEP5Token.NEP5Token.Transfer', return_value=(None, 0, None)): @@ -182,8 +181,7 @@ def test_token_send_no_tx(self): addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str - args = [token.symbol, addr_from, addr_to, '1300'] - send = token_send(wallet, args, prompt_passwd=False) + send = token_send(wallet, token.symbol, addr_from, addr_to, 1300, prompt_passwd=False) self.assertFalse(send) @@ -194,8 +192,7 @@ def test_token_send_bad_password(self): addr_from = wallet.GetDefaultContract().Address addr_to = self.watch_addr_str - args = [token.symbol, addr_from, addr_to, '1300'] - send = token_send(wallet, args) + send = token_send(wallet, token.symbol, addr_from, addr_to, 1300) self.assertFalse(send) @@ -617,6 +614,71 @@ def test_wallet_token_delete(self): self.assertTrue(res) self.assertIn("deleted", mock_print.getvalue()) + def test_wallet_token_send(self): + + with self.OpenWallet1(): + # test with no parameters + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("specify the required parameter", mock_print.getvalue()) + + # test with insufficient parameters + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send', 'arg1'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("specify the required parameter", mock_print.getvalue()) + + # test with too many parameters (max is 4 mandatory + 1 optional) + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("Too many parameters supplied", mock_print.getvalue()) + + # test with invalid token argument + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send', 'invalid_token_name', 'arg2', 'arg3', '10'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("does not represent a known NEP5 token", mock_print.getvalue()) + + # test with valid token arg, but invalid from_addr + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send', 'NXT4', 'invalid_from_addr', 'arg3', '10'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("not a valid address", mock_print.getvalue()) + + # test with valid token and from_addr, but invalid to_addr + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send', 'NXT4', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'invalid_to_addr', '10'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("not a valid address", mock_print.getvalue()) + + # test with invalid amount + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['token', 'send', 'NXT4', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'invalid_amount'] + res = CommandWallet().execute(args) + self.assertFalse(res) + self.assertIn("not a valid amount", mock_print.getvalue()) + + # Note that there is no test for invalid tx-attributes. Invalid attributes result in an empty attribute list being + # passed to the underlying function thus having no effect. + + # test with a good transfer + # we don't really send anything. Testing `do_token_transfer` already happens in `test_token_send_good()` + with patch('neo.Prompt.Commands.Tokens.do_token_transfer', side_effect=[object()]): + token = self.get_token(PromptData.Wallet) + addr_from = PromptData.Wallet.GetDefaultContract().Address + addr_to = self.watch_addr_str + + send = token_send(PromptData.Wallet, token.symbol, addr_from, addr_to, 13, prompt_passwd=False) + self.assertTrue(send) + # utility function def Approve_Allowance(self): wallet = self.GetWallet1(recreate=True) From a9be97d5b6bae0683456774ed1effefb4c95c5c1 Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Wed, 19 Dec 2018 08:15:29 +0100 Subject: [PATCH 3/5] Update requirements and use isValidPublicAddress from new neo-python-core version --- neo/Prompt/Commands/Tokens.py | 20 ++------------------ requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/neo/Prompt/Commands/Tokens.py b/neo/Prompt/Commands/Tokens.py index 77c2410ef..1e9802bf5 100644 --- a/neo/Prompt/Commands/Tokens.py +++ b/neo/Prompt/Commands/Tokens.py @@ -5,31 +5,15 @@ from neocore.UInt160 import UInt160 from prompt_toolkit import prompt from decimal import Decimal -from neo.Core.TX.TransactionAttribute import TransactionAttribute, TransactionAttributeUsage +from neo.Core.TX.TransactionAttribute import TransactionAttribute import binascii from neo.Prompt.CommandBase import CommandBase, CommandDesc, ParameterDesc from neo.Prompt.PromptData import PromptData from neo.Prompt.Utils import get_arg from neo.Implementations.Wallets.peewee.Models import NEP5Token as ModelNEP5Token from neo.Core.TX.TransactionAttribute import TransactionAttributeUsage +from neocore.Utils import isValidPublicAddress import peewee -import base58 - - -# TODO: remove this once neo-python-core is merged -def isValidPublicAddress(address: str) -> bool: - """Check if address is a valid NEO address""" - valid = False - - if len(address) == 34 and address[0] == 'A': - try: - base58.b58decode_check(address.encode()) - valid = True - except ValueError: - # checksum mismatch - valid = False - - return valid class CommandWalletToken(CommandBase): diff --git a/requirements.txt b/requirements.txt index 776149dac..47292811a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,10 +33,10 @@ logzero==1.5.0 memory-profiler==0.54.0 mmh3==2.5.1 mock==2.0.0 -mpmath==1.0.0 +mpmath==1.1.0 neo-boa==0.5.6 neo-python-rpc==0.2.1 -neocore==0.5.4 +neocore==0.5.5 pbr==4.2.0 peewee==3.6.4 pexpect==4.6.0 From 1b66e9f8a0f3b6ae8b516b748893bf551dfff89b Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Wed, 19 Dec 2018 15:01:01 +0100 Subject: [PATCH 4/5] - fix send token description - process feedback --- neo/Prompt/Commands/Tokens.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/neo/Prompt/Commands/Tokens.py b/neo/Prompt/Commands/Tokens.py index 1e9802bf5..a77ff182e 100644 --- a/neo/Prompt/Commands/Tokens.py +++ b/neo/Prompt/Commands/Tokens.py @@ -106,7 +106,7 @@ def execute(self, arguments): amount = float(arguments[3]) except ValueError: print(f"{arguments[3]} is not a valid amount") - return + return False try: success = token_send(wallet, token, send_from, send_to, amount, user_tx_attributes) @@ -128,7 +128,7 @@ def command_desc(self): f"{' ':>20} --tx-attr=[{{\"usage\": ,\"data\":\"\"}}, ...]\n" f"{' ':>20} --tx-attr=[{{\"usage\": 0x90,\"data\":\"my brief description\"}}]\n", optional=True) - return CommandDesc('send', 'remove a token from the wallet', [p1, p2, p3, p4, p5]) + return CommandDesc('send', 'send a token from the wallet', [p1, p2, p3, p4, p5]) def token_send(wallet, token_str, send_from, send_to, amount, prompt_passwd=True, user_tx_attributes=None): @@ -156,6 +156,7 @@ def token_send(wallet, token_str, send_from, send_to, amount, prompt_passwd=True break elif token_str == t.ScriptHash.ToString(): token = t + break if not isinstance(token, NEP5Token): raise ValueError("The given token argument does not represent a known NEP5 token") From d8e6e96d10ce30558347834d477179f5e9a8e543 Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Wed, 19 Dec 2018 15:28:52 +0100 Subject: [PATCH 5/5] fix doc string typo --- neo/Prompt/Commands/Tokens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neo/Prompt/Commands/Tokens.py b/neo/Prompt/Commands/Tokens.py index a77ff182e..7785364d9 100644 --- a/neo/Prompt/Commands/Tokens.py +++ b/neo/Prompt/Commands/Tokens.py @@ -140,7 +140,7 @@ def token_send(wallet, token_str, send_from, send_to, amount, prompt_passwd=True send_from (str): a wallet address send_to (str): a wallet address amount (float): the number of tokens to send - prompt_passwd (str): (optional) whether to prompt for a password before sending it to the network + prompt_passwd (bool): (optional) whether to prompt for a password before sending it to the network user_tx_attributes (list): a list of ``TransactionAttribute``s. Returns: