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+ }
0 commit comments