Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/AppController.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {SignatureUtilsMixin} from "@eigenlayer-contracts/src/contracts/mixins/SignatureUtilsMixin.sol";
import {IPermissionController} from "@eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
import {PermissionControllerMixin} from "@eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol";
import {
IReleaseManager,
IReleaseManagerTypes
} from "@eigenlayer-contracts/src/contracts/interfaces/IReleaseManager.sol";
import {OperatorSet} from "@eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
import {IComputeAVSRegistrar} from "./interfaces/IComputeAVSRegistrar.sol";
import {IComputeOperator} from "./interfaces/IComputeOperator.sol";
import {AppControllerStorage} from "./storage/AppControllerStorage.sol";
import {IAppController} from "./interfaces/IAppController.sol";
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import {IApp} from "./interfaces/IApp.sol";
contract AppController is Initializable, SignatureUtilsMixin, PermissionControllerMixin, AppControllerStorage {
using EnumerableSet for EnumerableSet.AddressSet;
/// MODIFIERS
/// @notice Modifier to ensure app exists
modifier appExists(IApp app) {
require(_exists(_appConfigs[app].status), InvalidAppStatus());
_;
}
/// @notice Modifier to ensure app is in an active status
modifier appIsActive(IApp app) {
require(_isActive(_appConfigs[app].status), InvalidAppStatus());
_;
}
/**
* @param _version The version string to use for this contract's domain separator
* @param _permissionController The PermissionController contract address
* @param _computeAVSRegistrar The ComputeAVSRegistrar contract address
* @param _computeOperator The ComputeOperator contract address
* @param _appBeacon The beacon for creating App proxies
*/
constructor(
string memory _version,
IPermissionController _permissionController,
IReleaseManager _releaseManager,
IComputeAVSRegistrar _computeAVSRegistrar,
IComputeOperator _computeOperator,
IBeacon _appBeacon
)
SignatureUtilsMixin(_version)
PermissionControllerMixin(_permissionController)
AppControllerStorage(_releaseManager, _computeOperator, _computeAVSRegistrar, _appBeacon)
{
_disableInitializers();
}
/// @inheritdoc IAppController
function initialize(address admin) external initializer {
// Accept the ComputeAVSRegistrar as an admin
permissionController.acceptAdmin(address(computeAVSRegistrar));
// Add this contract itself as an admin
permissionController.addPendingAdmin(address(this), address(this));
permissionController.acceptAdmin(address(this));
// Add the app controller as an admin
permissionController.addPendingAdmin(address(this), admin);
// Assign an initial operator set ID
uint32 operatorSetId = computeAVSRegistrar.assignOperatorSetId();
// Create the operator set
computeAVSRegistrar.createOperatorSet(operatorSetId);
// Add the compute operator to the allowlist
computeAVSRegistrar.addOperatorToAllowlist(
OperatorSet({avs: address(computeAVSRegistrar), id: operatorSetId}), address(computeOperator)
);
// Register the compute operator for the operator set
computeOperator.registerForOperatorSet(operatorSetId);
}
/// @inheritdoc IAppController
function setMaxActiveAppsPerUser(address user, uint32 limit) external checkCanCall(address(this)) {
_setMaxActiveAppsPerUser(user, limit);
}
/// @inheritdoc IAppController
function setMaxGlobalActiveApps(uint32 limit) external checkCanCall(address(this)) {
maxGlobalActiveApps = limit;
emit GlobalMaxActiveAppsSet(limit);
}
/// @inheritdoc IAppController
function createApp(bytes32 salt, Release calldata release) external returns (IApp app) {
_checkAndIncrementActiveApps(msg.sender);
// Assign an operator set ID to the app
uint32 operatorSetId = computeAVSRegistrar.assignOperatorSetId();
// Publish the initial release metadata URI
releaseManager.publishMetadataURI(
OperatorSet({avs: address(computeAVSRegistrar), id: operatorSetId}), "https://eigencloud.xyz"
);
// Create app using BeaconProxy
app = IApp(Create2.deploy(0, _calculateAppMixedSalt(msg.sender, salt), _calculateAppInitCode(msg.sender)));
_appConfigs[app].operatorSetId = operatorSetId;
_appConfigs[app].latestReleaseBlockNumber = 0;
_appConfigs[app].creator = msg.sender;
_allApps.add(address(app));
emit AppCreated(msg.sender, app, operatorSetId);
// Upgrade the app with the initial release
_upgradeApp(app, release);
_startApp(app);
}
/// @inheritdoc IAppController
function upgradeApp(IApp app, Release calldata release)
external
checkCanCall(address(app))
appIsActive(app)
returns (uint256)
{
return _upgradeApp(app, release);
}
/// @inheritdoc IAppController
function updateAppMetadataURI(IApp app, string calldata metadataURI)
external
checkCanCall(address(app))
appExists(app)
{
emit AppMetadataURIUpdated(app, metadataURI);
}
/// @inheritdoc IAppController
function startApp(IApp app) external checkCanCall(address(app)) appExists(app) {
_startApp(app);
}
/// @inheritdoc IAppController
function stopApp(IApp app) external checkCanCall(address(app)) {
AppConfig storage config = _appConfigs[app];
require(config.status == AppStatus.STARTED, InvalidAppStatus());
config.status = AppStatus.STOPPED;
emit AppStopped(app);
}
/// @inheritdoc IAppController
function terminateApp(IApp app) external checkCanCall(address(app)) appIsActive(app) {
_terminateApp(app);
}
/// @inheritdoc IAppController
function terminateAppByAdmin(IApp app) external checkCanCall(address(this)) appIsActive(app) {
_terminateApp(app);
emit AppTerminatedByAdmin(app);
}
/// @inheritdoc IAppController
function suspend(address account, IApp[] calldata apps) external {
// Allow account owner to self-suspend or AppController admin to enforce suspension
require(msg.sender == account || _checkCanCall(address(this)), InvalidPermissions());
// Suspend all provided apps, skipping apps that are already SUSPENDED, TERMINATED, or NONE
for (uint256 i = 0; i < apps.length; i++) {
IApp app = apps[i];
AppConfig memory config = _appConfigs[app];
// Validate ownership
require(config.creator == account, InvalidAppStatus());
// Only suspend if app is active (STARTED or STOPPED)
if (_isActive(config.status)) {
_suspendApp(app);
}
}
// Verify all active apps were provided - prevents partial suspension
require(_userConfigs[account].activeAppCount == 0, AccountHasActiveApps());
// Zero-out the account's max active apps
_setMaxActiveAppsPerUser(account, 0);
}
/// INTERNAL FUNCTIONS
/**
* @notice Checks if an app status is not NONE
* @param status The app status to check
* @return True if status is not NONE, false otherwise
*/
function _exists(AppStatus status) internal pure returns (bool) {
return status != AppStatus.NONE;
}
/**
* @notice Checks if an app status is active
* @param status The app status to check
* @return True if status is STARTED or STOPPED, false otherwise
*/
function _isActive(AppStatus status) internal pure returns (bool) {
return status == AppStatus.STARTED || status == AppStatus.STOPPED;
}
/**
* @notice Checks active app limits and increments counters for a user
* @param user The user address to check and increment for
*/
function _checkAndIncrementActiveApps(address user) internal {
UserConfig storage userConfig = _userConfigs[user];
// Check global active app limit
require(globalActiveAppCount < maxGlobalActiveApps, GlobalMaxActiveAppsExceeded());
// Check user active app limit
require(userConfig.activeAppCount < userConfig.maxActiveApps, MaxActiveAppsExceeded());
// Increment active app counts
globalActiveAppCount++;
userConfig.activeAppCount++;
}
/**
* @notice Decrements global and creator active app counters
* @param app The app instance to decrement counters for
*/
function _decrementActiveApps(IApp app) internal {
// Decrement active app counts to free up capacity
globalActiveAppCount--;
// Decrement the creator's active app count
address appCreator = _appConfigs[app].creator;
_userConfigs[appCreator].activeAppCount--;
}
/**
* @notice Sets the maximum number of active apps allowed for a user
* @param user The user address to set the limit for
* @param limit The maximum number of active apps allowed
*/
function _setMaxActiveAppsPerUser(address user, uint32 limit) internal {
_userConfigs[user].maxActiveApps = limit;
emit MaxActiveAppsSet(user, limit);
}
/**
* @notice Upgrades an app to a new release by publishing it through the release manager
* @param app The app instance to upgrade
* @param release The new release data containing artifacts and metadata
* @return releaseId The unique identifier assigned to the published release by the release manager
*/
function _upgradeApp(IApp app, Release calldata release) internal returns (uint256 releaseId) {
// Check that the release has exactly one artifact
require(release.rmsRelease.artifacts.length == 1, MoreThanOneArtifact());
releaseId = releaseManager.publishRelease(
OperatorSet({avs: address(computeAVSRegistrar), id: _appConfigs[app].operatorSetId}), release.rmsRelease
);
_appConfigs[app].latestReleaseBlockNumber = uint32(block.number);
emit AppUpgraded(app, releaseId, release);
}
/**
* @notice Starts an app and marks it as started
* @param app The app instance to start
*/
function _startApp(IApp app) internal {
AppConfig storage config = _appConfigs[app];
require(config.status != AppStatus.TERMINATED, InvalidAppStatus());
// If resuming from suspended, re-check limits and increment active app counters
if (config.status == AppStatus.SUSPENDED) {
_checkAndIncrementActiveApps(config.creator);
}
config.status = AppStatus.STARTED;
emit AppStarted(app);
}
/**
* @notice Terminates an app and decrements active app counters
* @param app The app instance to terminate
*/
function _terminateApp(IApp app) internal {
_appConfigs[app].status = AppStatus.TERMINATED;
_decrementActiveApps(app);
emit AppTerminated(app);
}
/**
* @notice Suspends an app and decrements active app counters
* @param app The app instance to suspend
*/
function _suspendApp(IApp app) internal {
_appConfigs[app].status = AppStatus.SUSPENDED;
_decrementActiveApps(app);
emit AppSuspended(app);
}
/**
* @notice Calculates a mixed salt for app deployment using deployer address and provided salt
* @param deployer The address of the app deployer
* @param salt The salt value
* @return The keccak256 hash of deployer and salt, used for deterministic app address generation
*/
function _calculateAppMixedSalt(address deployer, bytes32 salt) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(deployer, salt));
}
/**
* @notice Generates the initialization code for deploying a new app using beacon proxy pattern
* @param deployer The address that will be set as the app owner during initialization
* @return The complete bytecode for deploying a BeaconProxy that initializes the app with the deployer
*/
function _calculateAppInitCode(address deployer) internal view returns (bytes memory) {
return abi.encodePacked(
_BEACON_PROXY_BYTECODE, abi.encode(appBeacon, abi.encodeWithSelector(IApp.initialize.selector, deployer))
);
}
/**
* @notice Gets filtered apps based on a predicate function
* @param predicate The predicate function to apply to each app
* @param target The target address to filter by
* @param offset The offset to start from
* @param limit The maximum number of apps to return
* @return apps The filtered apps
* @return appConfigsMem The app configs for the filtered apps
*/
function _getFilteredApps(
function(IApp, address) view returns (bool) predicate,
address target,
uint256 offset,
uint256 limit
) private view returns (IApp[] memory apps, AppConfig[] memory appConfigsMem) {
uint256 totalApps = _allApps.length();
apps = new IApp[](limit);
appConfigsMem = new AppConfig[](limit);
uint256 skipped = 0;
uint256 found = 0;
for (uint256 i = 0; i < totalApps && found < limit; i++) {
IApp app = IApp(_allApps.at(i));
if (predicate(app, target)) {
if (skipped < offset) {
skipped++;
continue;
}
apps[found] = app;
appConfigsMem[found] = _appConfigs[app];
found++;
}
}
// Resize arrays to actual number found
assembly {
mstore(apps, found)
mstore(appConfigsMem, found)
}
}
/**
* @notice Check if address is developer of app
* @param app The app to check
* @param developer The developer to check
* @return True if the developer is the developer of the app
*/
function _isDeveloper(IApp app, address developer) private view returns (bool) {
return permissionController.isAdmin(address(app), developer);
}
/**
* @notice Check if address is creator of app
* @param app The app to check
* @param creator The creator to check
* @return True if the creator is the creator of the app
*/
function _isCreator(IApp app, address creator) private view returns (bool) {
return _appConfigs[app].creator == creator;
}
/// VIEW FUNCTIONS
/// @inheritdoc IAppController
function getMaxActiveAppsPerUser(address user) external view returns (uint32) {
return _userConfigs[user].maxActiveApps;
}
/// @inheritdoc IAppController
function getActiveAppCount(address user) external view returns (uint32) {
return _userConfigs[user].activeAppCount;
}
/// @inheritdoc IAppController
function calculateAppId(address deployer, bytes32 salt) external view returns (IApp) {
return IApp(
Create2.computeAddress(
_calculateAppMixedSalt(deployer, salt),
keccak256(_calculateAppInitCode(deployer)) //bytecode
)
);
}
/// @inheritdoc IAppController
function getAppStatus(IApp app) external view returns (AppStatus) {
return _appConfigs[app].status;
}
/// @inheritdoc IAppController
function getAppCreator(IApp app) external view returns (address) {
return _appConfigs[app].creator;
}
/// @inheritdoc IAppController
function getAppOperatorSetId(IApp app) external view returns (uint32) {
return _appConfigs[app].operatorSetId;
}
/// @inheritdoc IAppController
function getAppLatestReleaseBlockNumber(IApp app) external view returns (uint32) {
return _appConfigs[app].latestReleaseBlockNumber;
}
/// @inheritdoc IAppController
function getApps(uint256 offset, uint256 limit)
external
view
returns (IApp[] memory apps, AppConfig[] memory appConfigsMem)
{
uint256 totalApps = _allApps.length();
if (offset >= totalApps) return (new IApp[](0), new AppConfig[](0));
uint256 end = offset + limit > totalApps ? totalApps : offset + limit;
uint256 rangeSize = end - offset;
apps = new IApp[](rangeSize);
appConfigsMem = new AppConfig[](rangeSize);
for (uint256 i = 0; i < rangeSize; i++) {
apps[i] = IApp(_allApps.at(offset + i));
appConfigsMem[i] = _appConfigs[apps[i]];
}
}
/// @inheritdoc IAppController
function getAppsByDeveloper(address developer, uint256 offset, uint256 limit)
external
view
returns (IApp[] memory apps, AppConfig[] memory appConfigsMem)
{
return _getFilteredApps(_isDeveloper, developer, offset, limit);
}
/// @inheritdoc IAppController
function getAppsByCreator(address creator, uint256 offset, uint256 limit)
external
view
returns (IApp[] memory apps, AppConfig[] memory appConfigsMem)
{
return _getFilteredApps(_isCreator, creator, offset, limit);
}
/// @inheritdoc IAppController
function calculateApiPermissionDigestHash(bytes4 permission, uint256 expiry) external view returns (bytes32) {
return _calculateSignableDigest(keccak256(abi.encode(API_PERMISSION_TYPEHASH, permission, expiry)));
}
}
"
},
"lib/eigenlayer-middleware/lib/openzeppelin-contracts/contracts/utils/Create2.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Create2.sol)
pragma solidity ^0.8.0;
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
require(address(this).balance >= amount, "Create2: insufficient balance");
require(bytecode.length != 0, "Create2: bytecode length is zero");
/// @solidity memory-safe-assembly
assembly {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
}
require(addr != address(0), "Create2: Failed on deploy");
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := keccak256(start, 85)
}
}
}
"
},
"lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
"
},
"lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
"
},
"lib/eigenlayer-middleware/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
"
},
"lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/mixins/SignatureUtilsMixin.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/utils/cryptography/SignatureCheckerUpgradeable.sol";
import "../interfaces/ISignatureUtilsMixin.sol";
import "./SemVerMixin.sol";
/// @dev The EIP-712 domain type hash used for computing the domain separator
/// See https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
bytes32 constant EIP712_DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @title SignatureUtilsMixin
/// @notice A mixin contract that provides utilities for validating signatures according to EIP-712 and EIP-1271 standards.
/// @dev The domain name is hardcoded to "EigenLayer". This contract implements signature validation functionality that can be
/// inherited by other contracts. The domain separator uses the major version (e.g., "v1") to maintain EIP-712
/// signature compatibility across minor and patch version updates.
abstract contract SignatureUtilsMixin is ISignatureUtilsMixin, SemVerMixin {
using SignatureCheckerUpgradeable for address;
/// @notice Initializes the contract with a semantic version string.
/// @param _version The SemVer-formatted version string (e.g., "1.1.1") to use for this contract's domain separator.
/// @dev Version should follow SemVer 2.0.0 format with 'v' prefix: vMAJOR.MINOR.PATCH.
/// Only the major version component is used in the domain separator to maintain signature compatibility
/// across minor and patch version updates.
constructor(
string memory _version
) SemVerMixin(_version) {}
/// EXTERNAL FUNCTIONS ///
/// @inheritdoc ISignatureUtilsMixin
function domainSeparator() public view virtual returns (bytes32) {
// forgefmt: disable-next-item
return
keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes("EigenLayer")),
keccak256(bytes(_majorVersion())),
block.chainid,
address(this)
)
);
}
/// INTERNAL HELPERS ///
/// @notice Creates a digest that can be signed using EIP-712.
/// @dev Prepends the EIP-712 prefix ("\x19\x01") and domain separator to the input hash.
/// This follows the EIP-712 specification for creating structured data hashes.
/// See https://eips.ethereum.org/EIPS/eip-712#specification.
/// @param hash The hash of the typed data to be signed.
/// @return The complete digest that should be signed according to EIP-712.
function _calculateSignableDigest(
bytes32 hash
) internal view returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator(), hash));
}
/// @notice Validates a signature against a signer and digest, with an expiry timestamp.
/// @dev Reverts if the signature is invalid or expired. Uses EIP-1271 for smart contract signers.
/// For EOA signers, validates ECDSA signatures directly.
/// For contract signers, calls isValidSignature according to EIP-1271.
/// See https://eips.ethereum.org/EIPS/eip-1271#specification.
/// @param signer The address that should have signed the digest.
/// @param signableDigest The digest that was signed, created via _calculateSignableDigest.
/// @param signature The signature bytes to validate.
/// @param expiry The timestamp after which the signature is no longer valid.
function _checkIsValidSignatureNow(
address signer,
bytes32 signableDigest,
bytes memory signature,
uint256 expiry
) internal view {
// First, check if the signature has expired by comparing the expiry timestamp
// against the current block timestamp.
require(expiry >= block.timestamp, SignatureExpired());
// Next, verify that the signature is valid for the given signer and digest.
// For EOA signers, this performs standard ECDSA signature verification.
// For contract signers, this calls the EIP-1271 isValidSignature method.
require(signer.isValidSignatureNow(signableDigest, signature), InvalidSignature());
}
}
"
},
"lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "./ISemVerMixin.sol";
interface IPermissionControllerErrors {
/// @notice Thrown when a non-admin caller attempts to perform an admin-only action.
error NotAdmin();
/// @notice Thrown when attempting to remove an admin that does not exist.
error AdminNotSet();
/// @notice Thrown when attempting to set an appointee for a function that already has one.
error AppointeeAlreadySet();
/// @notice Thrown when attempting to interact with a non-existent appointee.
error AppointeeNotSet();
/// @notice Thrown when attempting to remove the last remaining admin.
error CannotHaveZeroAdmins();
/// @notice Thrown when attempting to set an admin that is already registered.
error AdminAlreadySet();
/// @notice Thrown when attempting to interact with an admin that is not in pending status.
error AdminNotPending();
/// @notice Thrown when attempting to add an admin that is already pending.
error AdminAlreadyPending();
}
interface IPermissionControllerEvents {
/// @notice Emitted when an appointee is set for an account to handle specific function calls.
event AppointeeSet(address indexed account, address indexed appointee, address target, bytes4 selector);
/// @notice Emitted when an appointee's permission to handle function calls for an account is revoked.
event AppointeeRemoved(address indexed account, address indexed appointee, address target, bytes4 selector);
/// @notice Emitted when an address is set as a pending admin for an account, requiring acceptance.
event PendingAdminAdded(address indexed account, address admin);
/// @notice Emitted when a pending admin status is removed for an account before acceptance.
event PendingAdminRemoved(address indexed account, address admin);
/// @notice Emitted when an address accepts and becomes an active admin for an account.
event AdminSet(address indexed account, address admin);
/// @notice Emitted when an admin's permissions are removed from an account.
event AdminRemoved(address indexed account, address admin);
}
interface IPermissionController is IPermissionControllerErrors, IPermissionControllerEvents, ISemVerMixin {
/**
* @notice Sets a pending admin for an account.
* @param account The account to set the pending admin for.
* @param admin The address to set as pending admin.
* @dev The pending admin must accept the role before becoming an active admin.
* @dev Multiple admins can be set for a single account.
*/
function addPendingAdmin(address account, address admin) external;
/**
* @notice Removes a pending admin from an account before they have accepted the role.
* @param account The account to remove the pending admin from.
* @param admin The pending admin address to remove.
* @dev Only an existing admin of the account can remove a pending admin.
*/
function removePendingAdmin(address account, address admin) external;
/**
* @notice Allows a pending admin to accept their admin role for an account.
* @param account The account to accept the admin role for.
* @dev Only addresses that were previously set as pending admins can accept the role.
*/
function acceptAdmin(
address account
) external;
/**
* @notice Removes an active admin from an account.
* @param account The account to remove the admin from.
* @param admin The admin address to remove.
* @dev Only an existing admin of the account can remove another admin.
* @dev Will revert if removing this admin would leave the account with zero admins.
*/
function removeAdmin(address account, address admin) external;
/**
* @notice Sets an appointee who can call specific functions on behalf of an account.
* @param account The account to set the appointee for.
* @param appointee The address to be given permission.
* @param target The contract address the appointee can interact with.
* @param selector The function selector the appointee can call.
* @dev Only an admin of the account can set appointees.
*/
function setAppointee(address account, address appointee, address target, bytes4 selector) external;
/**
* @notice Removes an appointee's permission to call a specific function.
* @param account The account to remove the appointee from.
* @param appointee The appointee address to remove.
* @param target The contract address to remove permissions for.
* @param selector The function selector to remove permissions for.
* @dev Only an admin of the account can remove appointees.
*/
function removeAppointee(address account, address appointee, address target, bytes4 selector) external;
/**
* @notice Checks if a given address is an admin of an account.
* @param account The account to check admin status for.
* @param caller The address to check.
* @dev If the account has no admins, returns true only if the caller is the account itself.
* @return Returns true if the caller is an admin, false otherwise.
*/
function isAdmin(address account, address caller) external view returns (bool);
/**
* @notice Checks if an address is currently a pending admin for an account.
* @param account The account to check pending admin status for.
* @param pendingAdmin The address to check.
* @return Returns true if the address is a pending admin, false otherwise.
*/
function isPendingAdmin(address account, address pendingAdmin) external view returns (bool);
/**
* @notice Retrieves all active admins for an account.
* @param account The account to get the admins for.
* @dev If the account has no admins, returns an array containing only the account address.
* @return An array of admin addresses.
*/
function getAdmins(
address account
) external view returns (address[] memory);
/**
* @notice Retrieves all pending admins for an account.
* @param account The account to get the pending admins for.
* @return An array of pending admin addresses.
*/
function getPendingAdmins(
address account
) external view returns (address[] memory);
/**
* @notice Checks if a caller has permission to call a specific function.
* @param account The account to check permissions for.
* @param caller The address attempting to make the call.
* @param target The contract address being called.
* @param selector The function selector being called.
* @dev Returns true if the caller is either an admin or an appointed caller.
* @dev Be mindful that upgrades to the contract may invalidate the appointee's permissions.
* This is only possible if a function's selector changes (e.g. if a function's parameters are modified).
* @return Returns true if the caller has permission, false otherwise.
*/
function canCall(address account, address caller, address target, bytes4 selector) external returns (bool);
/**
* @notice Retrieves all permissions granted to an appointee for a given account.
* @param account The account to check appointee permissions for.
* @param appointee The appointee address to check.
* @return Two arrays: target contract addresses and their corresponding function selectors.
*/
function getAppointeePermissions(
address account,
address appointee
) external returns (address[] memory, bytes4[] memory);
/**
* @notice Retrieves all appointees that can call a specific function for an account.
* @param account The account to get appointees for.
* @param target The contract address to check.
* @param selector The function selector to check.
* @dev Does not include admins in the returned list, even though they have calling permission.
* @return An array of appointee addresses.
*/
function getAppointees(address account, address target, bytes4 selector) external returns (address[] memory);
}
"
},
"lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../interfaces/IPermissionController.sol";
abstract contract PermissionControllerMixin {
/// @dev Thrown when the caller is not allowed to call a function on behalf of an account.
error InvalidPermissions();
/// @notice Pointer to the permission controller contract.
IPermissionController public immutable permissionController;
constructor(
IPermissionController _permissionController
) {
permissionController = _permissionController;
}
/// @notice Checks if the caller (msg.sender) can call on behalf of an account.
modifier checkCanCall(
address account
) {
require(_checkCanCall(account), InvalidPermissions());
_;
}
/**
* @notice Checks if the caller is allowed to call a function on behalf of an account.
* @param account the account to check
* @dev `msg.sender` is the caller to check that can call the function on behalf of `account`.
* @dev Returns a bool, instead of reverting
*/
function _checkCanCall(
address account
) internal returns (bool) {
return permissionController.canCall(account, msg.sender, address(this), msg.sig);
}
}
"
},
"lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/interfaces/IReleaseManager.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "../libraries/OperatorSetLib.sol";
interface IReleaseManagerErrors {
/// @notice Thrown when a metadata URI must be published before publishing a release.
error MustPublishMetadataURI();
/// @notice Thrown when the upgrade by time is in the past.
error InvalidUpgradeByTime();
/// @notice Thrown when the metadata URI is empty.
error InvalidMetadataURI();
/// @notice Thrown when there are no releases for an operator set.
error NoReleases();
}
interface IReleaseManagerTypes {
/// @notice Represents a software artifact with its digest and registry URL.
/// @param digest The hash digest of the artifact.
/// @param registry Where the artifact can be found.
struct Artifact {
bytes32 digest;
string registry;
}
/// @notice Represents a release containing multiple artifacts and an upgrade deadline.
/// @param artifacts Array of artifacts included in this release.
/// @param upgradeByTime Timestamp by which operators must upgrade to this release. A value of 0 signals an instant upgrade requirement.
struct Release {
Artifact[] artifacts;
uint32 upgradeByTime;
}
}
interface IReleaseManagerEvents is IReleaseManagerTypes {
/// @notice Emitted when a new release is published.
/// @param operatorSet The operator set this release is for.
/// @param releaseId The id of the release that was published.
/// @param release The release that was published.
event ReleasePublished(OperatorSet indexed operatorSet, uint256 indexed releaseId, Release release);
/// @notice Emitted when a metadata URI is published.
/// @param operatorSet The operator set this metadata URI is for.
/// @param metadataURI The metadata URI that was published.
event MetadataURIPublished(OperatorSet indexed operatorSet, string metadataURI);
}
interface IReleaseManager is IReleaseManagerErrors, IReleaseManagerEvents {
/**
*
* WRITE FUNCTIONS
*
*/
/// @notice Publishes a new release for an operator set.
/// @dev If the upgradeByTime is 0, the release is meant to signal an instant upgrade.
/// @param operatorSet The operator set this release is for.
/// @param release The release that was published.
/// @return releaseId The index of the newly published release.
function publishRelease(
OperatorSet calldata operatorSet,
Release calldata release
) external returns (uint256 releaseId);
/// @notice Publishes a metadata URI for an operator set.
/// @param operatorSet The operator set this metadata URI is for.
/// @param metadataURI The metadata URI that was published.
function publishMetadataURI(OperatorSet calldata operatorSet, string calldata metadataURI) external;
/**
*
* VIEW FUNCTIONS
*
*/
/// @notice Returns the total number of releases for an operator set.
/// @param operatorSet The operator set to query.
/// @return The number of releases.
function getTotalReleases(
OperatorSet memory operatorSet
) external view returns (uint256);
/// @notice Returns a specific release by index.
/// @dev If the upgradeByTime is 0, the release is meant to signal an instant upgrade.
/// @param operatorSet The operator set to query.
/// @param releaseId The id of the release to get.
/// @return The release at the specified index.
function getRelease(OperatorSet memory operatorSet, uint256 releaseId) external view returns (Release memory);
/// @notice Returns the latest release for an operator set.
/// @dev If the upgradeByTime is 0, the release is meant to signal an instant upgrade.
/// @param operatorSet The operator set to query.
/// @return The id of the latest release.
/// @return The lates
Submitted on: 2025-10-29 21:31:58
Comments
Log in to comment.
No comments yet.