VYPR
High severityNVD Advisory· Published May 11, 2023· Updated Jan 24, 2025

Vyper vulnerable to incorrect ordering of arguments for kwargs passed to internal calls

CVE-2023-32059

Description

Vyper is a Pythonic smart contract language for the Ethereum virtual machine. Prior to version 0.3.8, internal calls with default arguments are compiled incorrectly. Depending on the number of arguments provided in the call, the defaults are added not right-to-left, but left-to-right. If the types are incompatible, typechecking is bypassed. The ability to pass kwargs to internal functions is an undocumented feature that is not well known about. The issue is patched in version 0.3.8.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Vyper prior to 0.3.8 incorrectly compiles internal calls with default arguments, potentially bypassing type checking.

Vulnerability

Summary Vyper, a Pythonic smart contract language for the Ethereum Virtual Machine (EVM), contains a compilation flaw in versions prior to 0.3.8. When internal functions are called with default arguments, the compiler incorrectly applies defaults left-to-right instead of the expected right-to-left order. This mishandling can cause type mismatches to be overlooked, bypassing the language's type-checking mechanism [1][3].

Attack

Vector and Exploitation The vulnerability resides in an undocumented feature that allows keyword arguments (kwargs) to be passed to internal functions [1]. An attacker who can influence the arguments supplied to such a function might exploit this ordering defect. Because the feature is not widely known, contracts relying on default arguments in internal calls are at risk if they assume the compiler applies defaults correctly. The attack does not require special network access beyond the ability to interact with the vulnerable smart contract [2][3].

Impact

If successfully exploited, this bug could lead to unexpected behavior in Vyper smart contracts. The bypass of type checking may allow arguments to be interpreted as types incompatible with the function signature, potentially leading to logical errors, fund loss, or other unintended contract states. The severity depends on how the vulnerable contract uses default arguments in internal functions [1][4].

Mitigation

The issue has been patched in Vyper version 0.3.8. Users are advised to upgrade all Vyper compiler installations to this version or later. No workarounds are documented; updating the compiler is the recommended course of action [1][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vyperPyPI
< 0.3.80.3.8

Affected products

1

Patches

1
c3e68c302aa6

Merge pull request from GHSA-ph9x-4vc9-m39g

https://github.com/vyperlang/vyperCharles CooperMay 11, 2023via ghsa
2 files changed · +63 3
  • tests/parser/features/test_internal_call.py+62 0 modified
    @@ -1,6 +1,9 @@
    +import string
     from decimal import Decimal
     
    +import hypothesis.strategies as st
     import pytest
    +from hypothesis import given, settings
     
     from vyper.compiler import compile_code
     from vyper.exceptions import ArgumentException, CallViolation
    @@ -642,3 +645,62 @@ def bar() -> String[6]:
         c = get_contract_with_gas_estimation(contract)
     
         assert c.bar() == "hello"
    +
    +
    +# TODO probably want to refactor these into general test utils
    +st_uint256 = st.integers(min_value=0, max_value=2**256 - 1)
    +st_string65 = st.text(max_size=65, alphabet=string.printable)
    +st_bytes65 = st.binary(max_size=65)
    +st_sarray3 = st.lists(st_uint256, min_size=3, max_size=3)
    +st_darray3 = st.lists(st_uint256, max_size=3)
    +
    +internal_call_kwargs_cases = [
    +    ("uint256", st_uint256),
    +    ("String[65]", st_string65),
    +    ("Bytes[65]", st_bytes65),
    +    ("uint256[3]", st_sarray3),
    +    ("DynArray[uint256, 3]", st_darray3),
    +]
    +
    +
    +@pytest.mark.parametrize("typ1,strategy1", internal_call_kwargs_cases)
    +@pytest.mark.parametrize("typ2,strategy2", internal_call_kwargs_cases)
    +def test_internal_call_kwargs(get_contract, typ1, strategy1, typ2, strategy2):
    +    # GHSA-ph9x-4vc9-m39g
    +
    +    @given(kwarg1=strategy1, default1=strategy1, kwarg2=strategy2, default2=strategy2)
    +    @settings(deadline=None, max_examples=5)  # len(cases) * len(cases) * 5 * 5
    +    def fuzz(kwarg1, kwarg2, default1, default2):
    +        code = f"""
    +@internal
    +def foo(a: {typ1} = {repr(default1)}, b: {typ2} = {repr(default2)}) -> ({typ1}, {typ2}):
    +    return a, b
    +
    +@external
    +def test0() -> ({typ1}, {typ2}):
    +    return self.foo()
    +
    +@external
    +def test1() -> ({typ1}, {typ2}):
    +    return self.foo({repr(kwarg1)})
    +
    +@external
    +def test2() -> ({typ1}, {typ2}):
    +    return self.foo({repr(kwarg1)}, {repr(kwarg2)})
    +
    +@external
    +def test3(x1: {typ1}) -> ({typ1}, {typ2}):
    +    return self.foo(x1)
    +
    +@external
    +def test4(x1: {typ1}, x2: {typ2}) -> ({typ1}, {typ2}):
    +    return self.foo(x1, x2)
    +        """
    +        c = get_contract(code)
    +        assert c.test0() == [default1, default2]
    +        assert c.test1() == [kwarg1, default2]
    +        assert c.test2() == [kwarg1, kwarg2]
    +        assert c.test3(kwarg1) == [kwarg1, default2]
    +        assert c.test4(kwarg1, kwarg2) == [kwarg1, kwarg2]
    +
    +    fuzz()
    
  • vyper/codegen/context.py+1 3 modified
    @@ -267,10 +267,8 @@ def _check(cond, s="Unreachable"):
             # _check(all(l.typ == r.typ for (l, r) in zip(args_ir, sig.args))
     
             num_provided_kwargs = len(args_ir) - len(sig.base_args)
    -        num_kwargs = len(sig.default_args)
    -        kwargs_needed = num_kwargs - num_provided_kwargs
     
    -        kw_vals = list(sig.default_values.values())[:kwargs_needed]
    +        kw_vals = list(sig.default_values.values())[num_provided_kwargs:]
     
             return sig, kw_vals
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.