From 24f4c628c35f9f04517bc22bda11cdafc57b7216 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 26 Apr 2024 13:36:28 +0200 Subject: [PATCH 01/13] fix double eval in blueprint builtin --- vyper/builtins/functions.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 35cbbe648d..91c7dd75a5 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1812,9 +1812,13 @@ def _build_create_IR( if len(ctor_args) != 1 or not isinstance(ctor_args[0].typ, BytesT): raise StructureException("raw_args must be used with exactly 1 bytes argument") - argbuf = bytes_data_ptr(ctor_args[0]) - argslen = get_bytearray_length(ctor_args[0]) - bufsz = ctor_args[0].typ.maxlen + with ctor_args[0].cache_when_complex("arg") as (b1, arg): + argbuf = bytes_data_ptr(arg) + argslen = get_bytearray_length(arg) + bufsz = arg.typ.maxlen + return b1.resolve( + self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset) + ) else: # encode the varargs to_encode = ir_tuple_from_args(ctor_args) @@ -1829,7 +1833,9 @@ def _build_create_IR( # return a complex expression which writes to memory and returns # the length of the encoded data argslen = abi_encode(argbuf, to_encode, context, bufsz=bufsz, returns_len=True) + return self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset) + def _helper(self, argbuf, bufsz, target, value, salt, argslen, code_offset): # NOTE: we need to invoke the abi encoder before evaluating MSIZE, # then copy the abi encoded buffer to past-the-end of the initcode # (since the abi encoder could write to fresh memory). From 927c33892ad72d8684175b32beed491d76fe0e90 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 26 Apr 2024 13:57:23 +0200 Subject: [PATCH 02/13] fix double eval in slice builtin --- vyper/builtins/functions.py | 71 +++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 91c7dd75a5..360157853a 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -249,45 +249,48 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: dst_typ = BytesT(length.value) # allocate a buffer for the return value np = context.new_internal_variable(dst_typ) + with start.cache_when_complex("start") as ( b1, start, ), length.cache_when_complex("length") as ( + b2, + length): + # `msg.data` by `calldatacopy` + if sub.value == "~calldata": + node = [ + "seq", + _make_slice_bounds_check(start, length, "calldatasize"), + ["mstore", np, length], + ["calldatacopy", np + 32, start, length], + np, + ] - # `msg.data` by `calldatacopy` - if sub.value == "~calldata": - node = [ - "seq", - _make_slice_bounds_check(start, length, "calldatasize"), - ["mstore", np, length], - ["calldatacopy", np + 32, start, length], - np, - ] - - # `self.code` by `codecopy` - elif sub.value == "~selfcode": - node = [ - "seq", - _make_slice_bounds_check(start, length, "codesize"), - ["mstore", np, length], - ["codecopy", np + 32, start, length], - np, - ] - - # `
.code` by `extcodecopy` - else: - assert sub.value == "~extcode" and len(sub.args) == 1 - node = [ - "with", - "_extcode_address", - sub.args[0], - [ + # `self.code` by `codecopy` + elif sub.value == "~selfcode": + node = [ "seq", - _make_slice_bounds_check(start, length, ["extcodesize", "_extcode_address"]), + _make_slice_bounds_check(start, length, "codesize"), ["mstore", np, length], - ["extcodecopy", "_extcode_address", np + 32, start, length], + ["codecopy", np + 32, start, length], np, - ], - ] + ] - assert isinstance(length.value, int) # mypy hint - return IRnode.from_list(node, typ=BytesT(length.value), location=MEMORY) + # `
.code` by `extcodecopy` + else: + assert sub.value == "~extcode" and len(sub.args) == 1 + node = [ + "with", + "_extcode_address", + sub.args[0], + [ + "seq", + _make_slice_bounds_check(start, length, ["extcodesize", "_extcode_address"]), + ["mstore", np, length], + ["extcodecopy", "_extcode_address", np + 32, start, length], + np, + ], + ] + + assert isinstance(length.value, int) # mypy hint + ret = IRnode.from_list(node, typ=BytesT(length.value), location=MEMORY) + return b1.resolve(b2.resolve(ret)) # note: this and a lot of other builtins could be refactored to accept any uint type From df79447f7181a821e91d73d8ef8bd408c2e75546 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 26 Apr 2024 14:08:38 +0200 Subject: [PATCH 03/13] fix double eval in sqrt builtin --- vyper/builtins/functions.py | 60 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 360157853a..5a91980de8 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -249,7 +249,7 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: dst_typ = BytesT(length.value) # allocate a buffer for the return value np = context.new_internal_variable(dst_typ) - with start.cache_when_complex("start") as ( b1, start, ), length.cache_when_complex("length") as ( + with start.cache_when_complex("start") as (b1, start), length.cache_when_complex("length") as ( b2, length): # `msg.data` by `calldatacopy` @@ -2121,7 +2121,8 @@ def build_IR(self, expr, args, kwargs, context): arg = args[0] # TODO: reify decimal and integer sqrt paths (see isqrt) - sqrt_code = """ + with arg.cache_when_complex("x") as (b1, arg): + sqrt_code = """ assert x >= 0.0 z: decimal = 0.0 @@ -2136,33 +2137,34 @@ def build_IR(self, expr, args, kwargs, context): break y = z z = (x / z + z) / 2.0 - """ - - x_type = DecimalT() - placeholder_copy = ["pass"] - # Steal current position if variable is already allocated. - if arg.value == "mload": - new_var_pos = arg.args[0] - # Other locations need to be copied. - else: - new_var_pos = context.new_internal_variable(x_type) - placeholder_copy = ["mstore", new_var_pos, arg] - # Create input variables. - variables = {"x": VariableRecord(name="x", pos=new_var_pos, typ=x_type, mutable=False)} - # Dictionary to update new (i.e. typecheck) namespace - variables_2 = {"x": VarInfo(DecimalT())} - # Generate inline IR. - new_ctx, sqrt_ir = generate_inline_function( - code=sqrt_code, - variables=variables, - variables_2=variables_2, - memory_allocator=context.memory_allocator, - ) - return IRnode.from_list( - ["seq", placeholder_copy, sqrt_ir, new_ctx.vars["z"].pos], # load x variable - typ=DecimalT(), - location=MEMORY, - ) + """ + + x_type = DecimalT() + placeholder_copy = ["pass"] + # Steal current position if variable is already allocated. + if arg.value == "mload": + new_var_pos = arg.args[0] + # Other locations need to be copied. + else: + new_var_pos = context.new_internal_variable(x_type) + placeholder_copy = ["mstore", new_var_pos, arg] + # Create input variables. + variables = {"x": VariableRecord(name="x", pos=new_var_pos, typ=x_type, mutable=False)} + # Dictionary to update new (i.e. typecheck) namespace + variables_2 = {"x": VarInfo(DecimalT())} + # Generate inline IR. + new_ctx, sqrt_ir = generate_inline_function( + code=sqrt_code, + variables=variables, + variables_2=variables_2, + memory_allocator=context.memory_allocator, + ) + ret = IRnode.from_list( + ["seq", placeholder_copy, sqrt_ir, new_ctx.vars["z"].pos], # load x variable + typ=DecimalT(), + location=MEMORY, + ) + return b1.resolve(ret) class ISqrt(BuiltinFunctionT): From 94a2a6daeab0a3e62544c36d8c80976cd0b1e5e4 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 26 Apr 2024 14:33:23 +0200 Subject: [PATCH 04/13] fix blueprint missing arg --- vyper/builtins/functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 5a91980de8..5d1e1d98a6 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1820,7 +1820,7 @@ def _build_create_IR( argslen = get_bytearray_length(arg) bufsz = arg.typ.maxlen return b1.resolve( - self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset) + self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure) ) else: # encode the varargs @@ -1836,9 +1836,9 @@ def _build_create_IR( # return a complex expression which writes to memory and returns # the length of the encoded data argslen = abi_encode(argbuf, to_encode, context, bufsz=bufsz, returns_len=True) - return self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset) + return self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure) - def _helper(self, argbuf, bufsz, target, value, salt, argslen, code_offset): + def _helper(self, argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure): # NOTE: we need to invoke the abi encoder before evaluating MSIZE, # then copy the abi encoded buffer to past-the-end of the initcode # (since the abi encoder could write to fresh memory). From 463664c39b79e2697c641dcfaedfde5d1542a284 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 26 Apr 2024 14:35:10 +0200 Subject: [PATCH 05/13] add tests for double evals --- .../builtins/codegen/test_create_functions.py | 37 +++++++++++++++++++ .../functional/builtins/codegen/test_slice.py | 31 ++++++++++++++++ .../codegen/types/numbers/test_sqrt.py | 19 ++++++++++ 3 files changed, 87 insertions(+) diff --git a/tests/functional/builtins/codegen/test_create_functions.py b/tests/functional/builtins/codegen/test_create_functions.py index ce832cd3cb..ef0d4c51dd 100644 --- a/tests/functional/builtins/codegen/test_create_functions.py +++ b/tests/functional/builtins/codegen/test_create_functions.py @@ -670,6 +670,43 @@ def test(target: address): assert test.foo() == 12 +def test_blueprint_evals_once_side_effects( + get_contract, deploy_blueprint_for, w3 +): + # test msize allocator does not get trampled by salt= kwarg + code = """ +foo: public(uint256) + """ + + deployer_code = f""" +created_address: public(address) +deployed: public(uint256) + +@external +def get() -> Bytes[32]: + self.deployed += 1 + return b'' + +@external +def create_(target: address): + self.created_address = create_from_blueprint(target, raw_call(self, method_id("get()"), max_outsize=32), raw_args=True, code_offset=3) + """ + + foo_contract = get_contract(code) + expected_runtime_code = w3.eth.get_code(foo_contract.address) + + f, FooContract = deploy_blueprint_for(code) + + d = get_contract(deployer_code) + + d.create_(f.address, transact={}) + + test = FooContract(d.created_address()) + assert w3.eth.get_code(test.address) == expected_runtime_code + assert test.foo() == 0 + assert d.deployed() == 1 + + def test_create_copy_of_complex_kwargs(get_contract, w3): # test msize allocator does not get trampled by salt= kwarg complex_salt = """ diff --git a/tests/functional/builtins/codegen/test_slice.py b/tests/functional/builtins/codegen/test_slice.py index 03dc7cc56d..5bb3596bf8 100644 --- a/tests/functional/builtins/codegen/test_slice.py +++ b/tests/functional/builtins/codegen/test_slice.py @@ -531,3 +531,34 @@ def test_slice_buffer_oob_reverts(bad_code, get_contract, tx_failed): c = get_contract(bad_code) with tx_failed(): c.do_slice() + + +def test_slice_adhoc_locs_evals_once_side_effects(get_contract): + code = """ +@external +def ret10_slice() -> Bytes[10]: + b: Bytes[32] = concat(convert(65, bytes32), b'') + c: Bytes[10] = slice(b, 31, 1) + return c + """ + + c = get_contract(code) + assert c.ret10_slice() == b"A" + + +def test_sqrt_evals_once_side_effects(get_contract): + code = """ +l: DynArray[uint256, 5] + +@external +def foo(cs: String[64]) -> uint256: + for i: uint256 in range(5): + self.l.append(1) + assert len(self.l) == 5 + s: Bytes[64] = b"" + s = slice(msg.data, self.l.pop(), 3) + return len(self.l) + """ + arg = "a" * 64 + c = get_contract(code) + assert c.foo(arg) == 4 \ No newline at end of file diff --git a/tests/functional/codegen/types/numbers/test_sqrt.py b/tests/functional/codegen/types/numbers/test_sqrt.py index 94676404e8..7eb9d03fbe 100644 --- a/tests/functional/codegen/types/numbers/test_sqrt.py +++ b/tests/functional/codegen/types/numbers/test_sqrt.py @@ -164,3 +164,22 @@ def test_sqrt_valid_range(sqrt_contract, value): def test_sqrt_invalid_range(sqrt_contract, value): with pytest.raises(TransactionFailed): sqrt_contract.test(decimal_to_int(value)) + + +def test_sqrt_evals_once_side_effects(get_contract): + code = """ +c: uint256 + +@internal +def some_decimal() -> decimal: + self.c += 1 + return 1.0 + +@external +def foo() -> uint256: + k: decimal = sqrt(self.some_decimal()) + return self.c + """ + + c = get_contract(code) + assert c.foo() == 1 From 130bd81e6ef8dcaf2c2d7030ee24e1bcbbc72aed Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 26 Apr 2024 15:02:26 +0200 Subject: [PATCH 06/13] lint --- .../builtins/codegen/test_create_functions.py | 12 +++++++----- tests/functional/builtins/codegen/test_slice.py | 2 +- vyper/builtins/functions.py | 13 +++++++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/functional/builtins/codegen/test_create_functions.py b/tests/functional/builtins/codegen/test_create_functions.py index ef0d4c51dd..7ae8afefc5 100644 --- a/tests/functional/builtins/codegen/test_create_functions.py +++ b/tests/functional/builtins/codegen/test_create_functions.py @@ -670,15 +670,13 @@ def test(target: address): assert test.foo() == 12 -def test_blueprint_evals_once_side_effects( - get_contract, deploy_blueprint_for, w3 -): +def test_blueprint_evals_once_side_effects(get_contract, deploy_blueprint_for, w3): # test msize allocator does not get trampled by salt= kwarg code = """ foo: public(uint256) """ - deployer_code = f""" + deployer_code = """ created_address: public(address) deployed: public(uint256) @@ -689,7 +687,11 @@ def get() -> Bytes[32]: @external def create_(target: address): - self.created_address = create_from_blueprint(target, raw_call(self, method_id("get()"), max_outsize=32), raw_args=True, code_offset=3) + self.created_address = create_from_blueprint( + target, + raw_call(self, method_id("get()"), max_outsize=32), + raw_args=True, code_offset=3 + ) """ foo_contract = get_contract(code) diff --git a/tests/functional/builtins/codegen/test_slice.py b/tests/functional/builtins/codegen/test_slice.py index 5bb3596bf8..dc0cad2c28 100644 --- a/tests/functional/builtins/codegen/test_slice.py +++ b/tests/functional/builtins/codegen/test_slice.py @@ -561,4 +561,4 @@ def foo(cs: String[64]) -> uint256: """ arg = "a" * 64 c = get_contract(code) - assert c.foo(arg) == 4 \ No newline at end of file + assert c.foo(arg) == 4 diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 5d1e1d98a6..39a36d8ae4 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -250,8 +250,9 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: # allocate a buffer for the return value np = context.new_internal_variable(dst_typ) with start.cache_when_complex("start") as (b1, start), length.cache_when_complex("length") as ( - b2, - length): + b2, + length, + ): # `msg.data` by `calldatacopy` if sub.value == "~calldata": node = [ @@ -1820,7 +1821,9 @@ def _build_create_IR( argslen = get_bytearray_length(arg) bufsz = arg.typ.maxlen return b1.resolve( - self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure) + self._helper( + argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure + ) ) else: # encode the varargs @@ -1836,7 +1839,9 @@ def _build_create_IR( # return a complex expression which writes to memory and returns # the length of the encoded data argslen = abi_encode(argbuf, to_encode, context, bufsz=bufsz, returns_len=True) - return self._helper(argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure) + return self._helper( + argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure + ) def _helper(self, argbuf, bufsz, target, value, salt, argslen, code_offset, revert_on_failure): # NOTE: we need to invoke the abi encoder before evaluating MSIZE, From 45c5d3d2fd34eb88d91d6805a40c2819933782c5 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Sun, 12 May 2024 16:29:43 +0200 Subject: [PATCH 07/13] remove w3 reference --- .../functional/builtins/codegen/test_create_functions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/builtins/codegen/test_create_functions.py b/tests/functional/builtins/codegen/test_create_functions.py index 668ed67568..19bea22a99 100644 --- a/tests/functional/builtins/codegen/test_create_functions.py +++ b/tests/functional/builtins/codegen/test_create_functions.py @@ -665,7 +665,7 @@ def test(target: address): assert test.foo() == 12 -def test_blueprint_evals_once_side_effects(get_contract, deploy_blueprint_for, w3): +def test_blueprint_evals_once_side_effects(get_contract, deploy_blueprint_for, env): # test msize allocator does not get trampled by salt= kwarg code = """ foo: public(uint256) @@ -690,16 +690,16 @@ def create_(target: address): """ foo_contract = get_contract(code) - expected_runtime_code = w3.eth.get_code(foo_contract.address) + expected_runtime_code = env.get_code(foo_contract.address) f, FooContract = deploy_blueprint_for(code) d = get_contract(deployer_code) - d.create_(f.address, transact={}) + d.create_(f.address) test = FooContract(d.created_address()) - assert w3.eth.get_code(test.address) == expected_runtime_code + assert env.get_code(test.address) == expected_runtime_code assert test.foo() == 0 assert d.deployed() == 1 From df7c7c6a60379ac93ae827f7386400f5b2802238 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Sun, 12 May 2024 16:30:06 +0200 Subject: [PATCH 08/13] lint --- vyper/builtins/functions.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 1acdc82884..c90af086b4 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -244,8 +244,6 @@ def _make_slice_bounds_check(start, length, src_len): return b1.resolve(b2.resolve(["assert", ok])) - - def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: Context) -> IRnode: assert length.is_literal, "typechecker failed" assert isinstance(length.value, int) # mypy hint @@ -255,8 +253,8 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: buf = context.new_internal_variable(dst_typ) with start.cache_when_complex("start") as (b1, start), length.cache_when_complex("length") as ( - b2, - length, + b2, + length, ): # `msg.data` by `calldatacopy` if sub.value == "~calldata": From 3f60a78a879fd0a71ca800954e403291c1547059 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 15 May 2024 16:49:51 -0400 Subject: [PATCH 09/13] use scope_multi --- vyper/builtins/functions.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 4b460e11d5..f649f35148 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -252,10 +252,7 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: # allocate a buffer for the return value buf = context.new_internal_variable(dst_typ) - with start.cache_when_complex("start") as (b1, start), length.cache_when_complex("length") as ( - b2, - length, - ): + with scope_multi((start, length), ("start", "length") as (b1, start, length): # `msg.data` by `calldatacopy` if sub.value == "~calldata": node = [ @@ -294,7 +291,7 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: assert isinstance(length.value, int) # mypy hint ret = IRnode.from_list(node, typ=BytesT(length.value), location=MEMORY) - return b1.resolve(b2.resolve(ret)) + return b1.resolve(ret) # note: this and a lot of other builtins could be refactored to accept any uint type From 05f3d4bb47c2c102aae3f0f69f30909005cddc05 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 15 May 2024 16:53:26 -0400 Subject: [PATCH 10/13] fix lint --- vyper/builtins/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index f649f35148..280aaea266 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -252,7 +252,7 @@ def _build_adhoc_slice_node(sub: IRnode, start: IRnode, length: IRnode, context: # allocate a buffer for the return value buf = context.new_internal_variable(dst_typ) - with scope_multi((start, length), ("start", "length") as (b1, start, length): + with scope_multi((start, length), ("start", "length")) as (b1, (start, length)): # `msg.data` by `calldatacopy` if sub.value == "~calldata": node = [ From 0c9ba6d003b9ba7d43b6446cf6a4a327434d3eeb Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 15 May 2024 16:57:36 -0400 Subject: [PATCH 11/13] remove redundant test, update test names --- .../functional/builtins/codegen/test_slice.py | 20 ++++--------------- .../codegen/types/numbers/test_sqrt.py | 2 +- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/tests/functional/builtins/codegen/test_slice.py b/tests/functional/builtins/codegen/test_slice.py index 781447817b..6dc3c682de 100644 --- a/tests/functional/builtins/codegen/test_slice.py +++ b/tests/functional/builtins/codegen/test_slice.py @@ -538,30 +538,18 @@ def test_slice_buffer_oob_reverts(bad_code, get_contract, tx_failed): c.do_slice() -def test_slice_adhoc_locs_evals_once_side_effects(get_contract): - code = """ -@external -def ret10_slice() -> Bytes[10]: - b: Bytes[32] = concat(convert(65, bytes32), b'') - c: Bytes[10] = slice(b, 31, 1) - return c - """ - - c = get_contract(code) - assert c.ret10_slice() == b"A" - - -def test_sqrt_evals_once_side_effects(get_contract): +def test_slice_length_eval_once(get_contract): code = """ l: DynArray[uint256, 5] @external def foo(cs: String[64]) -> uint256: - for i: uint256 in range(5): - self.l.append(1) + self.l = [1, 1, 1, 1, 1] assert len(self.l) == 5 + s: Bytes[64] = b"" s = slice(msg.data, self.l.pop(), 3) + return len(self.l) """ arg = "a" * 64 diff --git a/tests/functional/codegen/types/numbers/test_sqrt.py b/tests/functional/codegen/types/numbers/test_sqrt.py index 1b65ef283d..cf62cecdab 100644 --- a/tests/functional/codegen/types/numbers/test_sqrt.py +++ b/tests/functional/codegen/types/numbers/test_sqrt.py @@ -165,7 +165,7 @@ def test_sqrt_invalid_range(tx_failed, sqrt_contract, value): sqrt_contract.test(decimal_to_int(value)) -def test_sqrt_evals_once_side_effects(get_contract): +def test_sqrt_eval_once(get_contract): code = """ c: uint256 From 20f7f34da29a5db761e1dbffdfd9fc897114b816 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Thu, 16 May 2024 20:03:59 +0200 Subject: [PATCH 12/13] add tests for all slice's adhoc paths --- .../functional/builtins/codegen/test_slice.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/functional/builtins/codegen/test_slice.py b/tests/functional/builtins/codegen/test_slice.py index 6dc3c682de..e24a3115c2 100644 --- a/tests/functional/builtins/codegen/test_slice.py +++ b/tests/functional/builtins/codegen/test_slice.py @@ -538,20 +538,28 @@ def test_slice_buffer_oob_reverts(bad_code, get_contract, tx_failed): c.do_slice() -def test_slice_length_eval_once(get_contract): - code = """ -l: DynArray[uint256, 5] +# tests all 3 adhoc locations: `msg.data`, `self.code`, `
.code` +@pytest.mark.parametrize("adhoc_loc", ["msg.data", "self.code", "a.code"]) +def test_slice_start_eval_once(get_contract, adhoc_loc): + code = f""" +counter: uint256 + +@internal +def bar() -> uint256: + self.counter += 1 + return 1 @external def foo(cs: String[64]) -> uint256: - self.l = [1, 1, 1, 1, 1] - assert len(self.l) == 5 - s: Bytes[64] = b"" - s = slice(msg.data, self.l.pop(), 3) - - return len(self.l) + # use `a` to exercise the path with `
.code` + a: address = self + s = slice({adhoc_loc}, self.bar(), 3) + + return self.counter """ + arg = "a" * 64 c = get_contract(code) - assert c.foo(arg) == 4 + # ensure that counter was incremented only once + assert c.foo(arg) == 1 From 7e4e4b7b99a03cff3158424f5a664781a1e2ba06 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Thu, 16 May 2024 20:09:47 +0200 Subject: [PATCH 13/13] lint --- tests/functional/builtins/codegen/test_slice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional/builtins/codegen/test_slice.py b/tests/functional/builtins/codegen/test_slice.py index e24a3115c2..08800e7a8c 100644 --- a/tests/functional/builtins/codegen/test_slice.py +++ b/tests/functional/builtins/codegen/test_slice.py @@ -555,7 +555,6 @@ def foo(cs: String[64]) -> uint256: # use `a` to exercise the path with `
.code` a: address = self s = slice({adhoc_loc}, self.bar(), 3) - return self.counter """