Skip to content

feat: Versioned Registry Router + Upgradeable Proxies for All Core Contracts#310

Open
0xCardiE wants to merge 6 commits intomasterfrom
feat/proxy_registry
Open

feat: Versioned Registry Router + Upgradeable Proxies for All Core Contracts#310
0xCardiE wants to merge 6 commits intomasterfrom
feat/proxy_registry

Conversation

@0xCardiE
Copy link
Copy Markdown
Contributor

@0xCardiE 0xCardiE commented Apr 21, 2026

Summary

This PR introduces a Versioned Registry + Router architecture for upgradeable proxy systems in the Swarm storage incentives contracts. The core idea: Bee nodes should not automatically trust new proxy implementations. Instead, they verify (version → implementation) mappings on-chain before interacting with the system.

What changed

  • All 4 core contracts (PostageStamp, PriceOracle, StakeRegistry, Redistribution) are now deployed behind OpenZeppelin TransparentUpgradeableProxy with a shared DefaultProxyAdmin, across all networks (local, main, test, tenderly)
  • New VersionedRegistryRouter contract (src/VersionedRegistryRouter.sol) — a single contract that combines:
    • Registry: immutable version → implementation mappings with codehash, semver, and deprecation support
    • Router/Guard: verifies proxy implementations are registered and non-deprecated before forwarding calls
    • Multi-proxy management: tracks all 4 proxy addresses, supports verifyAllProxies() batch verification
  • Deploy scripts updated across all 4 networks to use proxies + initialize() instead of constructors, deploy the registry router, register all proxies, and register v1.0.0 releases with codehash verification
  • 58 tests for VersionedRegistryRouter covering all registry, router, role, invariant, and end-to-end scenarios

How it works

┌──────────┐     ┌──────────────────────────┐     ┌───────────────────┐
│ Bee Node │────▶│ VersionedRegistryRouter   │────▶│ Proxy (e.g.       │
│          │     │                           │     │ PostageStamp)     │
│ "I only  │     │ 1. getProxyImplementation │     │                   │
│  trust   │     │ 2. Check version exists   │     │ ┌───────────────┐│
│  v1.0.0" │     │ 3. Check not deprecated   │     │ │Implementation ││
│          │     │ 4. Verify codehash        │     │ │  (logic)      ││
│          │     │ 5. Forward call           │     │ └───────────────┘│
└──────────┘     └──────────────────────────┘     └───────────────────┘
                          │
                 ┌────────┴─────────┐
                 │ Release Registry  │
                 │ v1.0.0 → 0xABC   │
                 │ v2.0.0 → 0xDEF   │
                 │ (deprecated)      │
                 └──────────────────┘

Security model:

  • Proxy can still be upgraded on-chain by the ProxyAdmin owner
  • Registry publishes which implementation belongs to which version
  • Bee nodes independently decide which versions they accept (local allowlist)
  • Unapproved upgrades become effectively unusable by the Bee network

Roles:

  • REGISTRAR_ROLE — can register new releases
  • DEPRECATOR_ROLE — can deprecate releases
  • ROUTER_ADMIN_ROLE — can register proxies and configure selector routing
  • DEFAULT_ADMIN_ROLE — can manage all roles

Key invariants:

  1. One version → one implementation (immutable once registered)
  2. One implementation → one version (no reuse)
  3. Deprecated releases remain queryable but fail verification
  4. Mappings are permanent — cannot be overwritten even after deprecation

Contract changes

All 4 core Solidity contracts were made proxy-compatible:

  • Constructors replaced with initialize() functions (using OZ Initializable)
  • immutable state variables converted to regular storage
  • _disableInitializers() in constructor to prevent implementation initialization

Deploy pipeline (consistent across all networks)

000 → Token
001 → PostageStamp (proxy + initialize)
002 → PriceOracle (proxy + initialize)
003 → StakeRegistry (proxy + initialize)
004 → Redistribution (proxy + initialize)
005-008 → Role assignments (using keccak256 hashes, proxy-safe)
009 → Local data / env exports
010 → VersionedRegistryRouter + proxy registration + v1.0.0 releases
011 → Etherscan verification
012 → Multisig admin grants

