From 133c0ec2752a0724c6d2d4d62f8637b33d41bd49 Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:35:23 +0400 Subject: [PATCH 1/4] fix[venom]: reject statically undersized abi_decode input --- .../builtins/codegen/test_abi_decode.py | 14 ++++++++++++++ vyper/codegen_venom/builtins/abi.py | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index ca326a0d54..cdc27356f2 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 @@ -491,6 +492,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(): + # GH issue 5055: the venom pipeline must reject statically + # undersized input buffers at compile time, like the legacy pipeline + code = """ +@external +def f(data: Bytes[4]) -> (uint256, uint256): + return abi_decode(data, (uint256, uint256)) + """ + settings = Settings(experimental_codegen=True) + with pytest.raises(StructureException, match="Mismatch between size of input"): + compile_code(code, settings=settings) + + 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 From 5f176e8c518f21f3630be965aff6c7892a76bde8 Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:38:32 +0400 Subject: [PATCH 2/4] test[venom]: rely on CI matrix for abi_decode size check --- tests/functional/builtins/codegen/test_abi_decode.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index cdc27356f2..90df19c4e6 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -4,7 +4,6 @@ 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 @@ -500,9 +499,8 @@ def test_abi_decode_undersized_buffer_venom(): def f(data: Bytes[4]) -> (uint256, uint256): return abi_decode(data, (uint256, uint256)) """ - settings = Settings(experimental_codegen=True) with pytest.raises(StructureException, match="Mismatch between size of input"): - compile_code(code, settings=settings) + compile_code(code) def _abi_payload_from_tuple(payload: tuple[int | bytes, ...], max_sz: int) -> bytes: From b0de8d88fd7daa6dd3a3d6444402edeeacb34e3a Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Tue, 16 Jun 2026 02:28:35 +0400 Subject: [PATCH 3/4] test[venom]: reuse abi_decode length mismatch coverage --- .../builtins/codegen/test_abi_decode.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index 90df19c4e6..6f6b5b01cf 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -478,6 +478,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,18 +499,6 @@ 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(): - # GH issue 5055: the venom pipeline must reject statically - # undersized input buffers at compile time, like the legacy pipeline - code = """ -@external -def f(data: Bytes[4]) -> (uint256, uint256): - return abi_decode(data, (uint256, uint256)) - """ - with pytest.raises(StructureException, match="Mismatch between size of input"): - compile_code(code) - - 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 From e7825c6074c82713bc2982fa9ec9453f2c619cae Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:25:58 +0400 Subject: [PATCH 4/4] test[venom]: add explicit abi_decode undersized regression --- .../functional/builtins/codegen/test_abi_decode.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index 6f6b5b01cf..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 @@ -499,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