Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tests/functional/builtins/codegen/test_abi_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -478,6 +479,14 @@ def foo(x: Bytes[32]):
(
"""
@external
def foo(data: Bytes[4]) -> (uint256, uint256):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test passes also in the master why was the change in logic necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right - the folded table row passes on master in a default local run because that path uses legacy codegen. The regression is only in Venom.

I added test_abi_decode_undersized_buffer_venom, which forces Settings(experimental_codegen=True): on origin/master the reproducer compiles, while this branch raises StructureException. I also updated the PR body to make that distinction explicit.

Validation:

uv run --python 3.12 -m pytest tests/functional/builtins/codegen/test_abi_decode.py -k "length_mismatch or undersized_buffer_venom" -q
uv run --python 3.12 -m pytest tests/functional/builtins/codegen/test_abi_decode.py -k "length_mismatch or undersized_buffer_venom" --experimental-codegen -q
uv run --python 3.12 black --check tests/functional/builtins/codegen/test_abi_decode.py vyper/codegen_venom/builtins/abi.py
git diff --check

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)
""",
Expand All @@ -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
Expand Down
15 changes: 14 additions & 1 deletion vyper/codegen_venom/builtins/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down