Test improvements

  • Witness caching: anchor-aware cache format that auto-detects seed changes and regenerates when needed
  • Parallel witness mining: uses worker_threads across all CPU cores (~3 min vs ~16 min per trial)
  • Interval mining fix: disabled phantom block mining from 010_deploy_cluster.ts during tests
  • 216 tests passing in ~30 seconds (Stats test runs 100 trials from cached witnesses in ~9s)

Cleanup

  • Removed isolated deploy/registry/ directory (superseded by integrated pipeline)
  • Role scripts use keccak256('ROLE_NAME') instead of read() calls to avoid TransparentUpgradeableProxy: admin cannot fallback to proxy target errors

Test plan

  • All 216 tests pass (npx hardhat test)
  • Stats test: 100 trials, variance < 0.035, witnesses cached
  • VersionedRegistryRouter: 58 tests covering registry, router, roles, invariants, end-to-end lifecycle
  • Redistribution: claim-pot witnesses regenerated with anchor-aware caching
  • Deploy scripts compile and execute correctly for hardhat network
  • Deploy to testnet and verify contracts on Etherscan
  • Verify Bee node can read registry and validate implementations

- Wire core contracts and local deploy to the registry router
- Add registry deploy scripts, tests, and spec for the router
- Mine stats witnesses in tests; remove checked-in fixture JSON
Convert all core contract deployments (PostageStamp, PriceOracle,
StakeRegistry, Redistribution) from direct deploys to
TransparentUpgradeableProxy + DefaultProxyAdmin + initialize() across
local, main, test, and tenderly networks.

Add VersionedRegistryRouter deploy script to all networks that
registers all 4 proxies and their v1.0.0 releases with codehash
verification.

Update role scripts to use keccak256 hashes instead of read() calls
to avoid TransparentUpgradeableProxy admin-cannot-fallback errors.

Remove superseded deploy/registry/ directory.
Security and correctness fixes for the new proxy registry / router:

- Gate forwardUnchecked behind ROUTER_ADMIN_ROLE so it no longer bypasses
  the selector allowlist for arbitrary callers.
- Reject zero codehash in registerRelease so verifyProxy can't be
  silently disabled.
- Validate registered proxies are actually owned by this router's
  ProxyAdmin.
- Add deprecateProxy + ProxyDeprecated event; verifyProxy now rejects
  deprecated proxies and verifyAllProxies skips them instead of
  reverting.
- Reject calldata < 4 bytes in both forward paths instead of panicking.
- Add explicit ZeroAddress check on the constructor's _proxyAdmin arg
  and a SelectorRouted event for setRoutedSelector.

Multisig handover (deploy/main/012):

- Grant REGISTRAR_ROLE / DEPRECATOR_ROLE / ROUTER_ADMIN_ROLE to the
  multisig and renounce them (and DEFAULT_ADMIN_ROLE) from the deployer
  EOA so the multisig is the sole authority on the router.

Tooling and dead-code cleanup:

- Restore working solhint config (extends solhint:recommended; the
  removed solhint:default preset broke the linter).
- Drop unused imports/vars and dead scaffolding in
  scripts/mine-stats-witnesses.ts and deploy/local/011.
- Prettier-format remaining files touched on this branch.
- Expand VersionedRegistryRouter tests to cover the new behaviour
  (proxy admin mismatch, deprecateProxy, calldata length, zero
  codehash rejection, forwardUnchecked role gating, selector disable).

All 225 tests pass.
The .ts source was formatted in dad1e86 but the compiled .js sibling
(loaded by worker_threads at runtime) was missed; CI's prettier --check
runs against both.
It's the CommonJS sibling of mine-worker.ts, loaded directly by
worker_threads at runtime, so the require() calls flagged by
@typescript-eslint/no-var-requires are intentional and can't be
rewritten as ESM imports without breaking the worker.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant