From a68cf90aa5961a5b22496160ff942bcf1d95806a Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:20:31 -0400 Subject: [PATCH 01/13] draft --- src/contracts/core/ReleaseManager.sol | 205 +++++++++++++++++++ src/contracts/core/ReleaseManagerStorage.sol | 39 ++++ src/contracts/interfaces/IReleaseManager.sol | 118 +++++++++++ 3 files changed, 362 insertions(+) create mode 100644 src/contracts/core/ReleaseManager.sol create mode 100644 src/contracts/core/ReleaseManagerStorage.sol create mode 100644 src/contracts/interfaces/IReleaseManager.sol diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol new file mode 100644 index 000000000..a7107cf8f --- /dev/null +++ b/src/contracts/core/ReleaseManager.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "../mixins/PermissionControllerMixin.sol"; +import "../mixins/SemVerMixin.sol"; +import "../libraries/Snapshots.sol"; +import "./ReleaseManagerStorage.sol"; + +contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionControllerMixin, SemVerMixin { + using Snapshots for *; + using EnumerableSet for *; + using EnumerableMap for *; + using OperatorSetLib for *; + using Strings for *; + + /** + * + * INITIALIZING FUNCTIONS + * + */ + constructor( + IAllocationManager _allocationManager, + IPermissionController _permissionController, + string memory _version + ) + ReleaseManagerStorage(_allocationManager) + PermissionControllerMixin(_permissionController) + SemVerMixin(_version) + { + _disableInitializers(); + } + + /** + * + * EXTERNAL FUNCTIONS + * + */ + + /// @inheritdoc IReleaseManager + function publishRelease( + OperatorSet calldata operatorSet, + bytes32 releaseDigest, + string calldata registryUrl, + Version calldata version + ) external checkCanCall(operatorSet.avs) { + // Parse the next releaseId (equal to the total number of releases). + uint256 releaseId = getTotalOperatorSetReleases(operatorSet); + + // Push the release digest and set the version. + _setReleaseInfo(operatorSet, releaseDigest, version, uint32(0)); + + // Push the release snapshot. + _releaseSnapshots[operatorSet.key()].push(uint32(block.timestamp), releaseId); + + // Set the release registry URL for the release digest. + _releaseRegistryUrls[operatorSet.key()][releaseDigest] = registryUrl; + + emit ReleasePublished(operatorSet, releaseDigest, registryUrl, version); + } + + /// @inheritdoc IReleaseManager + function deprecateRelease( + OperatorSet calldata operatorSet, + bytes32 releaseDigest, + uint32 deprecationTimestamp + ) external checkCanCall(operatorSet.avs) { + // Get the deprecation timestamp and version for the provided digest. + (uint32 currentDeprecationTimestamp, Version memory version) = _getReleaseInfo(operatorSet, releaseDigest); + + // Assert that the release is not already deprecated. + require(currentDeprecationTimestamp != uint32(0), ReleaseAlreadyDeprecated()); + + // Set the deprecation timestamp for the release digest. + _setReleaseInfo(operatorSet, releaseDigest, version, uint32(deprecationTimestamp)); + + emit DeprecationScheduled(operatorSet, releaseDigest, deprecationTimestamp); + } + + /** + * + * INTERNAL FUNCTIONS + * + */ + + /// @dev Returns the deprecation timestamp and version for a release digest. + function _getReleaseInfo( + OperatorSet memory operatorSet, + bytes32 releaseDigest + ) internal view returns (uint32, Version memory) { + uint256 encoded = _releaseDigests[operatorSet.key()].get(releaseDigest); + + uint32 deprecationTimestamp = uint32(encoded >> 224); + uint16 major = uint16((encoded >> 192) & 0xFFFF); + uint16 minor = uint16((encoded >> 160) & 0xFFFF); + uint16 patch = uint16((encoded >> 128) & 0xFFFF); + + return (deprecationTimestamp, Version(major, minor, patch)); + } + + /// @dev Sets the release info for a release digest. + function _setReleaseInfo( + OperatorSet memory operatorSet, + bytes32 releaseDigest, + Version memory version, + uint32 deprecationTimestamp + ) internal { + // Push the release digest and set the version. + require( + _releaseDigests[operatorSet.key()].set( + releaseDigest, + uint256(bytes32(abi.encodePacked(deprecationTimestamp, version.major, version.minor, version.patch))) + ), + ReleaseAlreadyExists() + ); + } + + /** + * + * VIEW FUNCTIONS + * + */ + + /// @inheritdoc IReleaseManager + function isOperatorSetRelease(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool) { + return _releaseDigests[operatorSet.key()].contains(releaseDigest); + } + + /// @inheritdoc IReleaseManager + function getOperatorSetReleases( + OperatorSet memory operatorSet, + uint160 releaseId + ) external view returns (bytes32) { + (bytes32 releaseDigest,) = _releaseDigests[operatorSet.key()].at(releaseId); + + return releaseDigest; + } + + /// @inheritdoc IReleaseManager + function getOperatorSetReleases( + OperatorSet memory operatorSet + ) external view returns (bytes32[] memory) { + return _releaseDigests[operatorSet.key()].keys(); + } + + /// @inheritdoc IReleaseManager + function getTotalOperatorSetReleases( + OperatorSet memory operatorSet + ) public view returns (uint256) { + return _releaseDigests[operatorSet.key()].length(); + } + + /// @inheritdoc IReleaseManager + function getLastOperatorSetRelease( + OperatorSet memory operatorSet + ) public view returns (bytes32) { + uint256 lastReleaseId = getTotalOperatorSetReleases(operatorSet) - 1; + + (bytes32 releaseDigest,) = _releaseDigests[operatorSet.key()].at(lastReleaseId); + + return releaseDigest; + } + + /// @inheritdoc IReleaseManager + function getReleaseAtTime( + OperatorSet memory operatorSet, + uint32 previousTimestamp + ) external view returns (bytes32) { + // Get the most recent snapshot that was published at or before the provided timestamp. + uint256 releaseId = _releaseSnapshots[operatorSet.key()].upperLookup(previousTimestamp); + + // Return the release releaseDigest for the `releaseId`, revert if nonexistent. + (bytes32 releaseDigest,) = _releaseDigests[operatorSet.key()].at(releaseId); + + return releaseDigest; + } + + /// @inheritdoc IReleaseManager + function isReleaseDeprecated(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool) { + // Get the deprecation timestamp and version for the provided digest. + (uint32 deprecationTimestamp, Version memory version) = _getReleaseInfo(operatorSet, releaseDigest); + + // Get the the current major version for the operator set. + (, Version memory currentVersion) = _getReleaseInfo(operatorSet, getLastOperatorSetRelease(operatorSet)); + + // If the deprecation timestamp is in the future, the release is not deprecated. + if (deprecationTimestamp > block.timestamp) return false; + + // If the major version is the same as the current major version, the release is not deprecated. + if (version.major == currentVersion.major) return false; + + return true; + } + + /// @inheritdoc IReleaseManager + function getReleaseVersion( + OperatorSet memory operatorSet, + bytes32 releaseDigest + ) external view returns (string memory) { + (, Version memory version) = _getReleaseInfo(operatorSet, releaseDigest); + + return string.concat(version.major.toString(), ".", version.minor.toString(), ".", version.patch.toString()); + } +} diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/ReleaseManagerStorage.sol new file mode 100644 index 000000000..094bcf249 --- /dev/null +++ b/src/contracts/core/ReleaseManagerStorage.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import "../libraries/Snapshots.sol"; +import "../interfaces/IReleaseManager.sol"; +import "../interfaces/IAllocationManager.sol"; + +abstract contract ReleaseManagerStorage is IReleaseManager { + // Immutables + + /// @notice The EigenLayer AllocationManager contract. + IAllocationManager public immutable allocationManager; + + // Mutables + + /// @notice A set of all release digests for an operator set. + /// @dev operatorSet, releaseId => (releaseDigest => (uint32(deprecationTimestamp), uint16(major), uint16(minor), uint16(patch))) + mapping(bytes32 operatorSetKey => EnumerableMap.Bytes32ToUintMap releaseDigests) internal _releaseDigests; + + /// @notice The deployment deadline for a release digest. + /// @dev operatorSet => (uint224(releaseId)) + mapping(bytes32 operatorSetKey => Snapshots.DefaultZeroHistory) internal _releaseSnapshots; + + /// @notice The registry URL for a release digest. + mapping(bytes32 operatorSetKey => mapping(bytes32 releaseDigest => string registryUrl)) internal + _releaseRegistryUrls; + + constructor( + IAllocationManager _allocationManager + ) { + allocationManager = _allocationManager; + } + + // TODO: modify gap to account for odd variable sizes. + + uint256[46] private __gap; +} + diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol new file mode 100644 index 000000000..0b0c7601a --- /dev/null +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "../libraries/OperatorSetLib.sol"; + +interface IReleaseManagerErrors { + error ReleaseAlreadyExists(); + error ReleaseAlreadyDeprecated(); +} + +interface IReleaseManagerTypes { + struct Version { + uint16 major; + uint16 minor; + uint16 patch; + } +} + +interface IReleaseManagerEvents is IReleaseManagerTypes { + /// @notice Emitted when a new release is published for an operator set. + /// @param operatorSet The operator set that the release was published for. + /// @param releaseDigest The unique identifier for the release content. + /// @param registryUrl The URL where the release artifacts can be found. + /// @param version The semantic version of the release. + event ReleasePublished(OperatorSet operatorSet, bytes32 releaseDigest, string registryUrl, Version version); + + /// @notice Emitted when a release deprecation is scheduled for an operator set. + /// @param operatorSet The operator set that the release was deprecated for. + /// @param releaseDigest The unique identifier for the release content. + /// @param deprecationTimestamp The timestamp when the release will be deprecated. + event DeprecationScheduled(OperatorSet operatorSet, bytes32 releaseDigest, uint32 deprecationTimestamp); +} + +interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { + // WRITE + + /// @notice Publishes a new release for an operator set. + /// @param operatorSet The operator set to publish the release for. + /// @param digest The unique identifier for the release content. + /// @param registryUrl The URL where the release artifacts can be found. + /// @param version The semantic version of the release. + /// @dev Only callable by addresses with permissioned by the operator set's AVS. + function publishRelease( + OperatorSet calldata operatorSet, + bytes32 digest, + string calldata registryUrl, + Version calldata version + ) external; + + /// @notice Deprecates a release for an operator set. + /// @param operatorSet The operator set to deprecate the release for. + /// @param digest The unique identifier for the release content. + /// @param deprecationTimestamp The timestamp when the release will be deprecated. + /// @dev Only callable by addresses with permissioned by the operator set's AVS. + function deprecateRelease(OperatorSet calldata operatorSet, bytes32 digest, uint32 deprecationTimestamp) external; + + // READ + + /// @notice Checks if a release digest exists for an operator set. + /// @param operatorSet The operator set to check. + /// @param releaseDigest The release digest to check. + /// @return bool True if the release digest exists, false otherwise. + function isOperatorSetRelease(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool); + + /// @notice Gets a specific release digest for an operator set by its ID. + /// @param operatorSet The operator set to get the release from. + /// @param releaseId The ID of the release to get. + /// @return bytes32 The release digest. + function getOperatorSetReleases( + OperatorSet memory operatorSet, + uint160 releaseId + ) external view returns (bytes32); + + /// @notice Gets all release digests for an operator set. + /// @param operatorSet The operator set to get releases from. + /// @return bytes32[] Array of release digests. + function getOperatorSetReleases( + OperatorSet memory operatorSet + ) external view returns (bytes32[] memory); + + /// @notice Gets the total number of releases for an operator set. + /// @param operatorSet The operator set to count releases for. + /// @return uint256 The total number of releases. + function getTotalOperatorSetReleases( + OperatorSet memory operatorSet + ) external view returns (uint256); + + /// @notice Gets the most recent release digest for an operator set. + /// @param operatorSet The operator set to get the latest release from. + /// @return bytes32 The latest release digest. + function getLastOperatorSetRelease( + OperatorSet memory operatorSet + ) external view returns (bytes32); + + /// @notice Gets the release digest that was active at a specific timestamp. + /// @param operatorSet The operator set to get the release from. + /// @param previousTimestamp The timestamp to check. + /// @return bytes32 The release digest that was active at the timestamp. + function getReleaseAtTime( + OperatorSet memory operatorSet, + uint32 previousTimestamp + ) external view returns (bytes32); + + /// @notice Checks if a release is deprecated. + /// @param operatorSet The operator set to check. + /// @param releaseDigest The release digest to check. + /// @return bool True if the release is deprecated, false otherwise. + function isReleaseDeprecated(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool); + + /// @notice Gets the semantic version string for a release. + /// @param operatorSet The operator set containing the release. + /// @param releaseDigest The release digest to get the version for. + /// @return string The semantic version string (e.g. "1.2.3"). + function getReleaseVersion( + OperatorSet memory operatorSet, + bytes32 releaseDigest + ) external view returns (string memory); +} From 0517cdfb01053fe5cf5c3fded804f6e05b606365 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:32:38 -0400 Subject: [PATCH 02/13] refactor: explicit `using` statements --- src/contracts/core/ReleaseManager.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index a7107cf8f..c92513e77 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -9,11 +9,11 @@ import "../libraries/Snapshots.sol"; import "./ReleaseManagerStorage.sol"; contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionControllerMixin, SemVerMixin { - using Snapshots for *; - using EnumerableSet for *; - using EnumerableMap for *; - using OperatorSetLib for *; - using Strings for *; + using Snapshots for Snapshots.DefaultZeroHistory; + using EnumerableSet for EnumerableSet.Bytes32Set; + using EnumerableMap for EnumerableMap.Bytes32ToUintMap; + using OperatorSetLib for OperatorSet; + using Strings for uint16; /** * From 2188c01b73ad0b5008e416f031bd7bf4a1664d83 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:33:19 -0400 Subject: [PATCH 03/13] feat: add `getReleaseRegistryUrl` --- src/contracts/core/ReleaseManager.sol | 8 ++++++++ src/contracts/interfaces/IReleaseManager.sol | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index c92513e77..39bf835f5 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -193,6 +193,14 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr return true; } + /// @inheritdoc IReleaseManager + function getReleaseRegistryUrl( + OperatorSet memory operatorSet, + bytes32 releaseDigest + ) external view returns (string memory) { + return _releaseRegistryUrls[operatorSet.key()][releaseDigest]; + } + /// @inheritdoc IReleaseManager function getReleaseVersion( OperatorSet memory operatorSet, diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol index 0b0c7601a..d4fc7a8c7 100644 --- a/src/contracts/interfaces/IReleaseManager.sol +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -107,7 +107,16 @@ interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { /// @return bool True if the release is deprecated, false otherwise. function isReleaseDeprecated(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool); - /// @notice Gets the semantic version string for a release. + /// @notice Returns the registry URL for a release digest. + /// @param operatorSet The operator set containing the release. + /// @param releaseDigest The release digest to get the registry URL for. + /// @return string The registry URL. + function getReleaseRegistryUrl( + OperatorSet memory operatorSet, + bytes32 releaseDigest + ) external view returns (string memory); + + /// @notice Returns the semantic version string for a release. /// @param operatorSet The operator set containing the release. /// @param releaseDigest The release digest to get the version for. /// @return string The semantic version string (e.g. "1.2.3"). From db0f478d69dea5b273ee051258ddc4aed2c8404b Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:33:30 -0400 Subject: [PATCH 04/13] docs: improvements --- src/contracts/core/ReleaseManagerStorage.sol | 2 +- src/contracts/interfaces/IReleaseManager.sol | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/ReleaseManagerStorage.sol index 094bcf249..00996b808 100644 --- a/src/contracts/core/ReleaseManagerStorage.sol +++ b/src/contracts/core/ReleaseManagerStorage.sol @@ -15,7 +15,7 @@ abstract contract ReleaseManagerStorage is IReleaseManager { // Mutables /// @notice A set of all release digests for an operator set. - /// @dev operatorSet, releaseId => (releaseDigest => (uint32(deprecationTimestamp), uint16(major), uint16(minor), uint16(patch))) + /// @dev operatorSet, releaseId, releaseDigest => (uint32(deprecationTimestamp), version)) mapping(bytes32 operatorSetKey => EnumerableMap.Bytes32ToUintMap releaseDigests) internal _releaseDigests; /// @notice The deployment deadline for a release digest. diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol index d4fc7a8c7..b51de4363 100644 --- a/src/contracts/interfaces/IReleaseManager.sol +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -4,11 +4,17 @@ pragma solidity ^0.8.27; import "../libraries/OperatorSetLib.sol"; interface IReleaseManagerErrors { + /// @notice Thrown when a release with the same digest already exists. error ReleaseAlreadyExists(); + /// @notice Thrown when a release with the same digest is already deprecated. error ReleaseAlreadyDeprecated(); } interface IReleaseManagerTypes { + /// @notice A semantic version. + /// @param major The major version. + /// @param minor The minor version. + /// @param patch The patch version. struct Version { uint16 major; uint16 minor; @@ -56,13 +62,13 @@ interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { // READ - /// @notice Checks if a release digest exists for an operator set. + /// @notice Returns whether a release digest exists for an operator set. /// @param operatorSet The operator set to check. /// @param releaseDigest The release digest to check. /// @return bool True if the release digest exists, false otherwise. function isOperatorSetRelease(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool); - /// @notice Gets a specific release digest for an operator set by its ID. + /// @notice Returns a specific release digest for an operator set by its ID. /// @param operatorSet The operator set to get the release from. /// @param releaseId The ID of the release to get. /// @return bytes32 The release digest. @@ -71,28 +77,28 @@ interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { uint160 releaseId ) external view returns (bytes32); - /// @notice Gets all release digests for an operator set. + /// @notice Returns all release digests for an operator set. /// @param operatorSet The operator set to get releases from. /// @return bytes32[] Array of release digests. function getOperatorSetReleases( OperatorSet memory operatorSet ) external view returns (bytes32[] memory); - /// @notice Gets the total number of releases for an operator set. + /// @notice Returns the total number of releases for an operator set. /// @param operatorSet The operator set to count releases for. /// @return uint256 The total number of releases. function getTotalOperatorSetReleases( OperatorSet memory operatorSet ) external view returns (uint256); - /// @notice Gets the most recent release digest for an operator set. + /// @notice Returns the most recent release digest for an operator set. /// @param operatorSet The operator set to get the latest release from. /// @return bytes32 The latest release digest. function getLastOperatorSetRelease( OperatorSet memory operatorSet ) external view returns (bytes32); - /// @notice Gets the release digest that was active at a specific timestamp. + /// @notice Returns the release digest that was active at a specific timestamp. /// @param operatorSet The operator set to get the release from. /// @param previousTimestamp The timestamp to check. /// @return bytes32 The release digest that was active at the timestamp. @@ -101,7 +107,7 @@ interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { uint32 previousTimestamp ) external view returns (bytes32); - /// @notice Checks if a release is deprecated. + /// @notice Returns whether a release is deprecated. /// @param operatorSet The operator set to check. /// @param releaseDigest The release digest to check. /// @return bool True if the release is deprecated, false otherwise. From cd53f2d248e53cbe6f6deabce06aa5b79f21265f Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:44:39 -0400 Subject: [PATCH 05/13] fix: decoding --- src/contracts/core/ReleaseManager.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index 39bf835f5..8feb24b09 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -92,9 +92,9 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr uint256 encoded = _releaseDigests[operatorSet.key()].get(releaseDigest); uint32 deprecationTimestamp = uint32(encoded >> 224); - uint16 major = uint16((encoded >> 192) & 0xFFFF); - uint16 minor = uint16((encoded >> 160) & 0xFFFF); - uint16 patch = uint16((encoded >> 128) & 0xFFFF); + uint16 major = uint16((encoded >> 208) & type(uint16).max); + uint16 minor = uint16((encoded >> 192) & type(uint16).max); + uint16 patch = uint16((encoded >> 176) & type(uint16).max); return (deprecationTimestamp, Version(major, minor, patch)); } From 532d85ecd39af25c4ff433d389313caaf7b85e0c Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:51:39 -0400 Subject: [PATCH 06/13] fix: gap --- src/contracts/core/ReleaseManagerStorage.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/ReleaseManagerStorage.sol index 00996b808..1953ccaf5 100644 --- a/src/contracts/core/ReleaseManagerStorage.sol +++ b/src/contracts/core/ReleaseManagerStorage.sol @@ -32,8 +32,5 @@ abstract contract ReleaseManagerStorage is IReleaseManager { allocationManager = _allocationManager; } - // TODO: modify gap to account for odd variable sizes. - - uint256[46] private __gap; + uint256[45] private __gap; } - From f165133e364a2bdd49ee3415a0e059011d7fb8b6 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:51:52 -0400 Subject: [PATCH 07/13] wip: cleanup --- src/contracts/core/ReleaseManager.sol | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index 8feb24b09..203513f03 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -90,13 +90,16 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr bytes32 releaseDigest ) internal view returns (uint32, Version memory) { uint256 encoded = _releaseDigests[operatorSet.key()].get(releaseDigest); - + uint32 deprecationTimestamp = uint32(encoded >> 224); - uint16 major = uint16((encoded >> 208) & type(uint16).max); - uint16 minor = uint16((encoded >> 192) & type(uint16).max); - uint16 patch = uint16((encoded >> 176) & type(uint16).max); - return (deprecationTimestamp, Version(major, minor, patch)); + Version memory version = Version({ + major: uint16((encoded >> 208) & type(uint16).max), + minor: uint16((encoded >> 192) & type(uint16).max), + patch: uint16((encoded >> 176) & type(uint16).max) + }); + + return (deprecationTimestamp, version); } /// @dev Sets the release info for a release digest. From 42beb45bffb1342ac3f3787e3239a7d952fd35fa Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:55:18 -0400 Subject: [PATCH 08/13] chore: make fmt --- src/contracts/core/ReleaseManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index 203513f03..ee77c3212 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -90,7 +90,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr bytes32 releaseDigest ) internal view returns (uint32, Version memory) { uint256 encoded = _releaseDigests[operatorSet.key()].get(releaseDigest); - + uint32 deprecationTimestamp = uint32(encoded >> 224); Version memory version = Version({ From e75fb263baad7c7874c7eed44032fa02d2839c00 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:18:44 -0400 Subject: [PATCH 09/13] wip --- src/contracts/core/ReleaseManager.sol | 253 +++++++++---------- src/contracts/core/ReleaseManagerStorage.sol | 17 +- src/contracts/interfaces/IReleaseManager.sol | 146 ++++------- 3 files changed, 161 insertions(+), 255 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index ee77c3212..a46897914 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -5,13 +5,10 @@ import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "../mixins/PermissionControllerMixin.sol"; import "../mixins/SemVerMixin.sol"; -import "../libraries/Snapshots.sol"; import "./ReleaseManagerStorage.sol"; contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionControllerMixin, SemVerMixin { - using Snapshots for Snapshots.DefaultZeroHistory; using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using OperatorSetLib for OperatorSet; using Strings for uint16; @@ -37,45 +34,61 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr * EXTERNAL FUNCTIONS * */ - - /// @inheritdoc IReleaseManager function publishRelease( OperatorSet calldata operatorSet, - bytes32 releaseDigest, - string calldata registryUrl, - Version calldata version - ) external checkCanCall(operatorSet.avs) { - // Parse the next releaseId (equal to the total number of releases). - uint256 releaseId = getTotalOperatorSetReleases(operatorSet); - - // Push the release digest and set the version. - _setReleaseInfo(operatorSet, releaseDigest, version, uint32(0)); - - // Push the release snapshot. - _releaseSnapshots[operatorSet.key()].push(uint32(block.timestamp), releaseId); - - // Set the release registry URL for the release digest. - _releaseRegistryUrls[operatorSet.key()][releaseDigest] = registryUrl; - - emit ReleasePublished(operatorSet, releaseDigest, registryUrl, version); + Artifacts[] calldata artifacts, + uint32 upgradeWindow, + ReleaseType releaseType + ) external checkCanCall(operatorSet.avs) returns (Version memory) { + Version memory version = getLatestVersion(operatorSet); + + uint32 upgradeByTime = uint32(block.timestamp + upgradeWindow); + + if (releaseType == ReleaseType.MAJOR) { + ++version.major; + version.minor = 0; + version.patch = 0; + _upgradeByTimes[operatorSet.key()][version.major] = upgradeByTime; + } else if (releaseType == ReleaseType.MINOR) { + ++version.minor; + version.patch = 0; + } else if (releaseType == ReleaseType.PATCH) { + ++version.patch; + } + + bytes32 versionKey = _encodeVersion(version); + + // Append the version to the operator set's version history. + _versions[operatorSet.key()].add(versionKey); + // Map the version to the release artifacts and deprecation time. + Release storage release = _releases[operatorSet.key()][versionKey]; + for (uint256 i = 0; i < artifacts.length; i++) { + release.artifacts.push(artifacts[i]); + } + + emit ReleasePublished(operatorSet.key(), versionKey, releaseType, upgradeByTime, artifacts); + + return version; } - /// @inheritdoc IReleaseManager function deprecateRelease( OperatorSet calldata operatorSet, - bytes32 releaseDigest, - uint32 deprecationTimestamp - ) external checkCanCall(operatorSet.avs) { - // Get the deprecation timestamp and version for the provided digest. - (uint32 currentDeprecationTimestamp, Version memory version) = _getReleaseInfo(operatorSet, releaseDigest); - - // Assert that the release is not already deprecated. - require(currentDeprecationTimestamp != uint32(0), ReleaseAlreadyDeprecated()); - - // Set the deprecation timestamp for the release digest. - _setReleaseInfo(operatorSet, releaseDigest, version, uint32(deprecationTimestamp)); - - emit DeprecationScheduled(operatorSet, releaseDigest, deprecationTimestamp); + Version calldata version, + uint32 deprecationDelay + ) public checkCanCall(operatorSet.avs) { + bytes32 versionKey = _encodeVersion(version); + + // Check that the release is not already deprecated and modify state. + Release storage release = _releases[operatorSet.key()][versionKey]; + require(release.deprecateAtTime == 0, ReleaseAlreadyDeprecated()); + uint32 deprecateAtTime = uint32(block.timestamp + deprecationDelay); + release.deprecateAtTime = deprecateAtTime; + + // Checked that there is at least one stable version after deprecating this release. + Version memory lastStableVersion = getLatestStableVersion(operatorSet); + require(lastStableVersion.major != type(uint16).max, MustHaveAtLeastOneStableVersion()); + + emit ReleaseDeprecated(operatorSet.key(), versionKey, deprecateAtTime); } /** @@ -83,40 +96,20 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr * INTERNAL FUNCTIONS * */ - - /// @dev Returns the deprecation timestamp and version for a release digest. - function _getReleaseInfo( - OperatorSet memory operatorSet, - bytes32 releaseDigest - ) internal view returns (uint32, Version memory) { - uint256 encoded = _releaseDigests[operatorSet.key()].get(releaseDigest); - - uint32 deprecationTimestamp = uint32(encoded >> 224); - - Version memory version = Version({ - major: uint16((encoded >> 208) & type(uint16).max), - minor: uint16((encoded >> 192) & type(uint16).max), - patch: uint16((encoded >> 176) & type(uint16).max) - }); - - return (deprecationTimestamp, version); + function _encodeVersion( + Version memory version + ) internal pure returns (bytes32) { + return bytes32(abi.encodePacked(version.major, version.minor, version.patch)); } - /// @dev Sets the release info for a release digest. - function _setReleaseInfo( - OperatorSet memory operatorSet, - bytes32 releaseDigest, - Version memory version, - uint32 deprecationTimestamp - ) internal { - // Push the release digest and set the version. - require( - _releaseDigests[operatorSet.key()].set( - releaseDigest, - uint256(bytes32(abi.encodePacked(deprecationTimestamp, version.major, version.minor, version.patch))) - ), - ReleaseAlreadyExists() - ); + function _decodeVersion( + bytes32 encoded + ) internal pure returns (Version memory) { + return Version({ + major: uint16((uint256(encoded) >> 208) & type(uint16).max), + minor: uint16((uint256(encoded) >> 192) & type(uint16).max), + patch: uint16((uint256(encoded) >> 176) & type(uint16).max) + }); } /** @@ -124,93 +117,73 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr * VIEW FUNCTIONS * */ - - /// @inheritdoc IReleaseManager - function isOperatorSetRelease(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool) { - return _releaseDigests[operatorSet.key()].contains(releaseDigest); - } - - /// @inheritdoc IReleaseManager - function getOperatorSetReleases( - OperatorSet memory operatorSet, - uint160 releaseId - ) external view returns (bytes32) { - (bytes32 releaseDigest,) = _releaseDigests[operatorSet.key()].at(releaseId); - - return releaseDigest; - } - - /// @inheritdoc IReleaseManager - function getOperatorSetReleases( + function getTotalReleaseCount( OperatorSet memory operatorSet - ) external view returns (bytes32[] memory) { - return _releaseDigests[operatorSet.key()].keys(); + ) external view returns (uint256) { + return _versions[operatorSet.key()].length(); } - /// @inheritdoc IReleaseManager - function getTotalOperatorSetReleases( - OperatorSet memory operatorSet - ) public view returns (uint256) { - return _releaseDigests[operatorSet.key()].length(); - } - - /// @inheritdoc IReleaseManager - function getLastOperatorSetRelease( - OperatorSet memory operatorSet - ) public view returns (bytes32) { - uint256 lastReleaseId = getTotalOperatorSetReleases(operatorSet) - 1; - - (bytes32 releaseDigest,) = _releaseDigests[operatorSet.key()].at(lastReleaseId); - - return releaseDigest; + function getVersion(OperatorSet memory operatorSet, uint256 index) external view returns (Version memory) { + return _decodeVersion(_versions[operatorSet.key()].at(index)); } - /// @inheritdoc IReleaseManager - function getReleaseAtTime( + function getRelease( OperatorSet memory operatorSet, - uint32 previousTimestamp - ) external view returns (bytes32) { - // Get the most recent snapshot that was published at or before the provided timestamp. - uint256 releaseId = _releaseSnapshots[operatorSet.key()].upperLookup(previousTimestamp); - - // Return the release releaseDigest for the `releaseId`, revert if nonexistent. - (bytes32 releaseDigest,) = _releaseDigests[operatorSet.key()].at(releaseId); - - return releaseDigest; + Version memory version + ) external view returns (Release memory) { + return _releases[operatorSet.key()][_encodeVersion(version)]; } - /// @inheritdoc IReleaseManager - function isReleaseDeprecated(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool) { - // Get the deprecation timestamp and version for the provided digest. - (uint32 deprecationTimestamp, Version memory version) = _getReleaseInfo(operatorSet, releaseDigest); + function getReleaseStatus( + OperatorSet memory operatorSet, + Version memory version + ) external view returns (ReleaseStatus) { + // First, check whether the version exists. + bool exists = _versions[operatorSet.key()].contains(_encodeVersion(version)); - // Get the the current major version for the operator set. - (, Version memory currentVersion) = _getReleaseInfo(operatorSet, getLastOperatorSetRelease(operatorSet)); + // If the version does not exist, it is not valid. + if (!exists) return ReleaseStatus.NONEXISTENT; - // If the deprecation timestamp is in the future, the release is not deprecated. - if (deprecationTimestamp > block.timestamp) return false; + // Second, check whether the version is deprecated by a force deprecation. + uint32 deprecateAtTime = _releases[operatorSet.key()][_encodeVersion(version)].deprecateAtTime; + if (deprecateAtTime != 0 && block.timestamp >= deprecateAtTime) return (ReleaseStatus.DEPRECATED); - // If the major version is the same as the current major version, the release is not deprecated. - if (version.major == currentVersion.major) return false; + // Third, check whether the version is deprecated by a major version upgrade. + uint32 lastUpgradeByTime = _upgradeByTimes[operatorSet.key()][getLatestStableVersion(operatorSet).major]; + if (lastUpgradeByTime != 0 && block.timestamp >= lastUpgradeByTime) return (ReleaseStatus.OUTDATED); - return true; + // Otherwise, the version is live. + return (ReleaseStatus.LIVE); } - /// @inheritdoc IReleaseManager - function getReleaseRegistryUrl( - OperatorSet memory operatorSet, - bytes32 releaseDigest - ) external view returns (string memory) { - return _releaseRegistryUrls[operatorSet.key()][releaseDigest]; + function getLatestVersion( + OperatorSet memory operatorSet + ) public view returns (Version memory) { + EnumerableSet.Bytes32Set storage versions = _versions[operatorSet.key()]; + return _decodeVersion(versions.at(versions.length() - 1)); } - /// @inheritdoc IReleaseManager - function getReleaseVersion( - OperatorSet memory operatorSet, - bytes32 releaseDigest - ) external view returns (string memory) { - (, Version memory version) = _getReleaseInfo(operatorSet, releaseDigest); - - return string.concat(version.major.toString(), ".", version.minor.toString(), ".", version.patch.toString()); + function getLatestStableVersion( + OperatorSet memory operatorSet + ) public view returns (Version memory) { + EnumerableSet.Bytes32Set storage versions = _versions[operatorSet.key()]; + uint256 versionCount = versions.length(); + + // Linear search backwards for the latest stable version. + for (uint256 i = versionCount - 1; i >= 0; i--) { + bytes32 versionKey = versions.at(i); + uint32 deprecateAtTime = _releases[operatorSet.key()][versionKey].deprecateAtTime; + + // If the release is deprecated, skip it. + if (block.timestamp >= deprecateAtTime) { + continue; + } + + // Othersise, return the release version. + return _decodeVersion(versionKey); + } + + // No version has been published. + return Version({major: type(uint16).max, minor: type(uint16).max, patch: type(uint16).max}); } } diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/ReleaseManagerStorage.sol index 1953ccaf5..9a2345277 100644 --- a/src/contracts/core/ReleaseManagerStorage.sol +++ b/src/contracts/core/ReleaseManagerStorage.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; -import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "../libraries/Snapshots.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../interfaces/IReleaseManager.sol"; import "../interfaces/IAllocationManager.sol"; @@ -14,17 +13,11 @@ abstract contract ReleaseManagerStorage is IReleaseManager { // Mutables - /// @notice A set of all release digests for an operator set. - /// @dev operatorSet, releaseId, releaseDigest => (uint32(deprecationTimestamp), version)) - mapping(bytes32 operatorSetKey => EnumerableMap.Bytes32ToUintMap releaseDigests) internal _releaseDigests; + mapping(bytes32 operatorSetKey => EnumerableSet.Bytes32Set versions) internal _versions; - /// @notice The deployment deadline for a release digest. - /// @dev operatorSet => (uint224(releaseId)) - mapping(bytes32 operatorSetKey => Snapshots.DefaultZeroHistory) internal _releaseSnapshots; + mapping(bytes32 operatorSetKey => mapping(bytes32 versionKey => Release release)) internal _releases; - /// @notice The registry URL for a release digest. - mapping(bytes32 operatorSetKey => mapping(bytes32 releaseDigest => string registryUrl)) internal - _releaseRegistryUrls; + mapping(bytes32 operatorSetKey => mapping(uint16 major => uint32 upgradeByTime)) internal _upgradeByTimes; constructor( IAllocationManager _allocationManager @@ -32,5 +25,5 @@ abstract contract ReleaseManagerStorage is IReleaseManager { allocationManager = _allocationManager; } - uint256[45] private __gap; + uint256[48] private __gap; } diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol index b51de4363..d1c18d7ba 100644 --- a/src/contracts/interfaces/IReleaseManager.sol +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -8,9 +8,25 @@ interface IReleaseManagerErrors { error ReleaseAlreadyExists(); /// @notice Thrown when a release with the same digest is already deprecated. error ReleaseAlreadyDeprecated(); + /// @notice Thrown when a deprecation is attempted that leaves an operator + /// set without a stable version. + error MustHaveAtLeastOneStableVersion(); } interface IReleaseManagerTypes { + enum ReleaseType { + MAJOR, + MINOR, + PATCH + } + + enum ReleaseStatus { + NONEXISTENT, + DEPRECATED, + OUTDATED, + LIVE + } + /// @notice A semantic version. /// @param major The major version. /// @param minor The minor version. @@ -20,114 +36,38 @@ interface IReleaseManagerTypes { uint16 minor; uint16 patch; } + + /// @notice An artifact. + /// @param digest The digest of the artifact. + /// @param registryUrl The registry URL of the artifact. + struct Artifact { + bytes32 digest; + string registryUrl; + } + + /// @notice A release. + /// @param artifacts The artifacts of the release. + /// @param deprecateAtTime The time at which the release is deprecated. + struct Release { + Artifact[] artifacts; + uint32 deprecateAtTime; + } } interface IReleaseManagerEvents is IReleaseManagerTypes { - /// @notice Emitted when a new release is published for an operator set. - /// @param operatorSet The operator set that the release was published for. - /// @param releaseDigest The unique identifier for the release content. - /// @param registryUrl The URL where the release artifacts can be found. - /// @param version The semantic version of the release. - event ReleasePublished(OperatorSet operatorSet, bytes32 releaseDigest, string registryUrl, Version version); - - /// @notice Emitted when a release deprecation is scheduled for an operator set. - /// @param operatorSet The operator set that the release was deprecated for. - /// @param releaseDigest The unique identifier for the release content. - /// @param deprecationTimestamp The timestamp when the release will be deprecated. - event DeprecationScheduled(OperatorSet operatorSet, bytes32 releaseDigest, uint32 deprecationTimestamp); + event ReleasePublished( + bytes32 indexed operatorSetKey, + bytes32 indexed versionKey, + ReleaseType indexed releaseType, + uint32 upgradeByTime, + Artifact[] artifacts + ); + + event ReleaseDeprecated(bytes32 indexed operatorSetKey, bytes32 indexed versionKey, uint32 deprecateAtTime); } interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { - // WRITE - - /// @notice Publishes a new release for an operator set. - /// @param operatorSet The operator set to publish the release for. - /// @param digest The unique identifier for the release content. - /// @param registryUrl The URL where the release artifacts can be found. - /// @param version The semantic version of the release. - /// @dev Only callable by addresses with permissioned by the operator set's AVS. - function publishRelease( - OperatorSet calldata operatorSet, - bytes32 digest, - string calldata registryUrl, - Version calldata version - ) external; - - /// @notice Deprecates a release for an operator set. - /// @param operatorSet The operator set to deprecate the release for. - /// @param digest The unique identifier for the release content. - /// @param deprecationTimestamp The timestamp when the release will be deprecated. - /// @dev Only callable by addresses with permissioned by the operator set's AVS. - function deprecateRelease(OperatorSet calldata operatorSet, bytes32 digest, uint32 deprecationTimestamp) external; - - // READ - - /// @notice Returns whether a release digest exists for an operator set. - /// @param operatorSet The operator set to check. - /// @param releaseDigest The release digest to check. - /// @return bool True if the release digest exists, false otherwise. - function isOperatorSetRelease(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool); - - /// @notice Returns a specific release digest for an operator set by its ID. - /// @param operatorSet The operator set to get the release from. - /// @param releaseId The ID of the release to get. - /// @return bytes32 The release digest. - function getOperatorSetReleases( - OperatorSet memory operatorSet, - uint160 releaseId - ) external view returns (bytes32); - - /// @notice Returns all release digests for an operator set. - /// @param operatorSet The operator set to get releases from. - /// @return bytes32[] Array of release digests. - function getOperatorSetReleases( - OperatorSet memory operatorSet - ) external view returns (bytes32[] memory); - - /// @notice Returns the total number of releases for an operator set. - /// @param operatorSet The operator set to count releases for. - /// @return uint256 The total number of releases. - function getTotalOperatorSetReleases( - OperatorSet memory operatorSet - ) external view returns (uint256); - - /// @notice Returns the most recent release digest for an operator set. - /// @param operatorSet The operator set to get the latest release from. - /// @return bytes32 The latest release digest. - function getLastOperatorSetRelease( - OperatorSet memory operatorSet - ) external view returns (bytes32); - - /// @notice Returns the release digest that was active at a specific timestamp. - /// @param operatorSet The operator set to get the release from. - /// @param previousTimestamp The timestamp to check. - /// @return bytes32 The release digest that was active at the timestamp. - function getReleaseAtTime( - OperatorSet memory operatorSet, - uint32 previousTimestamp - ) external view returns (bytes32); - - /// @notice Returns whether a release is deprecated. - /// @param operatorSet The operator set to check. - /// @param releaseDigest The release digest to check. - /// @return bool True if the release is deprecated, false otherwise. - function isReleaseDeprecated(OperatorSet memory operatorSet, bytes32 releaseDigest) external view returns (bool); - - /// @notice Returns the registry URL for a release digest. - /// @param operatorSet The operator set containing the release. - /// @param releaseDigest The release digest to get the registry URL for. - /// @return string The registry URL. - function getReleaseRegistryUrl( - OperatorSet memory operatorSet, - bytes32 releaseDigest - ) external view returns (string memory); +// WRITE - /// @notice Returns the semantic version string for a release. - /// @param operatorSet The operator set containing the release. - /// @param releaseDigest The release digest to get the version for. - /// @return string The semantic version string (e.g. "1.2.3"). - function getReleaseVersion( - OperatorSet memory operatorSet, - bytes32 releaseDigest - ) external view returns (string memory); +// READ } From 2e778149fb9c93642ffbbb289865fff14974dd46 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:25:24 -0400 Subject: [PATCH 10/13] wip --- src/contracts/core/ReleaseManager.sol | 16 +++++++++++++--- src/contracts/interfaces/IReleaseManager.sol | 6 ++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index a46897914..9181aaa41 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -36,7 +36,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr */ function publishRelease( OperatorSet calldata operatorSet, - Artifacts[] calldata artifacts, + Artifact[] calldata artifacts, uint32 upgradeWindow, ReleaseType releaseType ) external checkCanCall(operatorSet.avs) returns (Version memory) { @@ -66,7 +66,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr release.artifacts.push(artifacts[i]); } - emit ReleasePublished(operatorSet.key(), versionKey, releaseType, upgradeByTime, artifacts); + emit ReleasePublished(operatorSet.key(), version, releaseType, upgradeByTime, artifacts); return version; } @@ -88,7 +88,17 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr Version memory lastStableVersion = getLatestStableVersion(operatorSet); require(lastStableVersion.major != type(uint16).max, MustHaveAtLeastOneStableVersion()); - emit ReleaseDeprecated(operatorSet.key(), versionKey, deprecateAtTime); + emit ReleaseDeprecated(operatorSet.key(), version, deprecateAtTime); + } + + function extendUpgradeWindow( + OperatorSet calldata operatorSet, + Version calldata version, + uint32 upgradeWindow + ) public checkCanCall(operatorSet.avs) { + _upgradeByTimes[operatorSet.key()][version.major] = uint32(block.timestamp + upgradeWindow); + + // emit UpgradeWindowExtended(operatorSet.key(), _encodeVersion(version), upgradeWindow); } /** diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol index d1c18d7ba..a57c91b0d 100644 --- a/src/contracts/interfaces/IReleaseManager.sol +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -57,13 +57,15 @@ interface IReleaseManagerTypes { interface IReleaseManagerEvents is IReleaseManagerTypes { event ReleasePublished( bytes32 indexed operatorSetKey, - bytes32 indexed versionKey, + Version indexed version, ReleaseType indexed releaseType, uint32 upgradeByTime, Artifact[] artifacts ); - event ReleaseDeprecated(bytes32 indexed operatorSetKey, bytes32 indexed versionKey, uint32 deprecateAtTime); + event ReleaseDeprecated(bytes32 indexed operatorSetKey, Version indexed version, uint32 deprecateAtTime); + + event UpgradeWindowExtended(bytes32 indexed operatorSetKey, Version indexed version, uint32 upgradeWindow); } interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { From c022fbe558eff0331ee7231dd66b7e556bb624e9 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:38:25 -0400 Subject: [PATCH 11/13] wip --- src/contracts/core/ReleaseManager.sol | 20 +++- src/contracts/core/ReleaseManagerStorage.sol | 2 +- src/contracts/interfaces/IReleaseManager.sol | 105 ++++++++++++++++++- 3 files changed, 121 insertions(+), 6 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index 9181aaa41..947a4ee58 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -34,6 +34,8 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr * EXTERNAL FUNCTIONS * */ + + /// @inheritdoc IReleaseManager function publishRelease( OperatorSet calldata operatorSet, Artifact[] calldata artifacts, @@ -71,6 +73,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr return version; } + /// @inheritdoc IReleaseManager function deprecateRelease( OperatorSet calldata operatorSet, Version calldata version, @@ -91,6 +94,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr emit ReleaseDeprecated(operatorSet.key(), version, deprecateAtTime); } + /// @inheritdoc IReleaseManager function extendUpgradeWindow( OperatorSet calldata operatorSet, Version calldata version, @@ -98,7 +102,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr ) public checkCanCall(operatorSet.avs) { _upgradeByTimes[operatorSet.key()][version.major] = uint32(block.timestamp + upgradeWindow); - // emit UpgradeWindowExtended(operatorSet.key(), _encodeVersion(version), upgradeWindow); + emit UpgradeWindowExtended(operatorSet.key(), version, upgradeWindow); } /** @@ -106,12 +110,19 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr * INTERNAL FUNCTIONS * */ + /// @notice Encodes a Version struct into a bytes32 value for storage. + /// @dev Needed to store versions in an EnumerableSet. + /// @dev Packs the major, minor, and patch versions into a single bytes32 value. + /// The encoding format is: [major(16 bits)][minor(16 bits)][patch(16 bits)][padding(224 bits)] function _encodeVersion( Version memory version ) internal pure returns (bytes32) { return bytes32(abi.encodePacked(version.major, version.minor, version.patch)); } + /// @notice Decodes a bytes32 value back into a Version struct. + /// @dev Unpacks the major, minor, and patch versions from the encoded bytes32 value. + /// The decoding format is: [major(16 bits)][minor(16 bits)][patch(16 bits)][padding(224 bits)] function _decodeVersion( bytes32 encoded ) internal pure returns (Version memory) { @@ -127,16 +138,20 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr * VIEW FUNCTIONS * */ + + /// @inheritdoc IReleaseManager function getTotalReleaseCount( OperatorSet memory operatorSet ) external view returns (uint256) { return _versions[operatorSet.key()].length(); } + /// @inheritdoc IReleaseManager function getVersion(OperatorSet memory operatorSet, uint256 index) external view returns (Version memory) { return _decodeVersion(_versions[operatorSet.key()].at(index)); } + /// @inheritdoc IReleaseManager function getRelease( OperatorSet memory operatorSet, Version memory version @@ -144,6 +159,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr return _releases[operatorSet.key()][_encodeVersion(version)]; } + /// @inheritdoc IReleaseManager function getReleaseStatus( OperatorSet memory operatorSet, Version memory version @@ -166,6 +182,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr return (ReleaseStatus.LIVE); } + /// @inheritdoc IReleaseManager function getLatestVersion( OperatorSet memory operatorSet ) public view returns (Version memory) { @@ -173,6 +190,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr return _decodeVersion(versions.at(versions.length() - 1)); } + /// @inheritdoc IReleaseManager function getLatestStableVersion( OperatorSet memory operatorSet ) public view returns (Version memory) { diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/ReleaseManagerStorage.sol index 9a2345277..f8ac880b0 100644 --- a/src/contracts/core/ReleaseManagerStorage.sol +++ b/src/contracts/core/ReleaseManagerStorage.sol @@ -25,5 +25,5 @@ abstract contract ReleaseManagerStorage is IReleaseManager { allocationManager = _allocationManager; } - uint256[48] private __gap; + uint256[47] private __gap; } diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol index a57c91b0d..61aca288a 100644 --- a/src/contracts/interfaces/IReleaseManager.sol +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.27; import "../libraries/OperatorSetLib.sol"; interface IReleaseManagerErrors { - /// @notice Thrown when a release with the same digest already exists. - error ReleaseAlreadyExists(); /// @notice Thrown when a release with the same digest is already deprecated. error ReleaseAlreadyDeprecated(); /// @notice Thrown when a deprecation is attempted that leaves an operator @@ -14,12 +12,21 @@ interface IReleaseManagerErrors { } interface IReleaseManagerTypes { + /// @notice The type of release, following semantic versioning principles. + /// @custom:constant MAJOR A breaking change that requires operators to upgrade. + /// @custom:constant MINOR A backward-compatible feature addition. + /// @custom:constant PATCH A backward-compatible bug fix. enum ReleaseType { MAJOR, MINOR, PATCH } + /// @notice The current status of a release in the system. + /// @custom:constant NONEXISTENT The release has not been published. + /// @custom:constant DEPRECATED The release has been explicitly deprecated. + /// @custom:constant OUTDATED A newer major version has been released. + /// @custom:constant LIVE The release is currently active and valid. enum ReleaseStatus { NONEXISTENT, DEPRECATED, @@ -55,6 +62,12 @@ interface IReleaseManagerTypes { } interface IReleaseManagerEvents is IReleaseManagerTypes { + /// @notice Emitted when a new release is published for an operator set. + /// @param operatorSetKey The key identifying the operator set. + /// @param version The version number of the published release. + /// @param releaseType The type of release (MAJOR, MINOR, or PATCH). + /// @param upgradeByTime The timestamp by which operators must upgrade to this release. + /// @param artifacts The artifacts included in this release. event ReleasePublished( bytes32 indexed operatorSetKey, Version indexed version, @@ -63,13 +76,97 @@ interface IReleaseManagerEvents is IReleaseManagerTypes { Artifact[] artifacts ); + /// @notice Emitted when a release is deprecated. + /// @param operatorSetKey The key identifying the operator set. + /// @param version The version number of the deprecated release. + /// @param deprecateAtTime The timestamp at which the release becomes deprecated. event ReleaseDeprecated(bytes32 indexed operatorSetKey, Version indexed version, uint32 deprecateAtTime); + /// @notice Emitted when the upgrade window for a major version is extended. + /// @param operatorSetKey The key identifying the operator set. + /// @param version The version number of the release. + /// @param upgradeWindow The new upgrade window duration in seconds. event UpgradeWindowExtended(bytes32 indexed operatorSetKey, Version indexed version, uint32 upgradeWindow); } interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { -// WRITE + // WRITE + /// @notice Publishes a new release for an operator set with the specified artifacts and upgrade window. + /// @param operatorSet The operator set to publish the release for. + /// @param artifacts The artifacts included in this release. + /// @param upgradeWindow The time window (in seconds) for operators to upgrade to this release. + /// @param releaseType The type of release (MAJOR, MINOR, or PATCH). + /// @return The version number of the published release. + function publishRelease( + OperatorSet calldata operatorSet, + Artifact[] calldata artifacts, + uint32 upgradeWindow, + ReleaseType releaseType + ) external returns (Version memory); -// READ + /// @notice Deprecates a specific release version after a given delay. + /// @param operatorSet The operator set containing the release to deprecate. + /// @param version The version to deprecate. + /// @param deprecationDelay The delay (in seconds) before the release becomes deprecated. + function deprecateRelease( + OperatorSet calldata operatorSet, + Version calldata version, + uint32 deprecationDelay + ) external; + + /// @notice Extends the upgrade window for a major version release. + /// @param operatorSet The operator set containing the version. + /// @param version The version to extend the upgrade window for. + /// @param upgradeWindow The new upgrade window duration (in seconds). + function extendUpgradeWindow( + OperatorSet calldata operatorSet, + Version calldata version, + uint32 upgradeWindow + ) external; + + // READ + /// @notice Returns the total number of releases published for an operator set. + /// @param operatorSet The operator set to query. + /// @return The total number of releases. + function getTotalReleaseCount( + OperatorSet memory operatorSet + ) external view returns (uint256); + + /// @notice Returns the version at a specific index in the operator set's release history. + /// @param operatorSet The operator set to query. + /// @param index The index of the version to retrieve. + /// @return The version at the specified index. + function getVersion(OperatorSet memory operatorSet, uint256 index) external view returns (Version memory); + + /// @notice Returns the release details for a specific version. + /// @param operatorSet The operator set containing the release. + /// @param version The version to query. + /// @return The release details including artifacts and deprecation time. + function getRelease( + OperatorSet memory operatorSet, + Version memory version + ) external view returns (Release memory); + + /// @notice Returns the current status of a release version. + /// @param operatorSet The operator set containing the release. + /// @param version The version to check. + /// @return The status of the release (NONEXISTENT, DEPRECATED, OUTDATED, or LIVE). + function getReleaseStatus( + OperatorSet memory operatorSet, + Version memory version + ) external view returns (ReleaseStatus); + + /// @notice Returns the latest version published for an operator set. + /// @param operatorSet The operator set to query. + /// @return The latest version. + function getLatestVersion( + OperatorSet memory operatorSet + ) external view returns (Version memory); + + /// @notice Returns the latest stable (non-deprecated) version for an operator set. + /// @param operatorSet The operator set to query. + /// @return The latest stable version, or max values if no stable version exists. + function getLatestStableVersion( + OperatorSet memory operatorSet + ) external view returns (Version memory); } From 8cf7e53306d0a4721f298eaf89a989f705c5c0c2 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:42:21 -0400 Subject: [PATCH 12/13] wip --- src/contracts/core/ReleaseManager.sol | 7 ++++--- src/contracts/interfaces/IReleaseManager.sol | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index 947a4ee58..87a8c60fb 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -140,7 +140,7 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr */ /// @inheritdoc IReleaseManager - function getTotalReleaseCount( + function getTotalVersions( OperatorSet memory operatorSet ) external view returns (uint256) { return _versions[operatorSet.key()].length(); @@ -165,13 +165,14 @@ contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionContr Version memory version ) external view returns (ReleaseStatus) { // First, check whether the version exists. - bool exists = _versions[operatorSet.key()].contains(_encodeVersion(version)); + bytes32 versionKey = _encodeVersion(version); + bool exists = _versions[operatorSet.key()].contains(versionKey); // If the version does not exist, it is not valid. if (!exists) return ReleaseStatus.NONEXISTENT; // Second, check whether the version is deprecated by a force deprecation. - uint32 deprecateAtTime = _releases[operatorSet.key()][_encodeVersion(version)].deprecateAtTime; + uint32 deprecateAtTime = _releases[operatorSet.key()][versionKey].deprecateAtTime; if (deprecateAtTime != 0 && block.timestamp >= deprecateAtTime) return (ReleaseStatus.DEPRECATED); // Third, check whether the version is deprecated by a major version upgrade. diff --git a/src/contracts/interfaces/IReleaseManager.sol b/src/contracts/interfaces/IReleaseManager.sol index 61aca288a..371248b40 100644 --- a/src/contracts/interfaces/IReleaseManager.sol +++ b/src/contracts/interfaces/IReleaseManager.sol @@ -91,6 +91,7 @@ interface IReleaseManagerEvents is IReleaseManagerTypes { interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { // WRITE + /// @notice Publishes a new release for an operator set with the specified artifacts and upgrade window. /// @param operatorSet The operator set to publish the release for. /// @param artifacts The artifacts included in this release. @@ -125,10 +126,11 @@ interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents { ) external; // READ + /// @notice Returns the total number of releases published for an operator set. /// @param operatorSet The operator set to query. /// @return The total number of releases. - function getTotalReleaseCount( + function getTotalVersions( OperatorSet memory operatorSet ) external view returns (uint256); From 9079dcdf55ef918a5cd3e095c0e8ef25e72d7559 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:43:02 -0400 Subject: [PATCH 13/13] wip --- src/contracts/core/ReleaseManagerStorage.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/ReleaseManagerStorage.sol index f8ac880b0..649ccbbff 100644 --- a/src/contracts/core/ReleaseManagerStorage.sol +++ b/src/contracts/core/ReleaseManagerStorage.sol @@ -13,10 +13,21 @@ abstract contract ReleaseManagerStorage is IReleaseManager { // Mutables + /// @notice Maps operator set keys to their version history. + /// @param operatorSetKey The key identifying the operator set. + /// @return versions The set of version keys for this operator set. mapping(bytes32 operatorSetKey => EnumerableSet.Bytes32Set versions) internal _versions; + /// @notice Maps operator set and version keys to their release details. + /// @param operatorSetKey The key identifying the operator set. + /// @param versionKey The key identifying the version. + /// @return release The release details including artifacts and deprecation time. mapping(bytes32 operatorSetKey => mapping(bytes32 versionKey => Release release)) internal _releases; + /// @notice Maps operator set keys and major versions to their upgrade deadlines. + /// @param operatorSetKey The key identifying the operator set. + /// @param major The major version number. + /// @return upgradeByTime The timestamp by which operators must upgrade to this major version. mapping(bytes32 operatorSetKey => mapping(uint16 major => uint32 upgradeByTime)) internal _upgradeByTimes; constructor(