-
-
Notifications
You must be signed in to change notification settings - Fork 17
[Bug]: Fix: enforce required identity fields (name + email/phone) #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
cf43f80
12a9ab7
5926ba3
8f60e5d
6a0d7f8
0b6ee25
340f4d9
7c36ca1
f229460
cb415de
a926546
6c30f6d
417a1be
a192669
98dc951
4cfdb02
049f8fc
c656af9
10608be
5ea5109
42ade6b
d8590fc
aa8df60
1f50c7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,25 @@ contract IdentityToken is ERC721, IIdentityToken { | |
| return tokenId; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Adds validation to ensure required identity fields are present. | ||
| * Name is mandatory and at least one contact method (email or phone) | ||
| * must be provided. | ||
| */ | ||
| function _validateRequiredFields(uint256 tokenId) internal view { | ||
| bytes memory name = attributes[tokenId][keccak256(abi.encodePacked("name"))]; | ||
| bytes memory email = attributes[tokenId][keccak256(abi.encodePacked("email"))]; | ||
| bytes memory phone = attributes[tokenId][keccak256(abi.encodePacked("phone"))]; | ||
|
|
||
| // Name is mandatory | ||
| if (name.length == 0) revert Errors.MissingName(); | ||
|
|
||
| // At least one contact method required | ||
| if (email.length == 0 && phone.length == 0) { | ||
| revert Errors.MissingContact(); | ||
| } | ||
| } | ||
|
Comment on lines
+89
to
+101
|
||
|
|
||
| /** | ||
| * @dev Sets a metadata attribute (e.g., name, social link) for an identity. | ||
| */ | ||
|
|
@@ -111,6 +130,8 @@ contract IdentityToken is ERC721, IIdentityToken { | |
| for (uint256 i = 0; i < keys.length; i++) { | ||
| _setAttribute(tokenId, keys[i], values[i]); | ||
| } | ||
|
|
||
| _validateRequiredFields(tokenId); | ||
| } | ||
|
Comment on lines
89
to
149
|
||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,8 +37,11 @@ contract IdentityTokenTest is Test { | |
| vm.prank(alice); | ||
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| // Set name first, then email to satisfy validation | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
|
Comment on lines
+43
to
+45
|
||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| bytes32 keyHash = keccak256(abi.encodePacked("name")); | ||
| bytes memory retrievedValue = identityToken.attributes(tokenId, keyHash); | ||
|
|
@@ -52,6 +55,8 @@ contract IdentityTokenTest is Test { | |
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
|
|
||
| assertEq(string(identityToken.getAttribute(tokenId, "name")), "Alice Nakamoto"); | ||
| } | ||
|
|
@@ -60,6 +65,12 @@ contract IdentityTokenTest is Test { | |
| vm.prank(alice); | ||
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| // Set required fields first | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "github", bytes("https://github.com/alice")); | ||
|
|
||
|
|
@@ -73,6 +84,11 @@ contract IdentityTokenTest is Test { | |
| vm.prank(alice); | ||
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "github", bytes("https://github.com/alice")); | ||
| vm.prank(alice); | ||
|
|
@@ -92,6 +108,8 @@ contract IdentityTokenTest is Test { | |
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
|
|
||
| assertEq(string(identityToken.getAttribute(tokenId, "name")), "Alice Nakamoto"); | ||
|
|
@@ -102,15 +120,24 @@ contract IdentityTokenTest is Test { | |
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("")); | ||
|
|
||
| assertEq(identityToken.getAttribute(tokenId, "name").length, 0); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "github", bytes("")); | ||
| assertEq(identityToken.getAttribute(tokenId, "github").length, 0); | ||
|
Comment on lines
+109
to
+116
|
||
| } | ||
|
|
||
| function test_SetAttribute_LongURL() public { | ||
| vm.prank(alice); | ||
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| // Required fields first | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "name", bytes("Alice Nakamoto")); | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "email", bytes("alice@example.com")); | ||
|
|
||
| string memory url = "https://www.linkedin.com/in/alice-nakamoto-very-long-profile-url-example-1234567890"; | ||
| vm.prank(alice); | ||
| identityToken.setAttribute(tokenId, "linkedin", bytes(url)); | ||
|
|
@@ -150,17 +177,19 @@ contract IdentityTokenTest is Test { | |
| vm.prank(alice); | ||
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| string[] memory keys = new string[](4); | ||
| string[] memory keys = new string[](5); | ||
| keys[0] = "name"; | ||
| keys[1] = "github"; | ||
| keys[2] = "nationality"; | ||
| keys[3] = "residence"; | ||
| keys[4] = "email"; | ||
|
|
||
| bytes[] memory values = new bytes[](4); | ||
| bytes[] memory values = new bytes[](5); | ||
| values[0] = bytes("Alice Nakamoto"); | ||
| values[1] = bytes("https://github.com/alice"); | ||
| values[2] = bytes("Japanese"); | ||
| values[3] = bytes("Tokyo"); | ||
| values[4] = bytes("alice@example.com"); | ||
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttributesBatch(tokenId, keys, values); | ||
|
|
@@ -169,17 +198,22 @@ contract IdentityTokenTest is Test { | |
| assertEq(string(identityToken.getAttribute(tokenId, "github")), "https://github.com/alice"); | ||
| assertEq(string(identityToken.getAttribute(tokenId, "nationality")), "Japanese"); | ||
| assertEq(string(identityToken.getAttribute(tokenId, "residence")), "Tokyo"); | ||
| assertEq(string(identityToken.getAttribute(tokenId, "email")), "alice@example.com"); | ||
| } | ||
|
|
||
| function test_SetAttributesBatch_SingleEntry() public { | ||
| vm.prank(alice); | ||
| uint256 tokenId = identityToken.mint(); | ||
|
|
||
| string[] memory keys = new string[](1); | ||
| keys[0] = "age"; | ||
| string[] memory keys = new string[](3); | ||
| keys[0] = "name"; | ||
| keys[1] = "email"; | ||
| keys[2] = "age"; | ||
|
|
||
| bytes[] memory values = new bytes[](1); | ||
| values[0] = bytes("30"); | ||
| bytes[] memory values = new bytes[](3); | ||
| values[0] = bytes("Alice Nakamoto"); | ||
| values[1] = bytes("alice@example.com"); | ||
| values[2] = bytes("30"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Rename At Line 204, the test name says "SingleEntry" but the test now submits 3 entries. Rename to avoid confusion. 🤖 Prompt for AI Agents |
||
|
|
||
| vm.prank(alice); | ||
| identityToken.setAttributesBatch(tokenId, keys, values); | ||
|
Comment on lines
161
to
200
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Gas optimization: Pre-compute constant key hashes.
The function computes
keccak256(abi.encodePacked("name")),keccak256(abi.encodePacked("email")), andkeccak256(abi.encodePacked("phone"))on every call. Since these are static strings, declare them asconstant bytes32at the contract level (or reuse fromSchema.solif already defined) to save gas.♻️ Proposed refactor
Add constants at contract level:
Then refactor the function:
function _validateRequiredFields(uint256 tokenId) internal view { - bytes memory name = attributes[tokenId][keccak256(abi.encodePacked("name"))]; - bytes memory email = attributes[tokenId][keccak256(abi.encodePacked("email"))]; - bytes memory phone = attributes[tokenId][keccak256(abi.encodePacked("phone"))]; + bytes memory name = attributes[tokenId][KEY_NAME]; + bytes memory email = attributes[tokenId][KEY_EMAIL]; + bytes memory phone = attributes[tokenId][KEY_PHONE]; // Name is mandatory if (name.length == 0) revert Errors.MissingName(); // At least one contact method required if (email.length == 0 && phone.length == 0) { revert Errors.MissingContact(); } }🤖 Prompt for AI Agents