VYPR
High severityNVD Advisory· Published Jun 6, 2022· Updated Apr 22, 2025

Multiple evaluation of contract address in call in vyper

CVE-2022-29255

Description

Vyper is a Pythonic Smart Contract Language for the ethereum virtual machine. In versions prior to 0.3.4 when a calling an external contract with no return value, the contract address (including side effects) could be evaluated twice. This may result in incorrect outcomes for contracts. This issue has been addressed in v0.3.4.

AI Insight

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

In Vyper <0.3.4, an external call address expression is evaluated twice, potentially leading to incorrect contract behavior.

Vulnerability

In Vyper versions prior to 0.3.4, when a contract makes an external call to a function with no return value, the contract address expression (including any side effects in the address computation) is evaluated twice. This re-evaluation can lead to unexpected behavior in smart contracts. The issue was fixed in commit 6b4d8ff and released in version 0.3.4 [1][3].

Exploitation

An attacker does not require direct network access or special privileges; the vulnerability arises purely from the Vyper compiler's code generation logic. A contract that calls an external address that itself performs side effects (e.g., incrementing a counter) when evaluated will see those side effects occur twice. The attacker would need to deploy a contract whose address expression triggers side effects, or interact with a victim contract that uses such an expression [2][3].

Impact

Successful exploitation results in the external contract address being evaluated twice, causing any side effects within that expression to execute an additional time. This can lead to incorrect contract state, such as doubled counter increments, or other logic errors depending on the address expression [1][3].

Mitigation

The vulnerability is fixed in Vyper version 0.3.4. Users should upgrade to the latest released version. No workarounds are documented; contracts making external calls with no return value may be affected. The CVE is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog [1][4].

AI Insight generated on May 21, 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.40.3.4

Affected products

1

Patches

1
6b4d8ff185de

Merge pull request from GHSA-4v9q-cgpw-cf38

https://github.com/vyperlang/vyperCharles CooperJun 6, 2022via ghsa
2 files changed · +56 9
  • tests/parser/features/external_contracts/test_external_contract_calls.py+41 0 modified
    @@ -2411,3 +2411,44 @@ def bar(foo: Foo):
         # fails due to returndatasize being nonzero but also lt 64
         assert_tx_failed(lambda: c.bar(bad_1.address))
         c.bar(bad_2.address)
    +
    +
    +def test_contract_address_evaluation(get_contract):
    +    callee_code = """
    +# implements: Foo
    +
    +interface Counter:
    +    def increment_counter(): nonpayable
    +
    +@external
    +def foo():
    +    pass
    +
    +@external
    +def bar() -> address:
    +    Counter(msg.sender).increment_counter()
    +    return self
    +    """
    +    code = """
    +# implements: Counter
    +
    +interface Foo:
    +    def foo(): nonpayable
    +    def bar() -> address: nonpayable
    +
    +counter: uint256
    +
    +@external
    +def increment_counter():
    +    self.counter += 1
    +
    +@external
    +def do_stuff(f: Foo) -> uint256:
    +    Foo(f.bar()).foo()
    +    return self.counter
    +    """
    +
    +    c1 = get_contract(code)
    +    c2 = get_contract(callee_code)
    +
    +    assert c1.do_stuff(c2.address) == 1
    
  • vyper/codegen/external_call.py+15 9 modified
    @@ -168,15 +168,7 @@ def _extcodesize_check(address):
         return ["assert", ["extcodesize", address]]
     
     
    -def ir_for_external_call(call_expr, context):
    -    from vyper.codegen.expr import Expr  # TODO rethink this circular import
    -
    -    contract_address = Expr.parse_value_expr(call_expr.func.value, context)
    -    call_kwargs = _parse_kwargs(call_expr, context)
    -    args_ir = [Expr(x, context).ir_node for x in call_expr.args]
    -
    -    assert isinstance(contract_address.typ, InterfaceType)
    -
    +def _external_call_helper(contract_address, args_ir, call_kwargs, call_expr, context):
         # expr.func._metadata["type"].return_type is more accurate
         # than fn_sig.return_type in the case of JSON interfaces.
         fn_type = call_expr.func._metadata["type"]
    @@ -223,3 +215,17 @@ def ir_for_external_call(call_expr, context):
             ret.append(ret_unpacker)
     
         return IRnode.from_list(ret, typ=return_t, location=MEMORY)
    +
    +
    +def ir_for_external_call(call_expr, context):
    +    from vyper.codegen.expr import Expr  # TODO rethink this circular import
    +
    +    contract_address = Expr.parse_value_expr(call_expr.func.value, context)
    +    assert isinstance(contract_address.typ, InterfaceType)
    +    args_ir = [Expr(x, context).ir_node for x in call_expr.args]
    +    call_kwargs = _parse_kwargs(call_expr, context)
    +
    +    with contract_address.cache_when_complex("external_contract") as (b1, contract_address):
    +        return b1.resolve(
    +            _external_call_helper(contract_address, args_ir, call_kwargs, call_expr, context)
    +        )
    

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.