Skip to content

Commit 2df4053

Browse files
committed
feat: add PackageRegistry contract #4
1 parent a836611 commit 2df4053

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

src/core/PackageRegistry.sol

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "../interfaces/IPackageRegistry.sol";
5+
import "../structs/PackageStructs.sol";
6+
7+
/// @title PackageRegistry
8+
/// @notice On-chain registry for EthPM v3 packages (EIP-2678)
9+
/// @dev Stores package references with content-addressable URIs (IPFS)
10+
contract PackageRegistry is IPackageRegistry {
11+
12+
// Package name => version => manifest URI (IPFS/Swarm)
13+
mapping(string => mapping(string => string)) private packages;
14+
15+
// Package name => owner address
16+
mapping(string => address) private packageOwners;
17+
18+
// Package name => all versions
19+
mapping(string => string[]) private packageVersions;
20+
21+
// Events
22+
event PackagePublished(string indexed name, string version, string manifestURI, address publisher);
23+
event PackageOwnershipTransferred(string indexed name, address previousOwner, address newOwner);
24+
25+
/// @notice Publish a package to the registry
26+
/// @param name Package name (must be lowercase, numbers, hyphens only)
27+
/// @param version Package version (semver format)
28+
/// @param manifestURI Content-addressable URI (ipfs:// or bzz://)
29+
function publish(string calldata name, string calldata version, string calldata manifestURI) external override {
30+
require(bytes(name).length > 0, "Package name cannot be empty");
31+
require(bytes(version).length > 0, "Version cannot be empty");
32+
require(bytes(manifestURI).length > 0, "Manifest URI cannot be empty");
33+
require(_isValidPackageName(name), "Invalid package name format");
34+
35+
// Check ownership
36+
if (packageOwners[name] == address(0)) {
37+
// First time publishing this package
38+
packageOwners[name] = msg.sender;
39+
} else {
40+
// Only owner can publish new versions
41+
require(packageOwners[name] == msg.sender, "Only package owner can publish");
42+
}
43+
44+
// Check if version already exists
45+
require(bytes(packages[name][version]).length == 0, "Version already exists");
46+
47+
// Store package
48+
packages[name][version] = manifestURI;
49+
packageVersions[name].push(version);
50+
51+
emit PackagePublished(name, version, manifestURI, msg.sender);
52+
}
53+
54+
/// @notice Get package manifest URI
55+
/// @param name Package name
56+
/// @param version Package version
57+
/// @return manifestURI The content-addressable URI of the package manifest
58+
function getPackageURI(string calldata name, string calldata version) external view override returns (string memory) {
59+
string memory uri = packages[name][version];
60+
require(bytes(uri).length > 0, "Package version does not exist");
61+
return uri;
62+
}
63+
64+
/// @notice Check if package version exists
65+
/// @param name Package name
66+
/// @param version Package version
67+
/// @return exists True if package exists
68+
function packageExists(string calldata name, string calldata version) external view override returns (bool) {
69+
return bytes(packages[name][version]).length > 0;
70+
}
71+
72+
/// @notice Get all versions of a package
73+
/// @param name Package name
74+
/// @return versions Array of all versions
75+
function getVersions(string calldata name) external view returns (string[] memory) {
76+
return packageVersions[name];
77+
}
78+
79+
/// @notice Get package owner
80+
/// @param name Package name
81+
/// @return owner Address of package owner
82+
function getOwner(string calldata name) external view returns (address) {
83+
return packageOwners[name];
84+
}
85+
86+
/// @notice Transfer package ownership
87+
/// @param name Package name
88+
/// @param newOwner New owner address
89+
function transferOwnership(string calldata name, address newOwner) external {
90+
require(packageOwners[name] == msg.sender, "Only owner can transfer ownership");
91+
require(newOwner != address(0), "New owner cannot be zero address");
92+
93+
address previousOwner = packageOwners[name];
94+
packageOwners[name] = newOwner;
95+
96+
emit PackageOwnershipTransferred(name, previousOwner, newOwner);
97+
}
98+
99+
/// @notice Validate package name format (lowercase, numbers, hyphens)
100+
/// @param name Package name to validate
101+
/// @return valid True if name is valid
102+
function _isValidPackageName(string calldata name) private pure returns (bool) {
103+
bytes memory nameBytes = bytes(name);
104+
105+
for (uint256 i = 0; i < nameBytes.length; i++) {
106+
bytes1 char = nameBytes[i];
107+
108+
// Allow: a-z, 0-9, hyphen
109+
bool isLowercase = (char >= 0x61 && char <= 0x7A); // a-z
110+
bool isNumber = (char >= 0x30 && char <= 0x39); // 0-9
111+
bool isHyphen = (char == 0x2D); // -
112+
113+
if (!isLowercase && !isNumber && !isHyphen) {
114+
return false;
115+
}
116+
}
117+
118+
return true;
119+
}
120+
}

0 commit comments

Comments
 (0)