Skip to content

Commit 303de3d

Browse files
authored
Merge pull request #4 from EIPs-CodeLab/feat/impl-core
feat: implement core logic for EIP-2678
2 parents c917da9 + 5b92043 commit 303de3d

6 files changed

Lines changed: 488 additions & 0 deletions

File tree

src/core/ManifestValidator.sol

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/// @title ManifestValidator
5+
/// @notice Validates EthPM v3 manifest structure and naming conventions (EIP-2678)
6+
library ManifestValidator {
7+
8+
/// @notice Validate contract name matches EIP-2678 regex: ^[a-zA-Z_$][a-zA-Z0-9_$]{0,255}$
9+
/// @param name Contract name to validate
10+
/// @return valid True if name is valid
11+
function isValidContractName(string memory name) internal pure returns (bool) {
12+
bytes memory nameBytes = bytes(name);
13+
14+
// Must have at least 1 character, max 256
15+
if (nameBytes.length == 0 || nameBytes.length > 256) {
16+
return false;
17+
}
18+
19+
// First character must be: a-z, A-Z, _, $
20+
bytes1 first = nameBytes[0];
21+
bool validFirst = (first >= 0x61 && first <= 0x7A) || // a-z
22+
(first >= 0x41 && first <= 0x5A) || // A-Z
23+
(first == 0x5F) || // _
24+
(first == 0x24); // $
25+
26+
if (!validFirst) {
27+
return false;
28+
}
29+
30+
// Rest can be: a-z, A-Z, 0-9, _, $
31+
for (uint256 i = 1; i < nameBytes.length; i++) {
32+
bytes1 char = nameBytes[i];
33+
bool validChar = (char >= 0x61 && char <= 0x7A) || // a-z
34+
(char >= 0x41 && char <= 0x5A) || // A-Z
35+
(char >= 0x30 && char <= 0x39) || // 0-9
36+
(char == 0x5F) || // _
37+
(char == 0x24); // $
38+
39+
if (!validChar) {
40+
return false;
41+
}
42+
}
43+
44+
return true;
45+
}
46+
47+
/// @notice Validate package name matches EIP-2678: lowercase, numbers, hyphens only
48+
/// @param name Package name to validate
49+
/// @return valid True if name is valid
50+
function isValidPackageName(string memory name) internal pure returns (bool) {
51+
bytes memory nameBytes = bytes(name);
52+
53+
if (nameBytes.length == 0) {
54+
return false;
55+
}
56+
57+
for (uint256 i = 0; i < nameBytes.length; i++) {
58+
bytes1 char = nameBytes[i];
59+
60+
bool isLowercase = (char >= 0x61 && char <= 0x7A); // a-z
61+
bool isNumber = (char >= 0x30 && char <= 0x39); // 0-9
62+
bool isHyphen = (char == 0x2D); // -
63+
64+
if (!isLowercase && !isNumber && !isHyphen) {
65+
return false;
66+
}
67+
}
68+
69+
return true;
70+
}
71+
72+
/// @notice Validate contract alias matches EIP-2678: <contract-name> or <contract-name><identifier>
73+
/// @param contractAlias Contract alias to validate
74+
/// @return valid True if alias is valid
75+
function isValidContractAlias(string memory contractAlias) internal pure returns (bool) {
76+
bytes memory aliasBytes = bytes(contractAlias);
77+
if (aliasBytes.length == 0 || aliasBytes.length > 256) {
78+
return false;
79+
}
80+
81+
// Alias can contain: a-z, A-Z, 0-9, -, _
82+
for (uint256 i = 0; i < aliasBytes.length; i++) {
83+
bytes1 char = aliasBytes[i];
84+
85+
bool isLetter = (char >= 0x61 && char <= 0x7A) || (char >= 0x41 && char <= 0x5A);
86+
bool isNumber = (char >= 0x30 && char <= 0x39);
87+
bool isHyphen = (char == 0x2D);
88+
bool isUnderscore = (char == 0x5F);
89+
90+
if (!isLetter && !isNumber && !isHyphen && !isUnderscore) {
91+
return false;
92+
}
93+
}
94+
95+
return true;
96+
}
97+
98+
/// @notice Validate manifest version is "ethpm/3"
99+
/// @param version Manifest version string
100+
/// @return valid True if version is correct
101+
function isValidManifestVersion(string memory version) internal pure returns (bool) {
102+
return keccak256(bytes(version)) == keccak256(bytes("ethpm/3"));
103+
}
104+
105+
/// @notice Check if key is forbidden in v3 manifest
106+
/// @param key Manifest key to check
107+
/// @return forbidden True if key is forbidden
108+
function isForbiddenKey(string memory key) internal pure returns (bool) {
109+
// "manifest_version" is forbidden in v3, must use "manifest"
110+
return keccak256(bytes(key)) == keccak256(bytes("manifest_version"));
111+
}
112+
}

