Vyper 0.4.0: Mandatory @external/@internal Decorators Break Legacy Contracts
0 reputation · 13 Jul 2020, 06:05 UTC
0 reputation · 13 Jul 2020, 06:05 UTC
Upgrade a legacy Vyper 0.3.x codebase to 0.4.x while preserving public API compatibility.
In 0.4.0 every function must be annotated with either @external or @internal; the previous implicit public default is no longer accepted. This change can silently alter contract behavior if a function intended to be externally callable is left unannotated, leading to compile failures or unintended visibility. Additionally, the new module system replaces some of the old inheritance‑free patterns, raising questions about how to restructure shared logic.
In Vyper 0.4.0 every function must be explicitly marked with either @external or @internal. The compiler will reject any function lacking a decorator, and any function that was implicitly public in 0.3.x will become invisible unless you add the appropriate annotation. The safest path is to perform a systematic, automated scan, annotate every missing decorator, then run the full test suite to confirm that public API behavior is unchanged.
@external during bulk updates.vyper --strict-visibility on the entire codebase. This flags every function that still relies on implicit visibility.
vyper --strict-visibility --source-dir ./contracts
@external into every unannotated function that is called from the contract’s public API. For functions that are only used internally, annotate with @internal.
import re, pathlib
# Simplified example – adjust regex for your code style
for file in pathlib.Path("./contracts").rglob("*.vy"):
text = file.read_text()
def annotate(match):
return f"@external\n{match.group(0)}"
new_text = re.sub(r"^def ", annotate, text, flags=re.MULTILINE)
file.write_text(new_text)
git diff to ensure only visibility annotations changed. Pay special attention to functions that interact with other contracts or are called via low‑level call.
git diff --stat
vyper --strict-visibility --source-dir ./contracts
@external or @internal.
pytest tests/ -v
utils.vy) that contains shared functions.from utils import * or specific names.vyper --modules to validate module imports.
vyper --output-bytecode and inspect the bytecode selectors to ensure internal functions are not exposed.The script above performs a bulk annotation, but you must manually verify that functions intended to be internal are not marked @external. A pragmatic approach is to start with @external for all missing decorators, run the test suite, and then flip any that fail to @internal.
Tests or scripts that call functions by name (e.g., via contract_instance.function_name()) will now compile only if the function is annotated. If a test previously invoked a function that was implicitly public, you’ll need to add the decorator and update any import paths if modules were introduced.
Modules reduce duplication and make shared logic easier to maintain, but they require a refactor of state variables and function signatures. If your codebase is small or the shared logic is trivial, flattening may be acceptable. For larger projects, the module system offers better long‑term maintainability and aligns with 0.4.x best practices.
To fine‑tune the annotation strategy, could you share a snippet of a function that is currently unannotated but is used internally? That will help determine whether it should be @internal or @external in the migrated code.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.