diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 83d47d9d4..a38532dbd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ All notable changes to this project are documented in this file. - Fix network syncing against neo-cli ``2.10.3`` clients - Update Python requirements - Fix Docker configuration pip issue +- Fix param parsing input from command line [0.9.0] 2019-08-21 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..6beeaff8f 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,25 @@ 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 params. {len(contract.Code.ParameterList)} params specified and only {len(params)} given.") + return None, None, None, None, False + except Exception as e: + print("Could not parse param as %s : %s " % (ptype, e)) + return None, None, None, None, False + else: + params.reverse() sb = ScriptBuilder() @@ -363,7 +381,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 +462,32 @@ 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 params. {len(contract_state.Code.ParameterList)} params specified and only {len(invoke_args)} given.") + return None, [], 0, None + except Exception as e: + print("Could not parse param as %s : %s " % (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..38a13c873 100644 --- a/neo/Prompt/Commands/tests/test_sc_commands.py +++ b/neo/Prompt/Commands/tests/test_sc_commands.py @@ -128,6 +128,15 @@ 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 params. 3 params specified and only 2 given.", 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 +388,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 params. 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..2b93d7779 100644 --- a/neo/Prompt/Utils.py +++ b/neo/Prompt/Utils.py @@ -307,6 +307,41 @@ 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("Please provide a bytearray or bytes object") + + elif ptype == ContractParameterType.Array: + try: + res = eval(param) + if isinstance(res, list): + return res, False + except Exception: + pass + raise Exception("Please provide a list") + 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) @@ -314,7 +349,7 @@ def gather_param(index, param_type, do_continue=True): try: result = get_input_prompt(prompt_message) except KeyboardInterrupt: - print("Input cancelled") + print("Input cancelled") return None, True except Exception as e: print(str(e)) @@ -322,33 +357,7 @@ 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 diff --git a/neo/Prompt/test_utils.py b/neo/Prompt/test_utils.py index 9241c8817..9bd389a0b 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,14 @@ def test_gather_param(self): self.assertEqual(result, bytearray(b'abc')) + # test string input when expecting bytearray + 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) + + # 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 +210,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) 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)