Skip to content

Commit 1359137

Browse files
feat: add protocol registry (#1655)
**Motivation:** We want a centralized way to track protocol contracts, semantic versioning, and improve our pausing procedures. **Modifications:** The main addition is `ProtocolRegistry` which has the following methods: - `ship()` - Batch deployment registration with semantic versioning and configuration flags (only admin). - `configure()` - Configure an existing deployment (only admin). - `pauseAll()` - Emergency pausing across all pausable contracts via single transaction (only pauser). - `version()` - Get the semantic version of the protocol. - `majorVersion()` - Get the major version of the protocol. - `getAddress()` - Get deployment address by contract name. - `getDeployment()` - Get complete deployment info (address, implementation, config) by name. - `getAllDeployments()` - Get all deployments with full information. - `totalDeployments()` - Get total number of registered deployments. **Result:** A registry enabling easy introspection of protocol contracts, improved emergency response capabilities, and centralized semantic versioning (lowering the codesize burden on most of our contracts).
1 parent c572ec5 commit 1359137

File tree

5 files changed

+815
-0
lines changed

5 files changed

+815
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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 "@openzeppelin-upgrades/contracts/access/AccessControlEnumerableUpgradeable.sol";
6+
import "../interfaces/IPausable.sol";
7+
import "./ProtocolRegistryStorage.sol";
8+
9+
contract ProtocolRegistry is Initializable, AccessControlEnumerableUpgradeable, ProtocolRegistryStorage {
10+
using ShortStringsUpgradeable for *;
11+
using EnumerableMap for EnumerableMap.UintToAddressMap;
12+
13+
/**
14+
*
15+
* INITIALIZING FUNCTIONS
16+
*
17+
*/
18+
constructor() ProtocolRegistryStorage() {
19+
_disableInitializers();
20+
}
21+
22+
/// @inheritdoc IProtocolRegistry
23+
function initialize(address initialAdmin, address pauserMultisig) external initializer {
24+
_grantRole(DEFAULT_ADMIN_ROLE, initialAdmin);
25+
_grantRole(PAUSER_ROLE, pauserMultisig);
26+
}
27+
28+
/**
29+
*
30+
* INITIALIZING FUNCTIONS
31+
*
32+
*/
33+
34+
/// @inheritdoc IProtocolRegistry
35+
function ship(
36+
address[] calldata addresses,
37+
DeploymentConfig[] calldata configs,
38+
string[] calldata names,
39+
string calldata semanticVersion
40+
) external onlyRole(DEFAULT_ADMIN_ROLE) {
41+
// Update the semantic version.
42+
_updateSemanticVersion(semanticVersion);
43+
for (uint256 i = 0; i < addresses.length; ++i) {
44+
// Append each provided
45+
_appendDeployment(addresses[i], configs[i], names[i]);
46+
}
47+
}
48+
49+
/// @inheritdoc IProtocolRegistry
50+
function configure(address addr, DeploymentConfig calldata config) external onlyRole(DEFAULT_ADMIN_ROLE) {
51+
// Update the config
52+
_deploymentConfigs[addr] = config;
53+
// Emit the event.
54+
emit DeploymentConfigured(addr, config);
55+
}
56+
57+
/// @inheritdoc IProtocolRegistry
58+
function pauseAll() external onlyRole(PAUSER_ROLE) {
59+
uint256 length = totalDeployments();
60+
// Iterate over all stored deployments.
61+
for (uint256 i = 0; i < length; ++i) {
62+
(, address addr) = _deployments.at(i);
63+
DeploymentConfig memory config = _deploymentConfigs[addr];
64+
// Only attempt to pause deployments marked as pausable.
65+
if (config.pausable && !config.deprecated) {
66+
IPausable(addr).pauseAll();
67+
}
68+
}
69+
}
70+
71+
/**
72+
*
73+
* HELPER FUNCTIONS
74+
*
75+
*/
76+
77+
/// @dev Updates the semantic version of the protocol.
78+
function _updateSemanticVersion(
79+
string calldata semanticVersion
80+
) internal {
81+
string memory previousSemanticVersion = _semanticVersion.toString();
82+
_semanticVersion = semanticVersion.toShortString();
83+
emit SemanticVersionUpdated(previousSemanticVersion, semanticVersion);
84+
}
85+
86+
/// @dev Appends a deployment.
87+
function _appendDeployment(address addr, DeploymentConfig calldata config, string calldata name) internal {
88+
// Store name => address mapping
89+
_deployments.set({key: _unwrap(name.toShortString()), value: addr});
90+
// Store deployment config
91+
_deploymentConfigs[addr] = config;
92+
// Emit the events.
93+
emit DeploymentShipped(addr, config);
94+
}
95+
96+
/// @dev Unwraps a ShortString to a uint256.
97+
function _unwrap(
98+
ShortString shortString
99+
) internal pure returns (uint256) {
100+
return uint256(ShortString.unwrap(shortString));
101+
}
102+
103+
/**
104+
*
105+
* VIEW FUNCTIONS
106+
*
107+
*/
108+
109+
/// @inheritdoc IProtocolRegistry
110+
function version() external view virtual returns (string memory) {
111+
return _semanticVersion.toString();
112+
}
113+
114+
/// @inheritdoc IProtocolRegistry
115+
function majorVersion() external view returns (string memory) {
116+
bytes memory v = bytes(_semanticVersion.toString());
117+
return string(abi.encodePacked(v[0]));
118+
}
119+
120+
/// @inheritdoc IProtocolRegistry
121+
function getAddress(
122+
string calldata name
123+
) external view returns (address) {
124+
return _deployments.get(_unwrap(name.toShortString()));
125+
}
126+
127+
/// @inheritdoc IProtocolRegistry
128+
function getDeployment(
129+
string calldata name
130+
) external view returns (address addr, DeploymentConfig memory config) {
131+
addr = _deployments.get(_unwrap(name.toShortString()));
132+
config = _deploymentConfigs[addr];
133+
return (addr, config);
134+
}
135+
136+
/// @inheritdoc IProtocolRegistry
137+
function getAllDeployments()
138+
external
139+
view
140+
returns (string[] memory names, address[] memory addresses, DeploymentConfig[] memory configs)
141+
{
142+
uint256 length = totalDeployments();
143+
names = new string[](length);
144+
addresses = new address[](length);
145+
configs = new DeploymentConfig[](length);
146+
147+
for (uint256 i = 0; i < length; ++i) {
148+
(uint256 nameShortString, address addr) = _deployments.at(i);
149+
names[i] = ShortString.wrap(bytes32(nameShortString)).toString();
150+
addresses[i] = addr;
151+
configs[i] = _deploymentConfigs[addr];
152+
}
153+
154+
return (names, addresses, configs);
155+
}
156+
157+
/// @inheritdoc IProtocolRegistry
158+
function totalDeployments() public view returns (uint256) {
159+
return _deployments.length();
160+
}
161+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol";
5+
import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
6+
import "../interfaces/IProtocolRegistry.sol";
7+
8+
abstract contract ProtocolRegistryStorage is IProtocolRegistry {
9+
/**
10+
*
11+
* CONSTANTS
12+
*
13+
*/
14+
15+
/// @inheritdoc IProtocolRegistry
16+
bytes32 public constant PAUSER_ROLE = hex"01";
17+
18+
/**
19+
*
20+
* MUTABLE STATE
21+
*
22+
*/
23+
24+
/// @notice Returns the semantic version of the protocol.
25+
ShortString internal _semanticVersion;
26+
27+
/// @notice Maps deployment name hashes to addresses (enumerable for iteration).
28+
EnumerableMap.UintToAddressMap internal _deployments;
29+
30+
/// @notice Maps deployment addresses to their configurations.
31+
mapping(address => DeploymentConfig) internal _deploymentConfigs;
32+
33+
/**
34+
* @dev This empty reserved space is put in place to allow future versions to add new
35+
* variables without shifting down storage in the inheritance chain.
36+
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
37+
*/
38+
uint256[47] private __gap;
39+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
interface IProtocolRegistryErrors {}
5+
6+
interface IProtocolRegistryTypes {
7+
/**
8+
* @notice Configuration for a protocol deployment.
9+
* @param pausable Whether this deployment can be paused.
10+
* @param deprecated Whether this deployment is deprecated.
11+
*/
12+
struct DeploymentConfig {
13+
bool pausable;
14+
bool deprecated;
15+
}
16+
}
17+
18+
interface IProtocolRegistryEvents is IProtocolRegistryTypes {
19+
/**
20+
* @notice Emitted when a deployment is shipped.
21+
* @param addr The address of the deployment.
22+
* @param config The configuration for the deployment.
23+
*/
24+
event DeploymentShipped(address indexed addr, DeploymentConfig config);
25+
26+
/**
27+
* @notice Emitted when a deployment is configured.
28+
* @param addr The address of the deployment.
29+
* @param config The configuration for the deployment.
30+
*/
31+
event DeploymentConfigured(address indexed addr, DeploymentConfig config);
32+
33+
/**
34+
* @notice Emitted when the semantic version is updated.
35+
* @param previousSemanticVersion The previous semantic version.
36+
* @param semanticVersion The new semantic version.
37+
*/
38+
event SemanticVersionUpdated(string previousSemanticVersion, string semanticVersion);
39+
}
40+
41+
interface IProtocolRegistry is IProtocolRegistryErrors, IProtocolRegistryEvents {
42+
/**
43+
* @notice Initializes the ProtocolRegistry with the initial admin.
44+
* @param initialAdmin The address to set as the initial admin.
45+
* @param pauserMultisig The address to set as the pauser multisig.
46+
*/
47+
function initialize(address initialAdmin, address pauserMultisig) external;
48+
49+
/**
50+
* @notice Ships a list of deployments.
51+
* @dev Only callable by the admin.
52+
* @param addresses The addresses of the deployments to ship.
53+
* @param configs The configurations of the deployments to ship.
54+
* @param contractNames The names of the contracts to ship.
55+
* @param semanticVersion The semantic version to ship.
56+
* @dev Contract names must be in ALL CAPS and contain NO SPACES (e.g., "ALLOCATIONMANAGER").
57+
*/
58+
function ship(
59+
address[] calldata addresses,
60+
DeploymentConfig[] calldata configs,
61+
string[] calldata contractNames,
62+
string calldata semanticVersion
63+
) external;
64+
65+
/**
66+
* @notice Configures a deployment.
67+
* @dev Only callable by the admin.
68+
* @param addr The address of the deployment to configure.
69+
* @param config The configuration to set.
70+
*/
71+
function configure(address addr, DeploymentConfig calldata config) external;
72+
73+
/**
74+
* @notice Pauses all deployments that support pausing.
75+
* @dev Loops over all deployments and attempts to invoke `pauseAll()` on each contract that is marked as pausable.
76+
* Silently ignores errors during calls for rapid pausing in emergencies. Pauser role only.
77+
*/
78+
function pauseAll() external;
79+
80+
/**
81+
* @notice Returns the full semantic version string of the protocol (e.g. "1.2.3").
82+
* @dev Follows Semantic Versioning 2.0.0 (see https://semver.org/).
83+
* @return The SemVer-formatted version string of the protocol.
84+
*/
85+
function version() external view returns (string memory);
86+
87+
/**
88+
* @notice Returns the major version component of the protocol's semantic version.
89+
* @dev Extracts and returns only the major version number as a string (e.g. "1" for version "1.2.3").
90+
* @return The major version number as a string.
91+
*/
92+
function majorVersion() external view returns (string memory);
93+
94+
/**
95+
* @notice Returns a deployment by name.
96+
* @param name The name of the deployment to get.
97+
* @return address The address of the deployment.
98+
*/
99+
function getAddress(
100+
string calldata name
101+
) external view returns (address);
102+
103+
/**
104+
* @notice Returns a deployment by name.
105+
* @param name The name of the deployment to get.
106+
* @return addr The address.
107+
* @return config The configuration.
108+
*/
109+
function getDeployment(
110+
string calldata name
111+
) external view returns (address addr, DeploymentConfig memory config);
112+
113+
/**
114+
* @notice Returns all deployments.
115+
* @return names The names of the deployments.
116+
* @return addresses The addresses.
117+
* @return configs The configurations.
118+
*/
119+
function getAllDeployments()
120+
external
121+
view
122+
returns (string[] memory names, address[] memory addresses, DeploymentConfig[] memory configs);
123+
124+
/**
125+
* @notice Returns the total number of deployments.
126+
* @return The total number of deployments.
127+
*/
128+
function totalDeployments() external view returns (uint256);
129+
130+
/**
131+
* @notice Returns the pauser role for the protocol.
132+
* @return The pauser role for the protocol.
133+
*/
134+
function PAUSER_ROLE() external view returns (bytes32);
135+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)
3+
pragma solidity ^0.8.0;
4+
5+
/**
6+
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
7+
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
8+
*/
9+
interface IProxyAdmin {
10+
/**
11+
* @dev Returns the current implementation of `proxy`.
12+
*
13+
* Requirements:
14+
*
15+
* - This contract must be the admin of `proxy`.
16+
*/
17+
function getProxyImplementation(
18+
address proxy
19+
) external view returns (address);
20+
21+
/**
22+
* @dev Returns the current admin of `proxy`.
23+
*
24+
* Requirements:
25+
*
26+
* - This contract must be the admin of `proxy`.
27+
*/
28+
function getProxyAdmin(
29+
address proxy
30+
) external view returns (address);
31+
32+
/**
33+
* @dev Changes the admin of `proxy` to `newAdmin`.
34+
*
35+
* Requirements:
36+
*
37+
* - This contract must be the current admin of `proxy`.
38+
*/
39+
function changeProxyAdmin(address proxy, address newAdmin) external;
40+
41+
/**
42+
* @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
43+
*
44+
* Requirements:
45+
*
46+
* - This contract must be the admin of `proxy`.
47+
*/
48+
function upgrade(address proxy, address implementation) external;
49+
50+
/**
51+
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
52+
* {TransparentUpgradeableProxy-upgradeToAndCall}.
53+
*
54+
* Requirements:
55+
*
56+
* - This contract must be the admin of `proxy`.
57+
*/
58+
function upgradeAndCall(address proxy, address implementation, bytes memory data) external payable;
59+
}

0 commit comments

Comments
 (0)