Skip to content

Commit 6527e01

Browse files
authored
merged versioned distributor with simple distributor interfaces (#9)
* merged versioned distributor with simple distributor interfaces, replaced getmetadata with contract uri * happy linter - happy life * change abstract instantiate fn to internal visibility * made instantiate internal in abstract contracts
1 parent fb5ee35 commit 6527e01

22 files changed

+496
-439
lines changed

.changeset/violet-suits-roll.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@peeramid-labs/eds": major
3+
---
4+
5+
# Breaking changes
6+
7+
## Deprecated getMetadata in favor of contractURI
8+
9+
contractURI is more widely used and we want to align with the standard.
10+
11+
12+
## Merged VersionedDistributor into Distributor
13+
14+
Now there is only a single `IDistributor` interface. Distribution creators still have two methods of adding distributions - either by specifying repository address, or by specifying a distribution hash id directly.
15+
16+
The `VersionedDistributor` is now deprecated and will be removed in the next major version.
17+
18+
Instantiation function will now automatically detect if the provided address is a repository or a distribution id and call the appropriate method.
19+
20+
ERC7746 checks will now also automatically detect if the provided address is a repository or a distribution and will enforce version control accordingly.
21+
22+
### Instantiated event
23+
24+
Now emits also a version indexed parameter, arshHash was removed, args are availible as not indexed object in data
25+
26+
## addDistribution for versioned repositories
27+
28+
`addDistribution` now takes `LibSemver.VersionRequirement` as an argument, which is more convenient way to pack both version and requirement in a single argument.
29+
30+
## Repository now requires cURI
31+
32+
The `cURI` is now required for all repositories. This is to ensure that all repositories are compliant with the standard contractURI method
33+
34+
## Reposotory `get` function
35+
36+
Repostory `get` function now takes `LibSemver.VersionRequirement` as an argument, which is more convenient way to pack both version and requirement in a single argument.
37+
38+
## LibSemver.compare
39+
40+
`compare` now takes `LibSemver.VersionRequirement` as an argument, which is more convenient way to pack both version and requirement in a single argument.
41+
42+
## LibSemver compare(version version) -> areEqual
43+
44+
`compare(Version memory _version1, Version memory _version2)` now returns a boolean instead of an integer.

src/abstracts/CloneDistribution.sol

+6-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ abstract contract CloneDistribution is IDistribution, CodeIndexer {
1111
function sources() internal view virtual returns (address[] memory, bytes32 name, uint256 version);
1212

1313
// @inheritdoc IDistribution
14-
function instantiate(
15-
bytes memory
16-
) external virtual returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion) {
14+
function _instantiate()
15+
internal
16+
virtual
17+
returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion)
18+
{
1719
(address[] memory _sources, bytes32 _distributionName, uint256 _distributionVersion) = sources();
1820
uint256 srcsLength = _sources.length;
1921
instances = new address[](srcsLength);
@@ -29,5 +31,5 @@ abstract contract CloneDistribution is IDistribution, CodeIndexer {
2931
return sources();
3032
}
3133
// @inheritdoc IDistribution
32-
function getMetadata() external view virtual returns (string memory);
34+
function contractURI() external view virtual returns (string memory);
3335
}

src/abstracts/Distributor.sol

+112-28
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
77
import "../interfaces/IInitializer.sol";
88
import "../abstracts/CodeIndexer.sol";
99
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
10+
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
11+
import {IContractURI} from "../interfaces/IContractURI.sol";
1012
/**
1113
* @title Distributor
1214
* @notice Abstract contract that implements the IDistributor interface, CodeIndexer, and ERC165.
@@ -15,16 +17,24 @@ import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
1517
* @author Peeramid Labs, 2024
1618
*/
1719
abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
20+
using LibSemver for LibSemver.Version;
1821
struct DistributionComponent {
19-
bytes32 id;
22+
address distributionLocation;
2023
address initializer;
2124
}
25+
26+
struct VersionedDistribution {
27+
LibSemver.VersionRequirement requirement;
28+
}
29+
2230
using EnumerableSet for EnumerableSet.Bytes32Set;
2331
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+
2838
uint256 public numInstances;
2939
// @inheritdoc IDistributor
3040
function getDistributions() external view returns (bytes32[] memory) {
@@ -41,43 +51,94 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
4151
// @inheritdoc IDistributor
4252
function getDistributionURI(bytes32 distributorsId) external view returns (string memory) {
4353
DistributionComponent memory distributionComponent = distributionComponents[distributorsId];
44-
ICodeIndex codeIndex = getContractsIndex();
45-
return IDistribution(codeIndex.get(distributionComponent.id)).getMetadata();
54+
return IContractURI(distributionComponent.distributionLocation).contractURI();
4655
}
4756

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 {
4981
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);
5184
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);
5686
}
5787

5888
function _removeDistribution(bytes32 distributorsId) internal virtual {
5989
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
6090
distirbutionsSet.remove(distributorsId);
6191
delete distributionComponents[distributorsId];
92+
delete versionRequirements[distributorsId];
6293
emit DistributionRemoved(distributorsId);
6394
}
6495

6596
/**
6697
* @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.
6899
*/
69100
function _instantiate(
70101
bytes32 distributorsId,
71102
bytes memory args
72103
) internal virtual returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion) {
73-
ICodeIndex codeIndex = getContractsIndex();
74104
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
75105
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);
76111
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) {
81142
(bool success, bytes memory result) = address(distributionComponent.initializer).delegatecall(
82143
abi.encodeWithSelector(selector, instances, args)
83144
);
@@ -92,15 +153,15 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
92153
}
93154
}
94155
}
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+
}
101163
}
102-
emit Instantiated(distributorsId, instanceId, args, instances);
103-
return (instances, distributionName, distributionVersion);
164+
emit Instantiated(distributorsId, instanceId, distributionVersion, instances, args);
104165
}
105166

106167
/**
@@ -117,13 +178,17 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
117178
) external view virtual returns (bytes memory) {
118179
address target = config.length > 0 ? abi.decode(config, (address)) : msg.sender;
119180
bytes32 distributorsId = distributionOf[getInstanceId(maybeInstance)];
181+
uint256 instanceId = getInstanceId(maybeInstance);
120182
if (
121183
distributorsId != bytes32(0) &&
122-
getInstanceId(target) == getInstanceId(maybeInstance) &&
184+
getInstanceId(target) == instanceId &&
123185
distirbutionsSet.contains(distributorsId)
124186
) {
125187
// ToDo: This check could be based on DistributionOf, hence allowing cross-instance calls
126188
// 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+
}
127192
return abi.encode(distributorsId, "");
128193
}
129194
revert InvalidInstance(maybeInstance);
@@ -143,12 +208,31 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
143208
) external virtual {
144209
address target = config.length > 0 ? abi.decode(config, (address)) : msg.sender;
145210
bytes32 distributorsId = distributionOf[getInstanceId(maybeInstance)];
211+
uint256 instanceId = getInstanceId(maybeInstance);
146212
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distirbutionsSet.contains(distributorsId)) {
147213
revert InvalidInstance(maybeInstance);
148214
}
215+
if (!LibSemver.compare(instanceVersions[instanceId], versionRequirements[distributorsId])) {
216+
revert VersionOutdated(distributorsId, LibSemver.toString(instanceVersions[instanceId]));
217+
}
149218
}
150219

151220
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
152221
return interfaceId == type(IDistributor).interfaceId || super.supportsInterface(interfaceId);
153222
}
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+
}
154238
}

0 commit comments

Comments
 (0)