Skip to content
This repository has been archived by the owner on Nov 15, 2021. It is now read-only.

Improve parameter parsing [02] #973

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions neo/Prompt/Commands/BuildNRun.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
58 changes: 49 additions & 9 deletions neo/Prompt/Commands/Invoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = []
Expand All @@ -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()

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion neo/Prompt/Commands/SC.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
34 changes: 34 additions & 0 deletions neo/Prompt/Commands/tests/test_sc_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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]):
Expand Down
66 changes: 36 additions & 30 deletions neo/Prompt/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

@jseagrave21 jseagrave21 Sep 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pass is necessary in case there is no exception thrown but eval does not evaluate a list object.

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)
Expand All @@ -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)

Expand Down
46 changes: 32 additions & 14 deletions neo/Prompt/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions neo/SmartContract/tests/test_gas_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down