diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index ca326a0d54..14f299b89f 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -4,6 +4,7 @@ from tests.evm_backends.base_env import EvmError, ExecutionReverted from tests.utils import decimal_to_int from vyper.compiler import compile_code +from vyper.compiler.settings import Settings from vyper.exceptions import ArgumentException, StructureException from vyper.utils import method_id @@ -478,6 +479,14 @@ def foo(x: Bytes[32]): ( """ @external +def foo(data: Bytes[4]) -> (uint256, uint256): + return abi_decode(data, (uint256, uint256)) + """, + StructureException, # Size of input data is smaller than expected output + ), + ( + """ +@external def foo(x: Bytes[32]): _abi_decode(x) """, @@ -491,6 +500,19 @@ def test_abi_decode_length_mismatch(get_contract, assert_compile_failed, bad_cod assert_compile_failed(lambda: get_contract(bad_code), exception) +def test_abi_decode_undersized_buffer_venom(): + code = """ +@external +def foo(data: Bytes[4]) -> (uint256, uint256): + return abi_decode(data, (uint256, uint256)) + """ + + with pytest.raises(StructureException): + compile_code( + code, output_formats=["bytecode"], settings=Settings(experimental_codegen=True) + ) + + def _abi_payload_from_tuple(payload: tuple[int | bytes, ...], max_sz: int) -> bytes: ret = b"".join(p.to_bytes(32, "big") if isinstance(p, int) else p for p in payload) assert len(ret) <= max_sz diff --git a/vyper/codegen_venom/builtins/abi.py b/vyper/codegen_venom/builtins/abi.py index a7496c5093..d0cb61b919 100644 --- a/vyper/codegen_venom/builtins/abi.py +++ b/vyper/codegen_venom/builtins/abi.py @@ -15,7 +15,7 @@ from vyper.codegen_venom.abi import abi_decode_to_buf, abi_encode_to_buf from vyper.codegen_venom.buffer import Buffer, Ptr from vyper.codegen_venom.value import VyperValue -from vyper.exceptions import CompilerPanic +from vyper.exceptions import CompilerPanic, StructureException from vyper.semantics.data_locations import DataLocation from vyper.semantics.types import BytesT, TupleT from vyper.utils import fourbytes_to_int @@ -211,6 +211,19 @@ def lower_abi_decode(node: vy_ast.Call, ctx: VenomCodegenContext) -> VyperValue: if unwrap_tuple: wrapped_typ = calculate_type_for_external_return(output_typ) + # Compile-time check that the input buffer can fit the decoded type + abi_size_bound = wrapped_typ.abi_type.size_bound() + input_max_len = data_node._metadata["type"].maxlen + if input_max_len < abi_size_bound: + raise StructureException( + ( + "Mismatch between size of input and size of decoded types. " + f"length of ABI-encoded {wrapped_typ} must be equal to or greater " + f"than {abi_size_bound}" + ), + node.args[0], + ) + # Get data pointer and length data_vv = Expr(data_node, ctx).lower() data = ctx.unwrap(data_vv) # Copies storage/transient to memory