diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 99c2e85e1..82e6de48c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ All notable changes to this project are documented in this file. [0.9.2] In progress ------------------- - Fix shutdown operations when initializing np-api-server and setting minpeers/maxpeers, opening a wallet, or changing databases +- Fix param parsing input from command line [0.9.1] 2019-09-16 diff --git a/neo/Prompt/Commands/BuildNRun.py b/neo/Prompt/Commands/BuildNRun.py index 0983d480b..74555133c 100644 --- a/neo/Prompt/Commands/BuildNRun.py +++ b/neo/Prompt/Commands/BuildNRun.py @@ -90,9 +90,10 @@ def DoRun(contract_script, arguments, wallet, path, verbose=True, except Exception: raise TypeError - tx, result, total_ops, engine = test_deploy_and_invoke(script, i_args, wallet, from_addr, - min_fee, invocation_test_mode, debug_map=debug_map, - invoke_attrs=invoke_attrs, owners=owners, enable_debugger=enable_debugger) + tx, result, total_ops, engine = test_deploy_and_invoke(script, i_args, wallet, from_addr, min_fee, + invocation_test_mode, debug_map=debug_map, + invoke_attrs=invoke_attrs, owners=owners, + enable_debugger=enable_debugger, user_entry=True) i_args.reverse() return_type_results = [] diff --git a/neo/Prompt/Commands/Invoke.py b/neo/Prompt/Commands/Invoke.py index d2f7db90e..1e4b3b554 100644 --- a/neo/Prompt/Commands/Invoke.py +++ b/neo/Prompt/Commands/Invoke.py @@ -24,6 +24,7 @@ from neo.SmartContract import TriggerType from neo.SmartContract.StateMachine import StateMachine from neo.SmartContract.ContractParameterContext import ContractParametersContext +from neo.SmartContract.ContractParameter import ContractParameterType from neo.SmartContract.Contract import Contract from neo.Core.Cryptography.Helper import scripthash_to_address from neo.Core.Cryptography.Crypto import Crypto @@ -161,19 +162,17 @@ def InvokeWithTokenVerificationScript(wallet, tx, token, fee=Fixed8.Zero(), invo return False -def TestInvokeContract(wallet, args, withdrawal_tx=None, from_addr=None, - min_fee=DEFAULT_MIN_FEE, invoke_attrs=None, owners=None): +def TestInvokeContract(wallet, args, withdrawal_tx=None, from_addr=None, min_fee=DEFAULT_MIN_FEE, + invoke_attrs=None, owners=None, user_entry=False): BC = GetBlockchain() contract = BC.GetContract(args[0]) if contract: - # params = args[1:] if len(args) > 1 else [] params, neo_to_attach, gas_to_attach = PromptUtils.get_asset_attachments(params) params, parse_addresses = PromptUtils.get_parse_addresses(params) - params.reverse() if '--i' in params: params = [] @@ -183,6 +182,28 @@ def TestInvokeContract(wallet, args, withdrawal_tx=None, from_addr=None, return None, None, None, None, False params.append(param) params.reverse() + elif user_entry: + try: + i_args = [] + for index, iarg in enumerate(contract.Code.ParameterList): + ptype = ContractParameterType(iarg) + param, abort = PromptUtils.verify_params(ptype, params[index]) + if abort: + return None, None, None, None, False + i_args.append(param) + i_args.reverse() + params = i_args + except IndexError: + print(f"Check inputs. {len(contract.Code.ParameterList)} params specified and only {len(params)} given.") + return None, None, None, None, False + except ValueError as e: + print("Check params.", e) + return None, None, None, None, False + except Exception as e: + print(f'Could not parse {params[index]} as {ptype}:', e) + return None, None, None, None, False + else: + params.reverse() sb = ScriptBuilder() @@ -363,7 +384,7 @@ def test_invoke(script, wallet, outputs, withdrawal_tx=None, def test_deploy_and_invoke(deploy_script, invoke_args, wallet, from_addr=None, min_fee=DEFAULT_MIN_FEE, invocation_test_mode=True, - debug_map=None, invoke_attrs=None, owners=None, enable_debugger=False, snapshot=None): + debug_map=None, invoke_attrs=None, owners=None, enable_debugger=False, snapshot=None, user_entry=False): if settings.USE_DEBUG_STORAGE: debug_storage = DebugStorage.instance() @@ -444,16 +465,35 @@ def test_deploy_and_invoke(deploy_script, invoke_args, wallet, invoke_args, neo_to_attach, gas_to_attach = PromptUtils.get_asset_attachments(invoke_args) invoke_args, no_parse_addresses = PromptUtils.get_parse_addresses(invoke_args) - invoke_args.reverse() - if '--i' in invoke_args: invoke_args = [] for index, iarg in enumerate(contract_state.Code.ParameterList): param, abort = PromptUtils.gather_param(index, iarg) if abort: return None, [], 0, None - else: - invoke_args.append(param) + invoke_args.append(param) + invoke_args.reverse() + elif user_entry: + try: + i_args = [] + for index, iarg in enumerate(contract_state.Code.ParameterList): + ptype = ContractParameterType(iarg) + param, abort = PromptUtils.verify_params(ptype, invoke_args[index]) + if abort: + return None, [], 0, None + i_args.append(param) + i_args.reverse() + invoke_args = i_args + except IndexError: + print(f"Check inputs. {len(contract_state.Code.ParameterList)} params specified and only {len(invoke_args)} given.") + return None, [], 0, None + except ValueError as e: + print("Check params.", e) + return None, [], 0, None + except Exception as e: + print(f'Could not parse {invoke_args[index]} as {ptype}:', e) + return None, [], 0, None + else: invoke_args.reverse() sb = ScriptBuilder() diff --git a/neo/Prompt/Commands/SC.py b/neo/Prompt/Commands/SC.py index aa6ec77d6..bf86f2780 100644 --- a/neo/Prompt/Commands/SC.py +++ b/neo/Prompt/Commands/SC.py @@ -185,7 +185,8 @@ def execute(self, arguments): logger.debug("invalid fee") return False - tx, fee, results, num_ops, engine_success = TestInvokeContract(wallet, arguments, from_addr=from_addr, invoke_attrs=invoke_attrs, owners=owners) + tx, fee, results, num_ops, engine_success = TestInvokeContract(wallet, arguments, from_addr=from_addr, invoke_attrs=invoke_attrs, + owners=owners, user_entry=True) if tx is not None and results is not None: if return_type is not None: diff --git a/neo/Prompt/Commands/tests/test_sc_commands.py b/neo/Prompt/Commands/tests/test_sc_commands.py index 81551c447..0f760b343 100644 --- a/neo/Prompt/Commands/tests/test_sc_commands.py +++ b/neo/Prompt/Commands/tests/test_sc_commands.py @@ -128,6 +128,33 @@ def test_sc_buildrun(self): self.assertFalse(tx) self.assertIn("run `sc build_run help` to see supported queries", mock_print.getvalue()) + # test too few args + PromptData.Wallet = self.GetWallet1(recreate=True) + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['build_run', 'neo/Prompt/Commands/tests/SampleSC.py', 'True', 'False', 'False', '070502', '02', 'add', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', + ] # missing third param + tx, result, total_ops, engine = CommandSC().execute(args) + self.assertFalse(tx) + self.assertIn("Check inputs. 3 params specified and only 2 given.", mock_print.getvalue()) + + # test invalid param type + PromptData.Wallet = self.GetWallet1(recreate=True) + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['build_run', 'neo/Prompt/Commands/tests/SampleSC.py', 'True', 'False', 'False', 'fe0502', '02', 'add', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', + '3'] # "fe" is an invalid param type + tx, result, total_ops, engine = CommandSC().execute(args) + self.assertFalse(tx) + self.assertIn("Check params. 254 is not a valid ContractParameterType", mock_print.getvalue()) + + # test unknown param type + PromptData.Wallet = self.GetWallet1(recreate=True) + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['build_run', 'neo/Prompt/Commands/tests/SampleSC.py', 'True', 'False', 'False', '030502', '02', 'add', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', + '3'] # "03" is Hash160 + tx, result, total_ops, engine = CommandSC().execute(args) + self.assertFalse(tx) + self.assertIn('Could not parse add as Hash160: Unknown param type Hash160', mock_print.getvalue()) + # test successful build and run PromptData.Wallet = self.GetWallet1(recreate=True) with patch('sys.stdout', new=StringIO()) as mock_print: @@ -379,6 +406,13 @@ def test_sc_invoke(self): self.assertFalse(res) self.assertIn("Error testing contract invoke", mock_print.getvalue()) + # test too few args + with patch('sys.stdout', new=StringIO()) as mock_print: + args = ['invoke', token_hash_str, 'name'] # missing second arg + res = CommandSC().execute(args) + self.assertFalse(res) + self.assertIn("Check inputs. 2 params specified and only 1 given.", mock_print.getvalue()) + # test with keyboard interrupt with patch('sys.stdout', new=StringIO()) as mock_print: with patch('neo.Prompt.Commands.SC.prompt', side_effect=[KeyboardInterrupt]): diff --git a/neo/Prompt/Utils.py b/neo/Prompt/Utils.py index 336ac9b7d..b562eb70b 100644 --- a/neo/Prompt/Utils.py +++ b/neo/Prompt/Utils.py @@ -307,6 +307,40 @@ def get_input_prompt(message): return prompt(message) +def verify_params(ptype, param): + if ptype == ContractParameterType.String: + return str(param), False + elif ptype == ContractParameterType.Integer: + return int(param), False + elif ptype == ContractParameterType.Boolean: + return bool(param), False + elif ptype == ContractParameterType.PublicKey: + try: + return ECDSA.decode_secp256r1(param).G, False + except ValueError: + return None, True + elif ptype == ContractParameterType.ByteArray: + if isinstance(param, str) and len(param) == 34 and param[0] == 'A': + return Helper.AddrStrToScriptHash(param).Data, False + try: + res = eval(param, {"__builtins__": {'bytearray': bytearray, 'bytes': bytes}}, {}) + if isinstance(res, bytes): + return bytearray(res), False + return res, False + except Exception: + raise Exception(f"{param} is not a valid bytearray or bytes object") + elif ptype == ContractParameterType.Array: + try: + res = eval(param) + if isinstance(res, list): + return res, False + except Exception: + pass + raise Exception(f"{param} is not a valid list object") + else: + raise Exception("Unknown param type %s " % ptype.name) + + def gather_param(index, param_type, do_continue=True): ptype = ContractParameterType(param_type) prompt_message = '[Param %s] %s input: ' % (index, ptype.name) @@ -322,41 +356,13 @@ def gather_param(index, param_type, do_continue=True): return None, True try: - - if ptype == ContractParameterType.String: - return str(result), False - elif ptype == ContractParameterType.Integer: - return int(result), False - elif ptype == ContractParameterType.Boolean: - return bool(result), False - elif ptype == ContractParameterType.PublicKey: - try: - return ECDSA.decode_secp256r1(result).G, False - except ValueError: - return None, True - elif ptype == ContractParameterType.ByteArray: - if isinstance(result, str) and len(result) == 34 and result[0] == 'A': - return Helper.AddrStrToScriptHash(result).Data, False - res = eval(result, {"__builtins__": {'bytearray': bytearray, 'bytes': bytes}}, {}) - if isinstance(res, bytes): - return bytearray(res), False - return res, False - - elif ptype == ContractParameterType.Array: - res = eval(result) - if isinstance(res, list): - return res, False - raise Exception("Please provide a list") - else: - raise Exception("Unknown param type %s " % ptype.name) + return verify_params(ptype, result) except KeyboardInterrupt: # Control-C pressed: exit - return None, True except Exception as e: - - print("Could not parse param as %s : %s " % (ptype, e)) + print(f'Could not parse {result} as {ptype}:', e) if do_continue: return gather_param(index, param_type, do_continue) diff --git a/neo/Prompt/test_utils.py b/neo/Prompt/test_utils.py index 9241c8817..e0ec262c5 100644 --- a/neo/Prompt/test_utils.py +++ b/neo/Prompt/test_utils.py @@ -153,11 +153,13 @@ def test_parse_no_address(self): self.assertFalse(result) def test_gather_param(self): + # test string input with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value='hello') as fake_prompt: result, abort = Utils.gather_param(0, ContractParameterType.String) self.assertEqual(result, 'hello') + # test integer input with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value=1) as fake_prompt: result, abort = Utils.gather_param(0, ContractParameterType.Integer) @@ -173,6 +175,7 @@ def test_gather_param(self): self.assertEqual(result, 1) + # test bytearray input with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="bytearray(b'abc')") as fake_prompt: result, abort = Utils.gather_param(0, ContractParameterType.ByteArray) @@ -183,6 +186,16 @@ def test_gather_param(self): self.assertEqual(result, bytearray(b'abc')) + # test string input when expecting bytearray + with mock.patch('sys.stdout', new=StringIO()) as mock_print: + with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="abc") as fake_prompt: + result, abort = Utils.gather_param(0, ContractParameterType.ByteArray, do_continue=False) + + self.assertEqual(result, None) + self.assertEqual(abort, True) + self.assertIn('Could not parse abc as ByteArray: abc is not a valid bytearray or bytes object', mock_print.getvalue()) + + # test boolean input with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="abc") as fake_prompt: result, abort = Utils.gather_param(0, ContractParameterType.Boolean) @@ -199,6 +212,7 @@ def test_gather_param(self): self.assertEqual(result, bytearray(b'\xf9\x1dkp\x85\xdb|Z\xaf\t\xf1\x9e\xee\xc1\xca<\r\xb2\xc6\xec')) + # test array input with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value='["a","b","c"]') as fake_prompt: result, abort = Utils.gather_param(0, ContractParameterType.Array) @@ -210,19 +224,22 @@ def test_gather_param(self): self.assertEqual(result, ['a', 'b', 'c', [1, 3, 4], 'e']) # test ContractParameterType.Array without a closed list - with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value='["a","b","c", [1, 3, 4], "e"') as fake_prompt: - result, abort = Utils.gather_param(0, ContractParameterType.Array, do_continue=False) + with mock.patch('sys.stdout', new=StringIO()) as mock_print: + with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value='["a","b","c", [1, 3, 4], "e"') as fake_prompt: + result, abort = Utils.gather_param(0, ContractParameterType.Array, do_continue=False) - self.assertEqual(result, None) - self.assertEqual(abort, True) + self.assertEqual(result, None) + self.assertEqual(abort, True) + self.assertIn('Could not parse ["a","b","c", [1, 3, 4], "e" as Array: ["a","b","c", [1, 3, 4], "e" is not a valid list object', mock_print.getvalue()) # test ContractParameterType.Array with no list - with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="b'abc'") as fake_prompt: - result, abort = Utils.gather_param(0, ContractParameterType.Array, do_continue=False) + with mock.patch('sys.stdout', new=StringIO()) as mock_print: + with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="b'abc'") as fake_prompt: + result, abort = Utils.gather_param(0, ContractParameterType.Array, do_continue=False) - self.assertRaises(Exception, "Please provide a list") - self.assertEqual(result, None) - self.assertEqual(abort, True) + self.assertEqual(result, None) + self.assertEqual(abort, True) + self.assertIn("Could not parse b'abc' as Array: b'abc' is not a valid list object", mock_print.getvalue()) # test ContractParameterType.PublicKey with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="03cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6") as fake_prompt: @@ -255,12 +272,13 @@ def test_gather_param(self): self.assertTrue(abort) # test unknown ContractParameterType - with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="9698b1cac6ce9cbe8517e490778525b929e01903") as fake_prompt: - result, abort = Utils.gather_param(0, ContractParameterType.Hash160, do_continue=False) + with mock.patch('sys.stdout', new=StringIO()) as mock_print: + with mock.patch('neo.Prompt.Utils.get_input_prompt', return_value="9698b1cac6ce9cbe8517e490778525b929e01903") as fake_prompt: + result, abort = Utils.gather_param(0, ContractParameterType.Hash160, do_continue=False) - self.assertRaises(Exception, "Unknown param type Hash160") - self.assertEqual(result, None) - self.assertEqual(abort, True) + self.assertEqual(result, None) + self.assertEqual(abort, True) + self.assertIn("Could not parse 9698b1cac6ce9cbe8517e490778525b929e01903 as Hash160: Unknown param type Hash160", mock_print.getvalue()) # test Exception with mock.patch('sys.stdout', new=StringIO()) as mock_print: diff --git a/neo/SmartContract/tests/test_gas_costs.py b/neo/SmartContract/tests/test_gas_costs.py index 5019b4046..106d7b150 100644 --- a/neo/SmartContract/tests/test_gas_costs.py +++ b/neo/SmartContract/tests/test_gas_costs.py @@ -82,7 +82,7 @@ def test_build_contract_3(self): expected_fee = Fixed8.FromDecimal(.0001) self.assertEqual(expected_cost, engine.GasConsumed()) self.assertEqual(tx.Gas, expected_fee) - self.assertEqual(result[0].GetByteArray(), bytearray(b'\xab\xab\xab\xab\xab\xab')) + self.assertEqual(result[0].GetByteArray(), bytearray(b'abababababab')) def test_build_contract_4(self): """ @@ -98,7 +98,7 @@ def test_build_contract_4(self): tx, result, total_ops, engine = BuildAndRun(arguments, wallet, False) - expected_cost = Fixed8.FromDecimal(2.153) + expected_cost = Fixed8.FromDecimal(3.153) expected_fee = Fixed8.FromDecimal(.0001) self.assertEqual(expected_cost, engine.GasConsumed()) self.assertEqual(tx.Gas, expected_fee) @@ -126,8 +126,8 @@ def test_build_contract_5(self): tx, result, total_ops, engine = BuildAndRun(arguments, wallet, False) - expected_cost = Fixed8(1046600000) - expected_gas = Fixed8.FromDecimal(1.0) + expected_cost = Fixed8.FromDecimal(15.466) + expected_gas = Fixed8.FromDecimal(6) self.assertEqual(expected_cost, engine.GasConsumed()) self.assertEqual(tx.Gas, expected_gas)