src/core/PackageIndex.sol

Whitespace-only changes.

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+
}

src/core/PackageStorage.sol

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "../structs/PackageStructs.sol";
5+
6+
/// @title PackageStorage
7+
/// @notice Storage and retrieval of package metadata (EIP-2678)
8+
contract PackageStorage {
9+
10+
// Package name => version => PackageMeta
11+
mapping(string => mapping(string => PackageStructs.PackageMeta)) private packageMeta;
12+
13+
// Package name => version => CompilerInfo[]
14+
mapping(string => mapping(string => PackageStructs.CompilerInfo[])) private compilers;
15+
16+
// Package name => version => contract alias => ContractType
17+
mapping(string => mapping(string => mapping(string => PackageStructs.ContractType))) private contractTypes;
18+
19+
// Events
20+
event PackageMetaStored(string indexed name, string version);
21+
event CompilerInfoStored(string indexed name, string version, uint256 compilerIndex);
22+
event ContractTypeStored(string indexed name, string version, string contractAlias);
23+
24+
/// @notice Store package metadata
25+
function storePackageMeta(
26+
string calldata name,
27+
string calldata version,
28+
string[] calldata authors,
29+
string calldata license,
30+
string calldata description
31+
) external {
32+
PackageStructs.PackageMeta storage meta = packageMeta[name][version];
33+
34+
// Clear old authors if any
35+
delete meta.authors;
36+
37+
// Store new data
38+
for (uint256 i = 0; i < authors.length; i++) {
39+
meta.authors.push(authors[i]);
40+
}
41+
meta.license = license;
42+
meta.description = description;
43+
44+
emit PackageMetaStored(name, version);
45+
}
46+
47+
/// @notice Get package metadata
48+
function getPackageMeta(string calldata name, string calldata version)
49+
external
50+
view
51+
returns (string[] memory authors, string memory license, string memory description)
52+
{
53+
PackageStructs.PackageMeta storage meta = packageMeta[name][version];
54+
return (meta.authors, meta.license, meta.description);
55+
}
56+
57+
/// @notice Store compiler information
58+
function storeCompilerInfo(
59+
string calldata name,
60+
string calldata version,
61+
string calldata compilerName,
62+
string calldata compilerVersion
63+
) external {
64+
PackageStructs.CompilerInfo memory compiler;
65+
compiler.name = compilerName;
66+
compiler.version = compilerVersion;
67+
68+
compilers[name][version].push(compiler);
69+
70+
emit CompilerInfoStored(name, version, compilers[name][version].length - 1);
71+
}
72+
73+
/// @notice Get compiler information
74+
function getCompilers(string calldata name, string calldata version)
75+
external
76+
view
77+
returns (PackageStructs.CompilerInfo[] memory)
78+
{
79+
return compilers[name][version];
80+
}
81+
82+
/// @notice Store contract type
83+
function storeContractType(
84+
string calldata name,
85+
string calldata version,
86+
string calldata contractAlias,
87+
string calldata contractName,
88+
uint256 compilerIndex
89+
) external {
90+
PackageStructs.ContractType storage cType = contractTypes[name][version][contractAlias];
91+
cType.contractName = contractName;
92+
cType.compilerIndex = compilerIndex;
93+
94+
emit ContractTypeStored(name, version, contractAlias);
95+
}
96+
97+
/// @notice Get contract type
98+
function getContractType(string calldata name, string calldata version, string calldata contractAlias)
99+
external
100+
view
101+
returns (string memory contractName, uint256 compilerIndex)
102+
{
103+
PackageStructs.ContractType storage cType = contractTypes[name][version][contractAlias];
104+
return (cType.contractName, cType.compilerIndex);
105+
}
106+
}

0 commit comments

Comments
 (0)