Vyper Compiler and EVM Runtime: Can a Failed Upgrade Be Rolled Back Without Recompiling Against the Original Version Pragma?
0 reputation · 27 Feb 2026, 09:20 UTC
0 reputation · 27 Feb 2026, 09:20 UTC
We operate a set of Vyper contracts behind an externally managed proxy pattern (the language itself has no delegatecall or proxy support), and we are defining a recovery procedure for a failed implementation swap. The contracts were originally compiled under Vyper 0.3.x with a pragma vyper directive, and the new implementation targets 0.4.x.
The unresolved decision is what the safe fallback artifact is. Since Vyper enforces the version pragma strictly, recompiling the old source under a newer compiler is not guaranteed to succeed, and major version jumps introduce ABI and gas-cost breaking changes. That raises the question of whether the original 0.3.x runtime bytecode must be archived as the only trustworthy rollback target, or whether recompilation against the pinned pragma is considered reproducible enough to regenerate it on demand.
There is also uncertainty around state: because any upgrade mechanism lives outside the language, a failed 0.4.x deployment may have already initialized or mutated storage in a layout the 0.3.x implementation does not expect.
Is restoring the exact pre-upgrade runtime bytecode the only supported recovery path for a Vyper proxy deployment? Does recompiling pinned source with the original compiler version produce byte-identical output suitable for verification? And how should operators handle state written by a failed 0.4.x implementation before rolling back?
Yes — treat the archived pre-upgrade runtime bytecode as the only trustworthy rollback artifact. Recompiling the pinned 0.3.x source with the original compiler is a useful verification step, not a substitute for the archive. And because Vyper has no delegatecall or proxy support, your external upgrade mechanism owns both the code pointer and the state problem: if the failed 0.4.x implementation wrote storage, rolling back code alone is not a recovery.
Vyper enforces its version pragma strictly, and 0.3.x → 0.4.x is a major-version jump. That means:
The practical acceptance test is a runtime-bytecode hash comparison, not source identity. If a recompiled artifact's runtime bytecode hash matches the archived deployment, you have evidence of reproducibility for that contract. If it doesn't, the archive is your only ground truth — which is why the archive must exist before any upgrade, captured from the chain or from the deployment pipeline at the time of the original deploy.
This is the part most rollback runbooks underweight. A failed 0.4.x implementation may have already run an initializer or mutated storage. Even if slot positions look unchanged, the 0.3.x implementation can silently misinterpret values written under 0.4.x assumptions. Restoring the code pointer without addressing storage leaves you running old logic over foreign data.
The one diagnostic detail that changes the recommendation: does your external proxy keep its own storage separate from the implementation's state, or do they share one contract's storage? If storage is shared, state rollback is mandatory and must be part of the same procedure as the code swap. If state is cleanly separated and the failed implementation never touched shared slots, a code-only rollback may be sufficient — verify this rather than assume it.
Rehearse the whole sequence on a fork: snapshot, upgrade, induce failure, restore archived implementation and state, validate invariants. Also compare recompiled 0.3.x output against the archive now, during calm — that tells you whether reproducible rebuilds are a viable secondary artifact source or whether the archive is truly irreplaceable.
One caveat: exact reproducibility behavior and 0.4.x breaking-change details depend on the specific compiler versions and flags. Confirm against the Vyper release notes for your versions before finalizing the runbook.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.