Versioned per-name proxies: one stable address per contract, semver implementations behind it - #76
Open
charlesHetterich wants to merge 25 commits into
Open
Versioned per-name proxies: one stable address per contract, semver implementations behind it#76charlesHetterich wants to merge 25 commits into
charlesHetterich wants to merge 25 commits into
Conversation
The registry becomes a factory: a name's first publish CREATE2-instantiates a minimal per-name proxy (salt = keccak256(name), frozen blob, zero-arg constructor so the address commits only to registry+name+blob) that permanently owns the name's address, storage, and balance. Versions are implementation contracts the proxy delegate-calls — plain calls route to latest, [MAGIC][u128 key] calldata routes to an exact version, and a [MAGIC][0][selector] meta plane serves CDM queries plus the registry-only publish/setMinSupported/setAdmin ops. Version keys pack semver as major<<64|minor<<32|patch, strictly increasing per name, so the version list is sorted by construction; owners can ratchet a min-supported floor below which pinned calls revert UnsupportedVersion(). The v1→v2 registry storage layout is append-only (info.proxy lands in a previously-unwritten slot; key_of is a new mapping beside the untouched v1 tables), so setCode upgrades need no data migration: v1 records read back as legacy entries with keys derived as 0.0.(index+1), resolve exactly as before, and upgrade onto a proxy at their next publish. The getAddress 64-byte wire format is unchanged and now returns the stable proxy address. cdm::import! accepts semver version strings in cdm.json (legacy numeric indices still parse); the registry proxy crate moves to src/contract/registry-proxy so contract-proxy can take src/contract/proxy.
… upgrades proxy.ts mirrors the per-name proxy wire format (magic prefix, meta selectors, slots, key packing) with every constant drift-tested against the Rust pins, exposed browser-safe via the ./proxy subpath. Deploys read each crate's Cargo.toml version, skip anything at or below the registry's latest key (idempotent re-runs), salt implementation addresses by semver, publish through the new registry ABI, and bake the stable per-name address into Solidity imports. Installs resolve latest/exact/npm-range specs against the on-chain version list (numeric pins keep resolving as legacy v1 indices) and pin the resolved semver plus stable address in cdm.json. Metadata gains the implementation's storage layout for future compat checks. The contract-proxy blob is frozen under src/contract/proxy/artifacts with a hash-verified loader (regenerate via freeze-proxy-artifact.ts); fresh registry deploys upload it and set proxyCodeHash, and deploy-registry.ts --upgrade performs in-place setCode upgrades with consumed-salt probing. Migration snapshots gain a cdm.registry.v2 schema (version keys + per-name proxies); v1 snapshots stay readable and import with derived legacy keys.
cdm deploy shows each crate's version and renders already-published crates as up-to-date skips; the stable per-name address replaces the per-version implementation address in tables and summaries. cdm install accepts exact versions and npm-style ranges after the colon (the old parser silently truncated 1.2.3 to index 1), passes cdm.json specs through verbatim, and renders resolved semver. The frontend lists versions by key with their implementation targets, marks releases below the owner's min-supported floor, and labels the package's stable proxy address.
Resolves the harness and registry-suite conflicts by keeping the PPN connect-only harness from main and re-applying the versioned-proxy semantics: the harness additionally checks the contract-proxy blob, builds the shared-counter template, and deploys raw blobs without dry-runs (the Node/Bun instantiate divergence); the registry suite publishes semver keys and asserts stable proxy addresses with per-run unique names; the new proxy suite exercises multi-version shared state, the meta plane, and the min-supported ratchet; the bulletin suite publishes with the v2 signature.
The proxy suite's raw versioned/meta calls (no ABI — the wire format is a calldata prefix) now mirror product-sdk's exact papi shapes: the unsafe api (generated descriptors lag PPN's runtime for these entries), dest as a hex string, calldata as Uint8Array, and Revive.call's weight_limit field (not gas_limit). Wire helpers import through the browser-safe /proxy subpath — the package root loses named exports to the resolver's tree-shaking under vitest, same story as /abi. Template workspaces bump polkavm-derive 0.31 → 0.35 to match cargo-pvm-contract main's polkavm line; 0.31's picosimd pin conflicts with the current SDK resolution and broke template builds. Full run against an ephemeral local PPN: 4 suites, 47/47 — including live proof of two implementation versions sharing one proxy's storage, the meta plane byte-for-byte, and the min-supported ratchet.
Contributor
CDM CLI dev releaseThis PR includes a Install it: curl -fsSL https://raw.githubusercontent.com/paritytech/contract-dependency-manager/ee8c189b85cede149402ffe9b16d57da2fb3f838/install.sh | CDM_TAG=cdm-cli-dev-pr-76 bashUpdate an existing install: cdm update --tag cdm-cli-dev-pr-76Release tag: |
The proxy-based versioned system is now the only system. Versions are semver strings everywhere: the registry drops the legacy branches, derived 0.0.(index+1) keys, and the index-based getters (getVersionAt covers enumeration); version rows merge into one VersionRecord mapping; install/CLI/migrations lose the numeric-pin paths (old cdm.json pins fail with reinstall instructions); v1 snapshots are neither exported nor imported. Nothing live depends on v1 — paseo-next was reset and the PCF devnet bootstraps fresh. The per-name proxy keeps only what routing needs: the enumeration meta ops (versionCount/versionAt/resolveMax) and their duplicate sorted key list leave the frozen blob (27.5 → 24.1 kB); the O(1) point queries (implOf/latest/minSupported/admin) stay, riding on state the proxy holds anyway, with the latest key at a dedicated slot. The registry remains the catalog for enumeration. Also: cdm deploy now warns when a published method's selector collides with the proxy call prefix, and the two changesets merge into one with an explicit breaking notice.
… routers Every CDM contract now inherits an owner-controlled pause: freezeContract(name) halts all delegation at the per-name proxy (plain and versioned calls revert ContractFrozen()) while the meta plane and registry operations stay live — so a storage-reshaping migration is freeze → publish → unfreeze → ratchet minSupported, with no window where old code writes mid-reshape. New cdm.proxy.frozen slot and freeze/unfreeze/frozen meta ops, registry forwarding with owner auth, wire mirror, and live e2e. cargo-pvm-contract main replaced Mapping::view/view_mut with the #133 chained get()/entry() accessors; the registry adopts them (this is also what CI compiles against — it floats on branch main by design, so the Rust/TypeScript/Templates jobs red on the previous push go green here). Both routers switch to the SDK's bump allocator: passthrough frames make a handful of one-shot allocations, so a real malloc buys nothing — the per-name proxy blob drops 26.1 → 13.7 kB and the registry proxy 15.4 → 2.5 kB, recurring per-call code-loading savings on every CDM call. The registry implementation keeps picoalloc for its allocation-churning paging and import paths.
.gitignore's blanket *.polkavm rule swallowed the frozen artifact the loader hash-verifies against — the manifest was tracked, the blob wasn't, which is exactly the CI TypeScript failure. Carve out the proxy artifacts directory the same way create3's is.
This was referenced Aug 18, 2026
6 tasks
This was referenced Aug 19, 2026
…idity e2e on PPN
Solidity version source: @Custom:cdm @org/name:X.Y.Z
…les; initialize() entry point
…e cargo-pvm-contract#155
… note the initializations convention in CLAUDE.md
… drop unused bytecodeSize
Initializations: version-addressed init contracts run atomically at publish
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Trim narrative and rationale comments to constraint-bearing content across the per-name proxy, registry, and CDM tooling; drop dead exports and duplicate tests; fix docs that contradicted the code (EVM backend, CREATE3 registry deploy, proxy constructor args, import payloads).
Cleanup pass over the versioned-proxies branch
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.
Every
@org/namenow has one permanent address. The registry acts as a factory: a name's first publish instantiates a minimal per-name proxy that owns the address, storage, and balance forever; semver versions are implementation contracts behind it, and any published version can be called over the same shared state.Breaking — clean break with v1. Versions are semver strings everywhere; numeric indices are gone. The CLI and migration tooling don't read v1 registries; old numeric
cdm.jsonpins fail with a reinstall hint. (paseo-next was reset; nothing live depends on v1.)Closes #77, #81.
How it works
[MAGIC][versionKey][calldata]→ that exact version;[MAGIC][0][selector]→ CDM meta ops (implOf,latest,minSupported,frozen,admin; registry-onlypublish,setMinSupported,freeze/unfreeze,callCode).Cargo.toml [package].version(Rust) or/// @custom:cdm @org/name:X.Y.Z(Solidity). Publishes must strictly increase;cdm deployskips versions already on-chain, so it's idempotent.setMinSupportedratchets a floor below which pinned calls revert;freeze/unfreezehalts all delegation for migrations.initializations/<version>.{rs,sol}runs once, atomically, when that version publishes — first-publish setup and upgrade migrations in one mechanism.cdm i @org/pkg:1.2.3/:^1.2/latest, resolved to an exact version; ABI is version-exact.(registry, keccak(name), frozen proxy blob)— offline-predictable, identical across chains with the same registry address.Proxy surface
Every name's proxy exposes the same frozen surface, reached via
[MAGIC][0][selector]:implOf(uint128),latest(),minSupported(),frozen(),admin()publish(uint128, address),setMinSupported(uint128),freeze(),unfreeze(),setAdmin(address),callCode(address, bytes)Owners drive these through the registry:
publish/publishWithInit,setMinSupported(name, key),freezeContract(name)/unfreezeContract(name). Registry queries:getAddress,getProxy,getLatestKey,getMinSupported,getVersionCount,getVersionAt. Registry admin:setCode,setProxyCodeHash,freeze/unfreeze,adminImportContracts.On-chain
contract-registryv2:publish/publishWithInit, factory instantiation,setMinSupported,freezeContract,setProxyCodeHash, version getters. Upgradable in place viasetCode.contract-proxy(per-name, frozen blob0x9c918a8b…542e86) andcontract-registry-proxy, both on the bump allocator.contract-registry-core: wire format and selectors, hex-pinned and drift-tested against the TS mirror.Tooling
Builder (
proxy.tswire mirror, deploy pipeline, install resolution, storage-layout metadata, initialization shim builds, layout guard), CLI (version column, up-to-date state,+initmarker), frontend (semver display, min-supported),cdm::import!(semver cdm.json), migrations (cdm.registry.v2),deploy-registry --upgrade. Solidity builds target the EVM backend (forge build, no resolc).Not in this PR
Consumer-side version pinning (#78 TS, #79 Solidity, #80 Rust — wire format ships now),
cdm deploy --dry-run(#85), initialization scaffolding (#88), source-drift warning (#84).Verification
Rust host tests, TS unit suite, and five e2e suites against a local PPN (
make start EPHEMERAL=1), including shared-storage-across-versions, ratchet/freeze, Solidity-through-proxy cross-VM calls, and initializations. CI e2e needsPPN_GITHUB_TOKEN.Rollout
Fresh v2 deploy to reset paseo-next (same CREATE3 registry address) and extensive testing there before merging to main; PCF devnet bootstraps fresh on v2.