Skip to content

Commit c4a292a

Browse files
0xClandestineypatil12
authored andcommitted
feat: release manager (#1469)
**Motivation:** We want to enable AVSs to publish release metadata on-chain, giving operators a reliable and verifiable source of truth. **Modifications:** * Added a new core contract: `ReleaseManager`. * AVSs can call `publishRelease(operatorSet, release)` to push release data. **Result:** AVSs can now post release info on-chain, allowing operators to query a canonical, auditable source for updates.
1 parent 848a8d3 commit c4a292a

File tree

5 files changed

+922
-0
lines changed

5 files changed

+922
-0
lines changed

docs/core/ReleaseManager.md

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# ReleaseManager
2+
3+
| File | Notes |
4+
| -------- | -------- |
5+
| [`ReleaseManager.sol`](../../src/contracts/core/ReleaseManager.sol) | |
6+
| [`ReleaseManagerStorage.sol`](../../src/contracts/core/ReleaseManagerStorage.sol) | state variables |
7+
| [`IReleaseManager.sol`](../../src/contracts/interfaces/IReleaseManager.sol) | interface |
8+
9+
Libraries and Mixins:
10+
11+
| File | Notes |
12+
| -------- | -------- |
13+
| [`PermissionControllerMixin.sol`](../../src/contracts/mixins/PermissionControllerMixin.sol) | account delegation |
14+
| [`SemVerMixin.sol`](../../src/contracts/mixins/SemVerMixin.sol) | semantic versioning |
15+
| [`OperatorSetLib.sol`](../../src/contracts/libraries/OperatorSetLib.sol) | encode/decode operator sets |
16+
17+
## Overview
18+
19+
The `ReleaseManager` manages software releases for AVS operator sets. It provides a standardized way for AVSs to publish software artifacts (binaries, docker images, etc.) that operators in their operator sets should upgrade to by specified deadlines. This ensures operators run compatible and up-to-date software versions required by the AVS.
20+
21+
The `ReleaseManager's` responsibilities include:
22+
23+
* **Release Publishing**: AVSs can publish new releases containing one or more software artifacts for their operator sets.
24+
* **Upgrade Deadlines**: Each release specifies a deadline by which operators must upgrade.
25+
* **Release Tracking**: Maintains a history of all releases for each operator set.
26+
* **Release Queries**: Provides view functions to query release information.
27+
28+
An AVS in the context of `ReleaseManager` is defined as the `address` of the contract that implements the AVS logic. For `PermissionController` purposes, this AVS address is also the AVS [account](https://github.com/Layr-Labs/eigenlayer-contracts/blob/dev/docs/permissions/PermissionController.md#accounts).
29+
30+
### Important Notes on Release Management
31+
32+
* **Latest Release Validity**: Only the latest release for an operator set is considered valid. Previous releases become obsolete as soon as a new release is published.
33+
* **Upgrade Deadlines**: The `upgradeByTime` timestamp (in Unix time) is a suggested deadline and is not enforced on-chain or off-chain. It serves as a communication mechanism for AVSs to indicate when operators should complete their upgrades.
34+
* **Multiple Releases in Same Block**: If multiple releases are published in the same block with the same `upgradeByTime`, the last transaction processed in that block will determine the latest valid release.
35+
36+
---
37+
38+
## Releases
39+
40+
A release represents a collection of software artifacts that operators in an operator set must run. Each release is associated with a specific operator set and contains:
41+
42+
* **Artifacts**: An array of software artifacts, each with a digest (hash) and registry URL
43+
* **Upgrade By Time**: A Unix timestamp by which operators should complete the upgrade
44+
45+
The release structure is defined as:
46+
47+
```solidity
48+
/**
49+
* @notice Represents a software artifact with its digest and registry URL.
50+
* @param digest The hash digest of the artifact.
51+
* @param registryUrl The URL where the artifact can be found.
52+
*/
53+
struct Artifact {
54+
bytes32 digest;
55+
string registryUrl;
56+
}
57+
58+
/**
59+
* @notice Represents a release containing multiple artifacts and an upgrade deadline.
60+
* @param artifacts Array of artifacts included in this release.
61+
* @param upgradeByTime Timestamp by which operators must upgrade to this release.
62+
*/
63+
struct Release {
64+
Artifact[] artifacts;
65+
uint32 upgradeByTime;
66+
}
67+
```
68+
69+
**State Management:**
70+
71+
```solidity
72+
/// @dev Mapping from operator set key to array of releases for that operator set
73+
mapping(bytes32 operatorSetKey => Release[]) internal _operatorSetReleases;
74+
```
75+
76+
**Methods:**
77+
* [`publishRelease`](#publishrelease)
78+
* [`getTotalReleases`](#gettotalreleases)
79+
* [`getRelease`](#getrelease)
80+
* [`getLatestRelease`](#getlatestrelease)
81+
* [`getLatestUpgradeByTime`](#getlatestupgradebytime)
82+
* [`isValidRelease`](#isValidRelease)
83+
84+
---
85+
86+
### Write Functions
87+
88+
#### `publishRelease`
89+
90+
```solidity
91+
/**
92+
* @notice Publishes a new release for an operator set.
93+
* @param operatorSet The operator set this release is for.
94+
* @param release The release that was published.
95+
* @return releaseId The index of the newly published release.
96+
*/
97+
function publishRelease(
98+
OperatorSet calldata operatorSet,
99+
Release calldata release
100+
)
101+
external
102+
checkCanCall(operatorSet.avs)
103+
returns (uint256 releaseId)
104+
```
105+
106+
_Note: this method can be called directly by an AVS, or by a caller authorized by the AVS. See [`PermissionController.md`](../permissions/PermissionController.md) for details._
107+
108+
AVSs use this method to publish new software releases for their operator sets. Each release contains one or more artifacts that represent the software components operators must run (e.g., validator clients, network monitors, etc.). The AVS specifies a deadline (`upgradeByTime`) by which all operators in the operator set must upgrade to the new release.
109+
110+
The `releaseId` returned is the zero-based index of the release in the operator set's release array. This ID can be used to query the release later using [`getRelease`](#getrelease).
111+
112+
*Effects*:
113+
* Appends the release to `_operatorSetReleases[operatorSet.key()]`
114+
* The new release is assigned a `releaseId` equal to the previous array length
115+
* All artifact information (digest and registryUrl) is copied to storage
116+
* Emits a `ReleasePublished` event with the operator set, release ID, and release details
117+
118+
*Requirements*:
119+
* Caller MUST be authorized, either as the AVS itself or an admin/appointee (see [`PermissionController.md`](../permissions/PermissionController.md))
120+
* `release.upgradeByTime` MUST be greater than or equal to the current block timestamp
121+
---
122+
123+
### View Functions
124+
125+
#### `getTotalReleases`
126+
127+
```solidity
128+
/**
129+
* @notice Returns the total number of releases for an operator set.
130+
* @param operatorSet The operator set to query.
131+
* @return The number of releases.
132+
*/
133+
function getTotalReleases(
134+
OperatorSet memory operatorSet
135+
)
136+
public
137+
view
138+
returns (uint256)
139+
```
140+
141+
Returns the total number of releases that have been published for the specified operator set. This can be used to iterate through all releases or to determine if any releases exist.
142+
143+
*Returns*:
144+
* The length of the releases array for the given operator set
145+
* Returns 0 if no releases have been published
146+
147+
#### `getRelease`
148+
149+
```solidity
150+
/**
151+
* @notice Returns a specific release by index.
152+
* @param operatorSet The operator set to query.
153+
* @param releaseId The id of the release to get.
154+
* @return The release at the specified index.
155+
*/
156+
function getRelease(
157+
OperatorSet memory operatorSet,
158+
uint256 releaseId
159+
)
160+
external
161+
view
162+
returns (Release memory)
163+
```
164+
165+
Retrieves a specific release by its ID for a given operator set. The `releaseId` is the zero-based index of the release in the operator set's release history.
166+
167+
*Returns*:
168+
* The complete `Release` struct including all artifacts and the upgrade deadline
169+
* Reverts if `releaseId` is out of bounds
170+
171+
#### `getLatestRelease`
172+
173+
```solidity
174+
/**
175+
* @notice Returns the latest release for an operator set.
176+
* @param operatorSet The operator set to query.
177+
* @return The id of the latest release.
178+
* @return The latest release.
179+
*/
180+
function getLatestRelease(
181+
OperatorSet memory operatorSet
182+
)
183+
public
184+
view
185+
returns (uint256, Release memory)
186+
```
187+
188+
Retrieves the most recently published release for an operator set. This is typically the release that operators should be running or upgrading to.
189+
190+
*Returns*:
191+
* The latest `Release` struct from the operator set's release array
192+
* Reverts if no releases have been published for the operator set
193+
194+
#### `getLatestUpgradeByTime`
195+
196+
```solidity
197+
/**
198+
* @notice Returns the upgrade by time for the latest release.
199+
* @param operatorSet The operator set to query.
200+
* @return The upgrade by time for the latest release.
201+
*/
202+
function getLatestUpgradeByTime(
203+
OperatorSet memory operatorSet
204+
)
205+
external
206+
view
207+
returns (uint256)
208+
```
209+
210+
A convenience function that returns just the upgrade deadline from the latest release. This can be useful for quickly checking when operators must complete their upgrades.
211+
212+
*Returns*:
213+
* The `upgradeByTime` timestamp from the latest release
214+
* Reverts if no releases have been published for the operator set
215+
216+
#### `isValidRelease`
217+
218+
```solidity
219+
/**
220+
* @notice Returns true if the release is the latest release, false otherwise.
221+
* @param operatorSet The operator set to query.
222+
* @param releaseId The id of the release to check.
223+
* @return True if the release is the latest release, false otherwise.
224+
*/
225+
function isValidRelease(
226+
OperatorSet memory operatorSet,
227+
uint256 releaseId
228+
)
229+
external
230+
view
231+
returns (bool)
232+
```
233+
234+
Checks whether a given release ID corresponds to the latest release for an operator set. This can be useful for operators to verify they are running the most current software version.
235+
236+
**Note**: Only the latest release is considered valid. All previous releases are considered obsolete and should not be used by operators.
237+
238+
*Returns*:
239+
* `true` if the `releaseId` matches the latest release index
240+
* `false` if the `releaseId` refers to an older release
241+
* Reverts if the operator set has no releases
242+
243+
---

src/contracts/core/ReleaseManager.sol

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5+
import "../mixins/PermissionControllerMixin.sol";
6+
import "../mixins/SemVerMixin.sol";
7+
import "./ReleaseManagerStorage.sol";
8+
9+
contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionControllerMixin, SemVerMixin {
10+
using OperatorSetLib for OperatorSet;
11+
12+
/**
13+
*
14+
* INITIALIZING FUNCTIONS
15+
*
16+
*/
17+
constructor(
18+
IPermissionController _permissionController,
19+
string memory _version
20+
) PermissionControllerMixin(_permissionController) SemVerMixin(_version) {
21+
_disableInitializers();
22+
}
23+
24+
/**
25+
*
26+
* EXTERNAL FUNCTIONS
27+
*
28+
*/
29+
30+
/// @inheritdoc IReleaseManager
31+
function publishRelease(
32+
OperatorSet calldata operatorSet,
33+
Release calldata release
34+
) external checkCanCall(operatorSet.avs) returns (uint256 releaseId) {
35+
Release[] storage releases = _operatorSetReleases[operatorSet.key()];
36+
37+
require(release.upgradeByTime >= block.timestamp, InvalidUpgradeByTime());
38+
39+
// New release id is the length of the array before this call.
40+
releaseId = releases.length;
41+
// Increment total releases for this operator set.
42+
releases.push();
43+
// Copy the release to storage.
44+
for (uint256 i = 0; i < release.artifacts.length; ++i) {
45+
releases[releaseId].artifacts.push(release.artifacts[i]);
46+
}
47+
releases[releaseId].upgradeByTime = release.upgradeByTime;
48+
49+
emit ReleasePublished(operatorSet, releaseId, release);
50+
}
51+
52+
/**
53+
*
54+
* VIEW FUNCTIONS
55+
*
56+
*/
57+
58+
/// @inheritdoc IReleaseManager
59+
function getTotalReleases(
60+
OperatorSet memory operatorSet
61+
) public view returns (uint256) {
62+
return _operatorSetReleases[operatorSet.key()].length;
63+
}
64+
65+
/// @inheritdoc IReleaseManager
66+
function getRelease(OperatorSet memory operatorSet, uint256 releaseId) external view returns (Release memory) {
67+
return _operatorSetReleases[operatorSet.key()][releaseId];
68+
}
69+
70+
/// @inheritdoc IReleaseManager
71+
function getLatestRelease(
72+
OperatorSet memory operatorSet
73+
) public view returns (uint256, Release memory) {
74+
Release[] storage releases = _operatorSetReleases[operatorSet.key()];
75+
uint256 latestReleaseId = releases.length - 1;
76+
return (latestReleaseId, releases[latestReleaseId]);
77+
}
78+
79+
/// @inheritdoc IReleaseManager
80+
function getLatestUpgradeByTime(
81+
OperatorSet memory operatorSet
82+
) external view returns (uint32) {
83+
Release[] storage releases = _operatorSetReleases[operatorSet.key()];
84+
uint256 latestReleaseId = releases.length - 1;
85+
return releases[latestReleaseId].upgradeByTime;
86+
}
87+
88+
/// @inheritdoc IReleaseManager
89+
function isValidRelease(OperatorSet memory operatorSet, uint256 releaseId) external view returns (bool) {
90+
return releaseId == getTotalReleases(operatorSet) - 1;
91+
}
92+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "../interfaces/IReleaseManager.sol";
5+
import "../interfaces/IAllocationManager.sol";
6+
7+
abstract contract ReleaseManagerStorage is IReleaseManager {
8+
// Mutables
9+
10+
/// @notice Returns an array of releases for a given operator set.
11+
mapping(bytes32 operatorSetKey => Release[]) internal _operatorSetReleases;
12+
13+
/**
14+
* @dev This empty reserved space is put in place to allow future versions to add new
15+
* variables without shifting down storage in the inheritance chain.
16+
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
17+
*/
18+
uint256[49] private __gap;
19+
}

0 commit comments

Comments
 (0)