feat(core): first-class upgradeable contracts (UUPS/Transparent proxies) — #155 - #169
Merged
Merged
Conversation
…xies) — #155 Adds a declarative `upgradeable` field to `ContractEntry` (spec/types.ts, spec/schema.ts) supporting UUPS and Transparent proxy patterns, with cross-field validation (spec/validate.ts): initializer/proxyAdminOwner refs participate in ordering/cycle checks exactly like constructor args, initializer.function is validated as a plain Solidity identifier (INVALID_IDENTIFIER), and proxyAdminOwner is required for transparent proxies / rejected for uups (MISSING_PROXY_ADMIN_OWNER / UNUSED_PROXY_ADMIN_OWNER) — no unsafe defaults. compile/compile.ts composes the proxy by hand from Ignition primitives (ignition-core 0.15.15 has no m.proxy): impl deployment, an optional m.encodeFunctionCall initializer, the ERC1967Proxy/TransparentUpgradeableProxy future (artifact names overridable via CompileOptions.proxyArtifacts), and a final m.contractAt handle stored under the entry's own id so every downstream ref (and the compiled result) resolves to the PROXY, not the implementation. Also adds compileUpgrade() — a standalone v1 upgrade-flow compiler (UUPS: upgradeToAndCall; Transparent: ProxyAdmin.upgradeAndCall) — and deploy/proxyHistory.ts, a typed proxy-implementation-history data model with pure derive/append helpers (not wired into reader — future work). simulate.ts surfaces an `upgradeable` marker on PlannedStep and includes initializer/proxyAdminOwner refs in dependsOn, without changing output for non-upgradeable entries. Storage-layout safety is explicitly out of scope (v1) — documented in spec/types.ts, compile.ts, and the new packages/core/README.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
robercano
approved these changes
Jul 24, 2026
robercano
approved these changes
Jul 28, 2026
robercano
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds first-class upgradeable contracts (UUPS / Transparent proxies + an upgrade flow) to
@redeploy/core's declarative spec. Closes #155.Declare
upgradeableon aContractEntryand the compiler deploys the implementation + proxy (+ ProxyAdmin where applicable) using Hardhat Ignition primitives; downstreamrefs resolve to the proxy address.What changed (all within
packages/core)src/spec/types.ts):ContractEntry.upgradeable?: { kind: "uups" | "transparent"; initializer?: { function; args? }; proxyAdminOwner? }. Fully backward compatible.src/spec/schema.ts): zod validation of the new shape (initializer args reusecontractArgSchema).src/spec/validate.ts): new cross-field rules —MISSING_PROXY_ADMIN_OWNER(transparent requires an explicit owner — never silently defaulted),UNUSED_PROXY_ADMIN_OWNER(rejected on uups),INVALID_IDENTIFIER(initializer function must be a Solidity identifier); refs ininitializer.args/proxyAdminOwnerparticipate in ordering + cycle checks like constructor args.src/compile/compile.ts): emits the Ignition future chain — impl → optionalm.encodeFunctionCall→ERC1967Proxy(uups) /TransparentUpgradeableProxy(transparent) →m.contractAthandle (stored underentry.id, so refs resolve to the proxy). Proxy artifact names overridable viaCompileOptions.proxyArtifacts. AddscompileUpgrade()for the v1 upgrade flow (fresh future id required; explicit proxy-admin address for transparent — never guessed).src/simulate/simulate.ts):PlannedStep.upgradeablemarker;dependsOnfolds in initializer/proxyAdminOwner refs. Non-upgradeable output unchanged.src/deploy/proxyHistory.ts, new): typed proxy implementation-history model + pure derive/append helpers so a future@redeploy/readerchange can expose implementation history. Not wired into reader (separate module).packages/core/README.md, new): usage, uups vs transparent, refs-resolve-to-proxy, proxy-admin ownership, and the storage-layout caveat.Out of scope (per the issue)
packages/reader,packages/verify, orapps/studio(their proxy follow-ups are separate).Gates (independently verified on branch head
d247ecb)packages/core400/400 testspackages/core93.29% stmts / 91.69% branch / 98.46% funcs (threshold 80)Review
Four adversarial lenses (correctness, tests, security, performance) all APPROVED (consensus). Security confirmed proxy-admin ownership is a hard validation error (never silently defaulted), initializer flows only through
m.encodeFunctionCall, Map/Set prototype-pollution posture preserved, and artifact-name overrides are path-traversal-guarded.Non-blocking follow-ups (noted by reviewers, not addressed here)
_impl/_proxy/_initData) against the spec id namespace invalidateSpec(today a colliding hand-written id fails loudly atbuildModule, not at validation).args; transparent-upgrade-with-initializer arg position;after+ upgradeable.🤖 Generated with Claude Code