Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions src/core/ManifestValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title ManifestValidator
/// @notice Validates EthPM v3 manifest structure and naming conventions (EIP-2678)
library ManifestValidator {

/// @notice Validate contract name matches EIP-2678 regex: ^[a-zA-Z_$][a-zA-Z0-9_$]{0,255}$
/// @param name Contract name to validate
/// @return valid True if name is valid
function isValidContractName(string memory name) internal pure returns (bool) {
bytes memory nameBytes = bytes(name);

// Must have at least 1 character, max 256
if (nameBytes.length == 0 || nameBytes.length > 256) {
return false;
}

// First character must be: a-z, A-Z, _, $
bytes1 first = nameBytes[0];
bool validFirst = (first >= 0x61 && first <= 0x7A) || // a-z
(first >= 0x41 && first <= 0x5A) || // A-Z
(first == 0x5F) || // _
(first == 0x24); // $

if (!validFirst) {
return false;
}

// Rest can be: a-z, A-Z, 0-9, _, $
for (uint256 i = 1; i < nameBytes.length; i++) {
bytes1 char = nameBytes[i];
bool validChar = (char >= 0x61 && char <= 0x7A) || // a-z
(char >= 0x41 && char <= 0x5A) || // A-Z
(char >= 0x30 && char <= 0x39) || // 0-9
(char == 0x5F) || // _
(char == 0x24); // $

if (!validChar) {
return false;
}
}

return true;
}

/// @notice Validate package name matches EIP-2678: lowercase, numbers, hyphens only
/// @param name Package name to validate
/// @return valid True if name is valid
function isValidPackageName(string memory name) internal pure returns (bool) {
bytes memory nameBytes = bytes(name);

if (nameBytes.length == 0) {
return false;
}

for (uint256 i = 0; i < nameBytes.length; i++) {
bytes1 char = nameBytes[i];

bool isLowercase = (char >= 0x61 && char <= 0x7A); // a-z
bool isNumber = (char >= 0x30 && char <= 0x39); // 0-9
bool isHyphen = (char == 0x2D); // -

if (!isLowercase && !isNumber && !isHyphen) {
return false;
}
}

return true;
}

/// @notice Validate contract alias matches EIP-2678: <contract-name> or <contract-name><identifier>
/// @param contractAlias Contract alias to validate
/// @return valid True if alias is valid
function isValidContractAlias(string memory contractAlias) internal pure returns (bool) {
bytes memory aliasBytes = bytes(contractAlias);
if (aliasBytes.length == 0 || aliasBytes.length > 256) {
return false;
}

// Alias can contain: a-z, A-Z, 0-9, -, _
for (uint256 i = 0; i < aliasBytes.length; i++) {
bytes1 char = aliasBytes[i];

bool isLetter = (char >= 0x61 && char <= 0x7A) || (char >= 0x41 && char <= 0x5A);
bool isNumber = (char >= 0x30 && char <= 0x39);
bool isHyphen = (char == 0x2D);
bool isUnderscore = (char == 0x5F);

if (!isLetter && !isNumber && !isHyphen && !isUnderscore) {
return false;
}
}

return true;
}

/// @notice Validate manifest version is "ethpm/3"
/// @param version Manifest version string
/// @return valid True if version is correct
function isValidManifestVersion(string memory version) internal pure returns (bool) {
return keccak256(bytes(version)) == keccak256(bytes("ethpm/3"));
}

/// @notice Check if key is forbidden in v3 manifest
/// @param key Manifest key to check
/// @return forbidden True if key is forbidden
function isForbiddenKey(string memory key) internal pure returns (bool) {
// "manifest_version" is forbidden in v3, must use "manifest"
return keccak256(bytes(key)) == keccak256(bytes("manifest_version"));
}
}
Empty file removed src/core/PackageIndex.sol
Empty file.
120 changes: 120 additions & 0 deletions src/core/PackageRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../interfaces/IPackageRegistry.sol";
import "../structs/PackageStructs.sol";

