diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index 2a0f4e77e5..591ec3606d 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -1903,3 +1903,55 @@ def foo(): c = get_contract(code) with tx_failed(): c.foo() + + +# should not trip risky overlap detector +def test_risky_call_precompile(): + code = """ +x: DynArray[uint256, 32] + +@deploy +def __init__(): + self.x = [1, 1, 1] + +@internal +def bar() -> uint256: + self.x[0] = 1 + return 0 + +@external +def foo() -> uint256: + self.x[ + abi_decode( + slice( + concat( + abi_encode( + empty(uint256), + method_id=method_id("foo()")), + empty(bytes32) + ), 0, 32), + (uint256) + )] = self.bar() + return 0 + """ + assert compile_code(code) is not None + + +# should not trip risky overlap detector +def test_risky_call_precompile2(): + code = """ +x: DynArray[uint256, 32] + +@internal +def bar() -> uint256: + a: DynArray[uint256, 100] = [] + b: DynArray[uint256, 100] = [] + a = b + return 0 + +@external +def foo() -> uint256: + c: uint256 = self.x[self.bar()] + return 0 + """ + assert compile_code(code) is not None diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 6f9eb0359b..0d2914b9c2 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -479,9 +479,29 @@ def variable_writes(self): return ret + @property + def is_call_opcode(self): + return self.value in ("call", "staticcall", "delegatecall") + + @property + def is_precompile_call(self): + # whitelist for "safe" calls + assert self.is_call_opcode + target = self.args[1].value + PRECOMPILES = ( + 0x01, # builtin ecrecover + 0x02, # builtin sha256 + 0x04, # identify precompile + 0x06, # builtin ecadd + 0x07, # builtin ecmul + 0x000000000000000000636F6E736F6C652E6C6F67, # builtin print + ) + return isinstance(target, int) and target in PRECOMPILES + @cached_property def contains_risky_call(self): - ret = self.value in ("call", "delegatecall", "staticcall", "create", "create2") + ret = self.value in ("create", "create2") + ret |= self.is_call_opcode and not self.is_precompile_call for arg in self.args: ret |= arg.contains_risky_call