From 699ccfa63b26b815b0c1374de9e885b00e593c07 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 8 Dec 2017 13:44:44 +0200 Subject: [PATCH 1/5] Adds storage list logging support. --- tests/parser/features/test_logging.py | 22 ++++++++++++++++++++++ viper/parser/parser.py | 17 ++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 135e753e9b..6fb2004bca 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -542,6 +542,28 @@ def foo(): assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] +def test_storage_list_packing(get_last_log): + code = """ +Bar: __log__({_value: num[4]}) +x: num[4] + +@public +def foo(): + log.Bar(self.x) + +@public +def set_list(): + self.x = [1, 2, 3, 4] + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [0, 0, 0, 0] + c.set_list() + c.foo() + assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] + + def test_passed_list_packing(get_last_log): code = """ Bar: __log__({_value: num[4]}) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 09aed2bf05..41ae80de5b 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -703,9 +703,24 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): maxlen += (typ.count - 1) * 32 typ = typ.subtype - if isinstance(arg, ast.Name): + def check_list_type_match(provided): # Check list types match. + if provided != typ: + raise TypeMismatchException( + "Log list type '%s' does not match provided, expected '%s'" % (provided, typ) + ) + + if isinstance(arg, ast.Attribute) and arg.value.id == 'self': # List from storage + stor_list = context.globals[arg.attr] + check_list_type_match(stor_list.typ.subtype) + size = stor_list.typ.count + for offset in range(0, size): + arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], offset]], + typ=typ) + holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) + elif isinstance(arg, ast.Name): # List from variable. size = context.vars[arg.id].size pos = context.vars[arg.id].pos + check_list_type_match(context.vars[arg.id].typ.subtype) for i in range(0, size): offset = 32 * i arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory') From 96461a92063ea08ae5a4aa0d51c8e860602542ff Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 8 Dec 2017 13:48:11 +0200 Subject: [PATCH 2/5] Adds test to check list logging is of correct (sub)type. --- tests/parser/syntax/test_logging.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/parser/syntax/test_logging.py diff --git a/tests/parser/syntax/test_logging.py b/tests/parser/syntax/test_logging.py new file mode 100644 index 0000000000..4b0f6c4c80 --- /dev/null +++ b/tests/parser/syntax/test_logging.py @@ -0,0 +1,32 @@ +import pytest +from pytest import raises + +from viper import compiler +from viper.exceptions import TypeMismatchException + + +fail_list = [ + """ +Bar: __log__({_value: num[4]}) +x: decimal[4] + +@public +def foo(): + log.Bar(self.x) + """, + """ +Bar: __log__({_value: num[4]}) + +@public +def foo(): + x: decimal[4] + log.Bar(x) + """ +] + + +@pytest.mark.parametrize('bad_code', fail_list) +def test_logging_fail(bad_code): + + with raises(TypeMismatchException): + compiler.compile(bad_code) From 3e2a3ed89a449c4a3ecc00746bd0dc63942c12c4 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 11 Dec 2017 19:34:46 +0200 Subject: [PATCH 3/5] Adds support for logging bytes from storage. --- tests/parser/features/test_logging.py | 23 +++++++++++++++++++++++ viper/parser/parser.py | 22 ++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 6fb2004bca..19142fca9a 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -590,3 +590,26 @@ def foo(): c.foo() assert get_last_log(t, c)["_value"] == [1.11, 2.22, 3.33, 4.44] + + +def test_storage_byte_packing(get_last_log, bytes_helper): + code = """ +MyLog: __log__({arg1: bytes <= 29}) +x:bytes<=5 + +@public +def foo(a:num): + log.MyLog(self.x) + +@public +def setbytez(): + self.x = 'hello' + """ + + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)['arg1'] == bytes_helper('', 29) + c.setbytez() + c.foo() + assert get_last_log(t, c)['arg1'] == bytes_helper('hello', 29) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 41ae80de5b..75b8ebd563 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -273,7 +273,7 @@ def __init__(self, vars=None, globals=None, sigs=None, forvars=None, return_type self.placeholder_count = 1 # Original code (for error pretty-printing purposes) self.origcode = origcode - # In Loop status. Wether body is currently evaluating within a for-loop or not. + # In Loop status. Whether body is currently evaluating within a for-loop or not. self.in_for_loop = set() def set_in_for_loop(self, name_of_list): @@ -364,7 +364,7 @@ def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, ori def parse_tree_to_lll(code, origcode, runtime_only=False): _contracts, _events, _defs, _globals = get_contracts_and_defs_and_globals(code) _names = [_def.name for _def in _defs] + [_event.target.id for _event in _events] - # Checks for duplicate funciton / event names + # Checks for duplicate function / event names if len(set(_names)) < len(_names): raise VariableDeclarationException("Duplicate function or event name: %s" % [name for name in _names if _names.count(name) > 1][0]) # Initialization function @@ -681,8 +681,15 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): holder.append(LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory')) elif isinstance(typ, ByteArrayType): bytez = b'' + # Bytes from Storage + if isinstance(arg, ast.Attribute) and arg.value.id == 'self': + stor_bytes = context.globals[arg.attr] + if stor_bytes.typ.maxlen > 32: + raise InvalidLiteralException("Can only log a maximum of 32 bytes at a time.") + arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], 1]], typ=BaseType(32)) + holder, maxlen = pack_args_by_32(holder, maxlen, arg2, BaseType(32), context, context.new_placeholder(BaseType(32))) # String literals - if isinstance(arg, ast.Str): + elif isinstance(arg, ast.Str): if len(arg.s) > typ.maxlen: raise TypeMismatchException("Data input bytes are to big: %r %r" % (len(arg.s), typ)) for c in arg.s: @@ -709,7 +716,8 @@ def check_list_type_match(provided): # Check list types match. "Log list type '%s' does not match provided, expected '%s'" % (provided, typ) ) - if isinstance(arg, ast.Attribute) and arg.value.id == 'self': # List from storage + # List from storage + if isinstance(arg, ast.Attribute) and arg.value.id == 'self': stor_list = context.globals[arg.attr] check_list_type_match(stor_list.typ.subtype) size = stor_list.typ.count @@ -717,7 +725,8 @@ def check_list_type_match(provided): # Check list types match. arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], offset]], typ=typ) holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) - elif isinstance(arg, ast.Name): # List from variable. + # List from variable. + elif isinstance(arg, ast.Name): size = context.vars[arg.id].size pos = context.vars[arg.id].pos check_list_type_match(context.vars[arg.id].typ.subtype) @@ -725,7 +734,8 @@ def check_list_type_match(provided): # Check list types match. offset = 32 * i arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory') holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) - else: # is list literal. + # is list literal. + else: holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder) for j, arg2 in enumerate(arg.elts[1:]): holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) From cbd0d0132cfca6055cea0cc7e928c05d43004b5a Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 11 Dec 2017 19:40:18 +0200 Subject: [PATCH 4/5] Adds exception to limit to 32 bytes of bytes type logging (restriction for the time being). --- tests/parser/syntax/test_logging.py | 18 ++++++++++++++++-- viper/parser/parser.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/parser/syntax/test_logging.py b/tests/parser/syntax/test_logging.py index 4b0f6c4c80..c860530082 100644 --- a/tests/parser/syntax/test_logging.py +++ b/tests/parser/syntax/test_logging.py @@ -21,6 +21,16 @@ def foo(): def foo(): x: decimal[4] log.Bar(x) + """, + """ +# larger than 32 bytes logging. + +MyLog: __log__({arg1: bytes <= 29}) +x:bytes<=55 + +@public +def foo(a:num): + log.MyLog(self.x) """ ] @@ -28,5 +38,9 @@ def foo(): @pytest.mark.parametrize('bad_code', fail_list) def test_logging_fail(bad_code): - with raises(TypeMismatchException): - compiler.compile(bad_code) + if isinstance(bad_code, tuple): + with raises(bad_code[1]): + compiler.compile(bad_code[0]) + else: + with raises(TypeMismatchException): + compiler.compile(bad_code) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 75b8ebd563..00a77114f0 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -685,7 +685,7 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): if isinstance(arg, ast.Attribute) and arg.value.id == 'self': stor_bytes = context.globals[arg.attr] if stor_bytes.typ.maxlen > 32: - raise InvalidLiteralException("Can only log a maximum of 32 bytes at a time.") + raise TypeMismatchException("Can only log a maximum of 32 bytes at a time.") arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], 1]], typ=BaseType(32)) holder, maxlen = pack_args_by_32(holder, maxlen, arg2, BaseType(32), context, context.new_placeholder(BaseType(32))) # String literals From 43df433542286579eca86e8be1e778549b32d5dc Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 13 Dec 2017 15:59:59 +0200 Subject: [PATCH 5/5] Adds test for logging a decimal list. --- tests/parser/features/test_logging.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 5e9de1c084..2e232e695f 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -617,3 +617,26 @@ def setbytez(): c.setbytez() c.foo() assert get_last_log(t, c)['arg1'] == bytes_helper('hello', 29) + + +def test_storage_decimal_list_packing(t, get_last_log, bytes_helper, get_contract_with_gas_estimation, chain): + t.s = chain + code = """ +Bar: __log__({_value: decimal[4]}) +x: decimal[4] + +@public +def foo(): + log.Bar(self.x) + +@public +def set_list(): + self.x = [1.33, 2.33, 3.33, 4.33] + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [0, 0, 0, 0] + c.set_list() + c.foo() + assert get_last_log(t, c)["_value"] == [1.33, 2.33, 3.33, 4.33]