diff --git a/tests/parser/features/test_init.py b/tests/parser/features/test_init.py index 3ef7a440a5..feeabe311a 100644 --- a/tests/parser/features/test_init.py +++ b/tests/parser/features/test_init.py @@ -15,12 +15,13 @@ def __init__(a: uint256): assert c.val() == 123 # Make sure the init code does not access calldata - opcodes = vyper.compile_code(code, ["opcodes"])["opcodes"].split(" ") - ir_return_idx = opcodes.index("JUMP") + assembly = vyper.compile_code(code, ["asm"])["asm"].split(" ") + ir_return_idx_start = assembly.index("{") + ir_return_idx_end = assembly.index("}") - assert "CALLDATALOAD" in opcodes - assert "CALLDATACOPY" not in opcodes[:ir_return_idx] - assert "CALLDATALOAD" not in opcodes[:ir_return_idx] + assert "CALLDATALOAD" in assembly + assert "CALLDATACOPY" not in assembly[:ir_return_idx_start] + assembly[ir_return_idx_end:] + assert "CALLDATALOAD" not in assembly[:ir_return_idx_start] + assembly[ir_return_idx_end:] def test_init_calls_internal(get_contract, assert_compile_failed, assert_tx_failed): diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index 202c75a448..6de5bab16f 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -123,7 +123,24 @@ def handler_for(calldata_kwargs, default_kwargs): ret.append(["goto", sig.external_function_base_entry_label]) - ret = ["if", ["eq", "_calldata_method_id", method_id], ret] + method_id_check = ["eq", "_calldata_method_id", method_id] + + # if there is a function whose selector is 0, it won't be distinguished + # from the case where nil calldata is supplied, b/c calldataload loads + # 0s past the end of physical calldata (cf. yellow paper). + # since supplying 0 calldata is expected to trigger the fallback fn, + # we check that calldatasize > 0, which distinguishes the 0 selector + # from the fallback function "selector" + # (equiv. to "all selectors not in the selector table"). + + # note: cases where not enough calldata is supplied (besides + # calldatasize==0) are not addressed here b/c a calldatasize + # well-formedness check is already present in the function body + # as part of abi validation + if method_id.value == 0: + method_id_check = ["and", ["gt", "calldatasize", 0], method_id_check] + + ret = ["if", method_id_check, ret] return ret ret = ["seq"] diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index fdca675819..71f9ed552d 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -120,9 +120,6 @@ def _runtime_ir(runtime_functions, all_sigs, global_ctx): runtime = [ "seq", - # check that calldatasize is at least 4, otherwise - # calldataload will load zeros (cf. yellow paper). - ["if", ["lt", "calldatasize", 4], ["goto", "fallback"]], ["with", "_calldata_method_id", shr(224, ["calldataload", 0]), selector_section], close_selector_section, ["label", "fallback", ["var_list"], fallback_ir],