@@ -7,6 +7,8 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
7
7
import "../interfaces/IInitializer.sol " ;
8
8
import "../abstracts/CodeIndexer.sol " ;
9
9
import "@openzeppelin/contracts/utils/introspection/ERC165.sol " ;
10
+ import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol " ;
11
+ import {IContractURI} from "../interfaces/IContractURI.sol " ;
10
12
/**
11
13
* @title Distributor
12
14
* @notice Abstract contract that implements the IDistributor interface, CodeIndexer, and ERC165.
@@ -15,16 +17,24 @@ import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
15
17
* @author Peeramid Labs, 2024
16
18
*/
17
19
abstract contract Distributor is IDistributor , CodeIndexer , ERC165 {
20
+ using LibSemver for LibSemver.Version;
18
21
struct DistributionComponent {
19
- bytes32 id ;
22
+ address distributionLocation ;
20
23
address initializer;
21
24
}
25
+
26
+ struct VersionedDistribution {
27
+ LibSemver.VersionRequirement requirement;
28
+ }
29
+
22
30
using EnumerableSet for EnumerableSet.Bytes32Set;
23
31
EnumerableSet.Bytes32Set private distirbutionsSet;
24
- // mapping(bytes32 => IInitializer) private initializers;
25
- mapping (address => uint256 ) private instanceIds;
26
- mapping (uint256 => bytes32 ) public distributionOf;
27
- mapping (bytes32 => DistributionComponent) public distributionComponents;
32
+ mapping (address instance = > uint256 instanceId ) private instanceIds;
33
+ mapping (uint256 instance = > bytes32 distributorsId ) public distributionOf;
34
+ mapping (bytes32 distributorsId = > DistributionComponent distirbution ) public distributionComponents;
35
+ mapping (bytes32 distributorsId = > LibSemver.VersionRequirement VersionRequirement ) public versionRequirements;
36
+ mapping (uint256 instanceId = > LibSemver.Version instanceVersion ) public instanceVersions;
37
+
28
38
uint256 public numInstances;
29
39
// @inheritdoc IDistributor
30
40
function getDistributions () external view returns (bytes32 [] memory ) {
@@ -41,43 +51,94 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
41
51
// @inheritdoc IDistributor
42
52
function getDistributionURI (bytes32 distributorsId ) external view returns (string memory ) {
43
53
DistributionComponent memory distributionComponent = distributionComponents[distributorsId];
44
- ICodeIndex codeIndex = getContractsIndex ();
45
- return IDistribution (codeIndex.get (distributionComponent.id)).getMetadata ();
54
+ return IContractURI (distributionComponent.distributionLocation).contractURI ();
46
55
}
47
56
48
- function _addDistribution (bytes32 id , address initializerAddress ) internal virtual {
57
+ function _addDistribution (
58
+ address repository ,
59
+ address initializer ,
60
+ LibSemver.VersionRequirement memory requirement
61
+ ) internal {
62
+ if (! ERC165Checker .supportsInterface (address (repository), type (IRepository).interfaceId)) {
63
+ revert InvalidRepository (repository);
64
+ }
65
+ bytes32 distributorId = keccak256 (abi.encode (repository, initializer));
66
+ if (LibSemver.toUint256 (requirement.version) == 0 ) {
67
+ revert InvalidVersionRequested (distributorId, LibSemver.toString (requirement.version));
68
+ }
69
+ _newDistriubutionRecord (distributorId, repository, initializer);
70
+ versionRequirements[distributorId] = requirement;
71
+ emit VersionChanged (distributorId, requirement, requirement);
72
+ }
73
+
74
+ function _newDistriubutionRecord (bytes32 distributorId , address source , address initializer ) private {
75
+ if (distirbutionsSet.contains (distributorId)) revert DistributionExists (distributorId);
76
+ distirbutionsSet.add (distributorId);
77
+ distributionComponents[distributorId] = DistributionComponent (source, initializer);
78
+ emit DistributionAdded (distributorId, source, initializer);
79
+ }
80
+ function _addDistribution (bytes32 id , address initializerAddress ) internal {
49
81
ICodeIndex codeIndex = getContractsIndex ();
50
- if (codeIndex.get (id) == address (0 )) revert DistributionNotFound (id);
82
+ address distributionLocation = codeIndex.get (id);
83
+ if (distributionLocation == address (0 )) revert DistributionNotFound (id);
51
84
bytes32 distributorsId = keccak256 (abi.encode (id, initializerAddress));
52
- if (distirbutionsSet.contains (distributorsId)) revert DistributionExists (distributorsId);
53
- distirbutionsSet.add (distributorsId);
54
- distributionComponents[distributorsId] = DistributionComponent (id, initializerAddress);
55
- emit DistributionAdded (id, initializerAddress);
85
+ _newDistriubutionRecord (distributorsId, distributionLocation, initializerAddress);
56
86
}
57
87
58
88
function _removeDistribution (bytes32 distributorsId ) internal virtual {
59
89
if (! distirbutionsSet.contains (distributorsId)) revert DistributionNotFound (distributorsId);
60
90
distirbutionsSet.remove (distributorsId);
61
91
delete distributionComponents[distributorsId];
92
+ delete versionRequirements[distributorsId];
62
93
emit DistributionRemoved (distributorsId);
63
94
}
64
95
65
96
/**
66
97
* @notice Internal function to instantiate a new instance.
67
- * @dev WARNING: This function will DELEGATECALL to initializer. Initializer MUST be trusted contract .
98
+ * @dev WARNING: This function will DELEGATECALL to initializer if such is provided . Initializer contract MUST be trusted by distributor .
68
99
*/
69
100
function _instantiate (
70
101
bytes32 distributorsId ,
71
102
bytes memory args
72
103
) internal virtual returns (address [] memory instances , bytes32 distributionName , uint256 distributionVersion ) {
73
- ICodeIndex codeIndex = getContractsIndex ();
74
104
if (! distirbutionsSet.contains (distributorsId)) revert DistributionNotFound (distributorsId);
75
105
DistributionComponent memory distributionComponent = distributionComponents[distributorsId];
106
+ LibSemver.VersionRequirement memory versionRequirement = versionRequirements[distributorsId];
107
+
108
+ // External initializer is provided, delegatecall to it
109
+ // Countrary, if no initializer is provided, the distribution is expected to be self-initializing
110
+ bool externallyInitialized = distributionComponent.initializer == address (0 );
76
111
bytes4 selector = IInitializer.initialize.selector ;
77
- // bytes memory instantiationArgs = initializer != address(0) ? args : bytes ("");
78
- (instances, distributionName, distributionVersion) = IDistribution (codeIndex.get (distributionComponent.id))
79
- .instantiate (args);
80
- if (distributionComponent.initializer != address (0 )) {
112
+ bytes memory instantiationArgs = externallyInitialized ? args : bytes ("" );
113
+ address distributionLocation;
114
+ numInstances++ ;
115
+ uint256 instanceId = numInstances;
116
+
117
+ if (LibSemver.toUint256 (versionRequirement.version) == 0 ) {
118
+ // Unversioned distribution, expect IDistribution
119
+ distributionLocation = distributionComponent.distributionLocation;
120
+ // Name and version are inferred from what the distribution provides
121
+ (instances, distributionName, distributionVersion) = IDistribution (distributionLocation).instantiate (
122
+ instantiationArgs
123
+ );
124
+ // Unversioned distributions are considered to be at version 0, and are not expected to change
125
+ // This might change in the future, as it could make sence to inherit `distributionVersion` from the distribution
126
+ // Yet for ease of runtime validation and to avoid potential issues, we keep it at 0
127
+ instanceVersions[numInstances] = LibSemver.parse (0 );
128
+ } else {
129
+ // Versioned distribution, expect IRepository
130
+ IRepository repository = IRepository (distributionComponent.distributionLocation);
131
+ IRepository.Source memory repoSource = repository.get (versionRequirement);
132
+ ICodeIndex codeIndex = getContractsIndex ();
133
+ distributionLocation = codeIndex.get (repoSource.sourceId);
134
+ if (distributionLocation == address (0 )) revert DistributionNotFound (repoSource.sourceId);
135
+ (instances, , ) = IDistribution (distributionLocation).instantiate (instantiationArgs);
136
+ distributionName = repository.repositoryName ();
137
+ distributionVersion = LibSemver.toUint256 (repoSource.version);
138
+ instanceVersions[numInstances] = repoSource.version;
139
+ }
140
+
141
+ if (externallyInitialized) {
81
142
(bool success , bytes memory result ) = address (distributionComponent.initializer).delegatecall (
82
143
abi.encodeWithSelector (selector, instances, args)
83
144
);
@@ -92,15 +153,15 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
92
153
}
93
154
}
94
155
}
95
- numInstances++ ;
96
- uint256 instanceId = numInstances;
97
- uint256 instancesLength = instances.length ;
98
- for (uint256 i; i < instancesLength; ++ i) {
99
- instanceIds[instances[i]] = instanceId;
100
- distributionOf[instanceId] = distributorsId;
156
+
157
+ {
158
+ uint256 instancesLength = instances.length ;
159
+ for (uint256 i; i < instancesLength; ++ i) {
160
+ instanceIds[instances[i]] = instanceId;
161
+ distributionOf[instanceId] = distributorsId;
162
+ }
101
163
}
102
- emit Instantiated (distributorsId, instanceId, args, instances);
103
- return (instances, distributionName, distributionVersion);
164
+ emit Instantiated (distributorsId, instanceId, distributionVersion, instances, args);
104
165
}
105
166
106
167
/**
@@ -117,13 +178,17 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
117
178
) external view virtual returns (bytes memory ) {
118
179
address target = config.length > 0 ? abi.decode (config, (address )) : msg .sender ;
119
180
bytes32 distributorsId = distributionOf[getInstanceId (maybeInstance)];
181
+ uint256 instanceId = getInstanceId (maybeInstance);
120
182
if (
121
183
distributorsId != bytes32 (0 ) &&
122
- getInstanceId (target) == getInstanceId (maybeInstance) &&
184
+ getInstanceId (target) == instanceId &&
123
185
distirbutionsSet.contains (distributorsId)
124
186
) {
125
187
// ToDo: This check could be based on DistributionOf, hence allowing cross-instance calls
126
188
// Use layerConfig to allow client to configure requirement for the call
189
+ if (! LibSemver.compare (instanceVersions[instanceId], versionRequirements[distributorsId])) {
190
+ revert VersionOutdated (distributorsId, LibSemver.toString (instanceVersions[instanceId]));
191
+ }
127
192
return abi.encode (distributorsId, "" );
128
193
}
129
194
revert InvalidInstance (maybeInstance);
@@ -143,12 +208,31 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
143
208
) external virtual {
144
209
address target = config.length > 0 ? abi.decode (config, (address )) : msg .sender ;
145
210
bytes32 distributorsId = distributionOf[getInstanceId (maybeInstance)];
211
+ uint256 instanceId = getInstanceId (maybeInstance);
146
212
if ((getInstanceId (target) != getInstanceId (maybeInstance)) && distirbutionsSet.contains (distributorsId)) {
147
213
revert InvalidInstance (maybeInstance);
148
214
}
215
+ if (! LibSemver.compare (instanceVersions[instanceId], versionRequirements[distributorsId])) {
216
+ revert VersionOutdated (distributorsId, LibSemver.toString (instanceVersions[instanceId]));
217
+ }
149
218
}
150
219
151
220
function supportsInterface (bytes4 interfaceId ) public view virtual override (ERC165 , IERC165 ) returns (bool ) {
152
221
return interfaceId == type (IDistributor).interfaceId || super .supportsInterface (interfaceId);
153
222
}
223
+
224
+ function _changeVersion (bytes32 distributionId , LibSemver.VersionRequirement memory newRequirement ) internal {
225
+ if (! distirbutionsSet.contains (distributionId)) revert DistributionNotFound (distributionId);
226
+ LibSemver.VersionRequirement memory oldRequirement = versionRequirements[distributionId];
227
+ if (LibSemver.toUint256 (oldRequirement.version) == 0 ) {
228
+ revert UniversionedDistribution (distributionId);
229
+ }
230
+ if (LibSemver.toUint256 (newRequirement.version) == 0 ) {
231
+ revert InvalidVersionRequested (distributionId, LibSemver.toString (newRequirement.version));
232
+ }
233
+ if (LibSemver.areEqual (oldRequirement.version, newRequirement.version)) {
234
+ revert InvalidVersionRequested (distributionId, LibSemver.toString (newRequirement.version));
235
+ }
236
+ versionRequirements[distributionId] = newRequirement;
237
+ }
154
238
}
0 commit comments