/// @title PackageRegistry
/// @notice On-chain registry for EthPM v3 packages (EIP-2678)
/// @dev Stores package references with content-addressable URIs (IPFS)
contract PackageRegistry is IPackageRegistry {

// Package name => version => manifest URI (IPFS/Swarm)
mapping(string => mapping(string => string)) private packages;

// Package name => owner address
mapping(string => address) private packageOwners;

// Package name => all versions
mapping(string => string[]) private packageVersions;

// Events
event PackagePublished(string indexed name, string version, string manifestURI, address publisher);
event PackageOwnershipTransferred(string indexed name, address previousOwner, address newOwner);

/// @notice Publish a package to the registry
/// @param name Package name (must be lowercase, numbers, hyphens only)
/// @param version Package version (semver format)
/// @param manifestURI Content-addressable URI (ipfs:// or bzz://)
function publish(string calldata name, string calldata version, string calldata manifestURI) external override {
require(bytes(name).length > 0, "Package name cannot be empty");
require(bytes(version).length > 0, "Version cannot be empty");
require(bytes(manifestURI).length > 0, "Manifest URI cannot be empty");
require(_isValidPackageName(name), "Invalid package name format");

// Check ownership
if (packageOwners[name] == address(0)) {
// First time publishing this package
packageOwners[name] = msg.sender;
} else {
// Only owner can publish new versions
require(packageOwners[name] == msg.sender, "Only package owner can publish");
}

// Check if version already exists
require(bytes(packages[name][version]).length == 0, "Version already exists");

// Store package
packages[name][version] = manifestURI;
packageVersions[name].push(version);

emit PackagePublished(name, version, manifestURI, msg.sender);
}

/// @notice Get package manifest URI
/// @param name Package name
/// @param version Package version
/// @return manifestURI The content-addressable URI of the package manifest
function getPackageURI(string calldata name, string calldata version) external view override returns (string memory) {
string memory uri = packages[name][version];
require(bytes(uri).length > 0, "Package version does not exist");
return uri;
}

/// @notice Check if package version exists
/// @param name Package name
/// @param version Package version
/// @return exists True if package exists
function packageExists(string calldata name, string calldata version) external view override returns (bool) {
return bytes(packages[name][version]).length > 0;
}

/// @notice Get all versions of a package
/// @param name Package name
/// @return versions Array of all versions
function getVersions(string calldata name) external view returns (string[] memory) {
return packageVersions[name];
}

/// @notice Get package owner
/// @param name Package name
/// @return owner Address of package owner
function getOwner(string calldata name) external view returns (address) {
return packageOwners[name];
}

/// @notice Transfer package ownership
/// @param name Package name
/// @param newOwner New owner address
function transferOwnership(string calldata name, address newOwner) external {
require(packageOwners[name] == msg.sender, "Only owner can transfer ownership");
require(newOwner != address(0), "New owner cannot be zero address");

address previousOwner = packageOwners[name];
packageOwners[name] = newOwner;

emit PackageOwnershipTransferred(name, previousOwner, newOwner);
}

/// @notice Validate package name format (lowercase, numbers, hyphens)
/// @param name Package name to validate
/// @return valid True if name is valid
function _isValidPackageName(string calldata name) private pure returns (bool) {
bytes memory nameBytes = bytes(name);

for (uint256 i = 0; i < nameBytes.length; i++) {
bytes1 char = nameBytes[i];

// Allow: a-z, 0-9, hyphen
bool isLowercase = (char >= 0x61 && char <= 0x7A); // a-z
bool isNumber = (char >= 0x30 && char <= 0x39); // 0-9
bool isHyphen = (char == 0x2D); // -

if (!isLowercase && !isNumber && !isHyphen) {
return false;
}
}

return true;
}
}
106 changes: 106 additions & 0 deletions src/core/PackageStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../structs/PackageStructs.sol";

/// @title PackageStorage
/// @notice Storage and retrieval of package metadata (EIP-2678)
contract PackageStorage {

// Package name => version => PackageMeta
mapping(string => mapping(string => PackageStructs.PackageMeta)) private packageMeta;

// Package name => version => CompilerInfo[]
mapping(string => mapping(string => PackageStructs.CompilerInfo[])) private compilers;

// Package name => version => contract alias => ContractType
mapping(string => mapping(string => mapping(string => PackageStructs.ContractType))) private contractTypes;

// Events
event PackageMetaStored(string indexed name, string version);
event CompilerInfoStored(string indexed name, string version, uint256 compilerIndex);
event ContractTypeStored(string indexed name, string version, string contractAlias);

/// @notice Store package metadata
function storePackageMeta(
string calldata name,
string calldata version,
string[] calldata authors,
string calldata license,
string calldata description
) external {
PackageStructs.PackageMeta storage meta = packageMeta[name][version];

// Clear old authors if any
delete meta.authors;

// Store new data
for (uint256 i = 0; i < authors.length; i++) {
meta.authors.push(authors[i]);
}
meta.license = license;
meta.description = description;

emit PackageMetaStored(name, version);
}

/// @notice Get package metadata
function getPackageMeta(string calldata name, string calldata version)
external
view
returns (string[] memory authors, string memory license, string memory description)
{
PackageStructs.PackageMeta storage meta = packageMeta[name][version];
return (meta.authors, meta.license, meta.description);
}

/// @notice Store compiler information
function storeCompilerInfo(
string calldata name,
string calldata version,
string calldata compilerName,
string calldata compilerVersion
) external {
PackageStructs.CompilerInfo memory compiler;
compiler.name = compilerName;
compiler.version = compilerVersion;

compilers[name][version].push(compiler);

emit CompilerInfoStored(name, version, compilers[name][version].length - 1);
}

/// @notice Get compiler information
function getCompilers(string calldata name, string calldata version)
external
view
returns (PackageStructs.CompilerInfo[] memory)
{
return compilers[name][version];
}

/// @notice Store contract type
function storeContractType(
string calldata name,
string calldata version,
string calldata contractAlias,
string calldata contractName,
uint256 compilerIndex
) external {
PackageStructs.ContractType storage cType = contractTypes[name][version][contractAlias];
cType.contractName = contractName;
cType.compilerIndex = compilerIndex;

emit ContractTypeStored(name, version, contractAlias);
}

/// @notice Get contract type
function getContractType(string calldata name, string calldata version, string calldata contractAlias)
external
view
returns (string memory contractName, uint256 compilerIndex)
{
PackageStructs.ContractType storage cType = contractTypes[name][version][contractAlias];
return (cType.contractName, cType.compilerIndex);
}
}
Empty file removed src/structs/ContractInstance.sol
Empty file.
Empty file removed src/structs/DeploymentInfo.sol
Empty file.
Empty file removed src/structs/PackageMetadata.sol
Empty file.
Loading
Loading