@europeum-ebsi/beacon-proxy-contract
v1.0.0-rc.3
Published
Europeum EBSI Beacon Proxy smart contract
Readme
Beacon Proxy
OpenZeppelin-based beacon and beacon proxy contracts with optional versioning and opt-in upgrades.
Contracts
Classic (single implementation)
- UpgradeableBeaconExtended – Extends OZ
UpgradeableBeaconwithimplementationVersionandImplementationUpgraded(..., newVersion)event. - BeaconProxyExtended – Extends OZ
BeaconProxywithbeaconAddress()andimplementationAddress()view functions.
Versioned / opt-in (Safe-like)
Proxies pin a version and resolve implementation as beacon.implementation(version). The beacon publishes new versions; each proxy opts in via upgradeToVersion(newVersion) (proxy owner only).
VersionedUpgradeableBeacon
- Stores one
Versionrecord per version:mapping(uint64 => Version)whereVersion { address implementation; bool isBreakingChange; bool deprecated; bytes32 storageLayoutHash; }. The constructor validates version 1 the same wayaddVersionvalidates later versions: non-zero implementation with deployed code, and a non-zero storage-layout hash. - Views:
latestVersion(),implementation(version),isVersionAvailable(version),getVersion(version)(fullVersionrecord),getVersions(),getVersionsCompatibility(fromVersion, toVersion). - Admin:
addVersion(version, implementation, storageLayoutHash, isBreakingChange)(version must be strictly greater thanlatestVersion;implementationmust be a deployed contract — reverts withImplementationNotContractotherwise;storageLayoutHashmust be non-zero; reverts withStorageLayoutUnchangedifisBreakingChangeistruebutstorageLayoutHashequals the current latest version's — an unchanged layout can't be a breaking change),deprecateVersion(version)(reverts withVersionAlreadyDeprecatedif already deprecated — a no-op call that would otherwise re-emitVersionDeprecated). - Upgrade:
upgradeProxyToVersion(proxy, version, safeUpgrade)(proxy owner only). Reverts withSameVersionifversionequals the proxy's current version — a same-value write that would otherwise re-emitProxyUpgraded. WhensafeUpgradeistrue, the call also reverts (UnsafeUpgrade) unlessgetVersionsCompatibility(currentVersion, version)istrue. - Ownership:
transferProxyOwnership(proxy, newOwner)(proxy owner only) reverts withSameOwnerifnewOwneris already the current owner — a same-value write that would otherwise re-emitProxyOwnerChanged. - Compatibility:
getVersionsCompatibility(fromVersion, toVersion)only inspects the boundary version being moved onto — the target when upgrading (toVersion > fromVersion), the source when downgrading (toVersion < fromVersion) — not every version in between. A version'sisBreakingChangeflag already captures its diff against its own predecessor, so this is sufficient without walking the range. - Events:
VersionAdded(version, implementation, storageLayoutHash, isBreakingChange),VersionDeprecated(version).
- Stores one
VersionedBeaconProxy
- Storage: beacon (ERC-1967 slot), pinned
uint64 version, proxy owner. - Views:
beacon(),version(),proxyOwner(). - Actions:
upgradeToVersion(newVersion)(only proxy owner),transferProxyOwnership(newOwner). - Events:
ProxyUpgraded(oldVersion, newVersion, newImplementation),ProxyOwnerChanged(oldOwner, newOwner).
- Storage: beacon (ERC-1967 slot), pinned
Versioned flow
- Beacon admin publishes a new implementation:
beacon.addVersion(v, impl, storageLayoutHash, isBreakingChange). - Proxies see
beacon.latestVersion()andbeacon.implementation(version). - Proxy owner chooses when to upgrade:
beacon.upgradeProxyToVersion(proxy, targetVersion, safeUpgrade).
This allows staged rollouts, per-tenant governance, and adoption only after verification.
Usage
// Deploy versioned beacon with initial implementation (version 1) and its storage-layout hash
VersionedUpgradeableBeacon beacon = new VersionedUpgradeableBeacon(owner, implV1, hashV1);
// Add versions (beacon owner): storage-layout hash + whether the version is a breaking change
beacon.addVersion(2, implV2, hashV2, false); // non-breaking
beacon.addVersion(3, implV3, hashV3, true); // breaking storage change
// Deploy proxy (pins the latest version at registration), with init data and owner
bytes memory initData = abi.encodeWithSelector(MockImplementation.initialize.selector, "MyName");
VersionedBeaconProxy proxy = new VersionedBeaconProxy(address(beacon), initData, proxyOwner);
// Later: proxy owner opts in to a new version. safeUpgrade=true reverts if the boundary version
// being moved onto (target when upgrading, source when downgrading) is flagged as breaking.
beacon.upgradeProxyToVersion(address(proxy), 2, true);Tests
pnpm test– runstests/versioned-beacon-proxy.spec.ts(VersionedUpgradeableBeacon/VersionedBeaconProxy). The classic contracts (UpgradeableBeaconExtended,BeaconProxyExtended) have no tests yet.
