Multiple evaluation of contract address in call in vyper
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.
| Package | Affected versions | Patched versions |
|---|---|---|
vyperPyPI | < 0.3.4 | 0.3.4 |
Affected products
1Patches
16b4d8ff185deMerge pull request from GHSA-4v9q-cgpw-cf38
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- github.com/advisories/GHSA-4v9q-cgpw-cf38ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-29255ghsaADVISORY
- github.com/pypa/advisory-database/tree/main/vulns/vyper/PYSEC-2022-43053.yamlghsaWEB
- github.com/vyperlang/vyper/commit/6b4d8ff185de071252feaa1c319712b2d6577f8dghsax_refsource_MISCWEB
- github.com/vyperlang/vyper/security/advisories/GHSA-4v9q-cgpw-cf38ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.