-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Start using new sha256 gravatar addresses
- Loading branch information
1 parent
226bdcd
commit c84188d
Showing
5 changed files
with
206 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.20; | ||
|
||
/// @title LilHash | ||
/// @notice Lil' helper library for normalizing and hashing strings | ||
/// @author Davis Shaver <[email protected]> | ||
abstract contract LilHash { | ||
/// @notice Normalizes and hashes string | ||
/// @param stringToHash String to hash | ||
/// @return Hashed string | ||
function hashNormalizedString(string memory stringToHash) | ||
public | ||
pure | ||
returns (string memory) | ||
{ | ||
return toHexString(hashString(trim(toLowerCase(stringToHash)))); | ||
} | ||
|
||
/// @notice Trims whitespace from both ends of string | ||
/// @param str String to trim | ||
/// @return Trimmed string | ||
function trim(string memory str) public pure returns (string memory) { | ||
uint256 i = 0; | ||
bytes memory strBytes = bytes(str); | ||
while (i < strBytes.length && strBytes[i] == 0x20) { | ||
i++; | ||
} | ||
|
||
uint256 j = strBytes.length - 1; | ||
while (j > i && strBytes[j] == 0x20) { | ||
j--; | ||
} | ||
|
||
bytes memory trimmed = new bytes(j - i + 1); | ||
for (uint256 k = 0; k <= j - i; k++) { | ||
trimmed[k] = strBytes[k + i]; | ||
} | ||
|
||
return string(trimmed); | ||
} | ||
|
||
/// @notice Converts all characters in string to lowercase | ||
/// @param str String to convert | ||
/// @return Lowercase string | ||
function toLowerCase(string memory str) | ||
public | ||
pure | ||
returns (string memory) | ||
{ | ||
bytes memory bStr = bytes(str); | ||
bytes memory bLower = new bytes(bStr.length); | ||
|
||
for (uint256 i = 0; i < bStr.length; i++) { | ||
// Uppercase character ASCII range | ||
if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) { | ||
// Convert to lower case | ||
bLower[i] = bytes1(uint8(bStr[i]) + 32); | ||
} else { | ||
bLower[i] = bStr[i]; | ||
} | ||
} | ||
|
||
return string(bLower); | ||
} | ||
|
||
/// @notice Hashes string | ||
/// @param input String to hash | ||
/// @return Hashed string | ||
function hashString(string memory input) public pure returns (bytes32) { | ||
return sha256(abi.encodePacked(input)); | ||
} | ||
|
||
/// @notice Converts bytes32 to string | ||
/// @param _bytes32 bytes32 to convert | ||
/// @return Converted string | ||
function bytes32ToString(bytes32 _bytes32) | ||
public | ||
pure | ||
returns (string memory) | ||
{ | ||
return string(abi.encodePacked(_bytes32)); | ||
} | ||
|
||
/// @notice Converts bytes32 to hex string | ||
/// @param data bytes32 to convert | ||
/// @return Converted string | ||
function toHexString(bytes32 data) internal pure returns (string memory) { | ||
bytes memory alphabet = "0123456789abcdef"; | ||
bytes memory str = new bytes(64); | ||
for (uint256 i = 0; i < 32; i++) { | ||
str[i * 2] = alphabet[uint8(data[i] >> 4)]; | ||
str[1 + i * 2] = alphabet[uint8(data[i] & 0x0f)]; | ||
} | ||
return string(str); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ import "solmate/tokens/ERC721.sol"; | |
import "./LilBase64.sol"; | ||
import "./LilENS.sol"; | ||
import "./LilOwnable.sol"; | ||
import "./LilHash.sol"; | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
DEFAULTS | ||
|
@@ -53,7 +54,7 @@ library Events { | |
/// @title ProtoGravaNFT | ||
/// @notice Gravatar-powered ERC721 claimable by members of a Merkle tree | ||
/// @author Davis Shaver <[email protected]> | ||
contract ProtoGravaNFT is ERC721, LilENS, LilOwnable { | ||
contract ProtoGravaNFT is ERC721, LilENS, LilOwnable, LilHash { | ||
/*////////////////////////////////////////////////////////////// | ||
IMMUTABLE STORAGE | ||
//////////////////////////////////////////////////////////////*/ | ||
|
@@ -113,6 +114,12 @@ contract ProtoGravaNFT is ERC721, LilENS, LilOwnable { | |
/// @notice Thrown if a non-existent token is queried | ||
error DoesNotExist(); | ||
|
||
/// @notice Thrown if the address does not have an ENS name | ||
error NoENSName(); | ||
|
||
/// @notice Thrown if the ENS profile does not have an email address | ||
error NoENSEmailTextRecord(); | ||
|
||
/// @notice Thrown if unauthorized user tries to burn token | ||
error NotAuthorized(); | ||
|
||
|
@@ -286,6 +293,14 @@ contract ProtoGravaNFT is ERC721, LilENS, LilOwnable { | |
returns (string memory generatedTokenURIBase64) | ||
{ | ||
(string memory tokenName, bool hasEnsName) = getTokenName(id); | ||
if (hasEnsName == false) { | ||
revert NoENSName(); | ||
} | ||
string memory emailAddress = ensToText(tokenName, "email"); | ||
if (bytes(emailAddress).length == 0) { | ||
revert NoENSEmailTextRecord(); | ||
} | ||
string memory hashedEmail = hashNormalizedString(emailAddress); | ||
string memory tokenAttributes = hasEnsName | ||
? getTokenAttributes(tokenName) | ||
: '"attributes": []'; | ||
|
@@ -297,7 +312,7 @@ contract ProtoGravaNFT is ERC721, LilENS, LilOwnable { | |
'", "description": "', | ||
description, | ||
'", "image": "https://secure.gravatar.com/avatar/', | ||
gravIDsToHashes[id], | ||
hashedEmail, | ||
"?s=2048&d=", | ||
defaultFormat, | ||
'", "background_color": "4678eb", ', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.20; | ||
|
||
import "ds-test/test.sol"; | ||
import "forge-std/Vm.sol"; | ||
import "./utils/LilHashTest.sol"; | ||
|
||
contract LilHashTestContract is LilHashTest { | ||
Vm internal constant hevm = Vm(HEVM_ADDRESS); | ||
|
||
/// @notice Test trim function | ||
function testTrim() public view { | ||
string memory testString = " [email protected] "; | ||
string memory expectedString = "[email protected]"; | ||
require( | ||
keccak256(abi.encodePacked(hashtest.trim(testString))) == | ||
keccak256(abi.encodePacked(expectedString)), | ||
"Trim failed" | ||
); | ||
} | ||
|
||
/// @notice Test toLowerCase function | ||
function testToLowerCase() public view { | ||
string memory testString = "[email protected]"; | ||
string memory expectedString = "[email protected]"; | ||
require( | ||
keccak256(abi.encodePacked(hashtest.toLowerCase(testString))) == | ||
keccak256(abi.encodePacked(expectedString)), | ||
"Lower casing failed" | ||
); | ||
} | ||
|
||
/// @notice Test hashString function | ||
function testHashString() public view { | ||
string memory testString = "[email protected]"; | ||
bytes32 expectedHash = bytes32( | ||
0x599D7678A2AE568980365F733917D796443920F39FAB95DC8A590618DDF6FE8F | ||
); | ||
require( | ||
hashtest.hashString(testString) == expectedHash, | ||
"Hashing failed" | ||
); | ||
} | ||
|
||
/// @notice Test hashNormalizedString function | ||
function testHashNormalizedString() public view { | ||
string memory testString = " [email protected] "; | ||
string | ||
memory expectedHash = "599d7678a2ae568980365f733917d796443920f39fab95dc8a590618ddf6fe8f"; | ||
require( | ||
keccak256( | ||
abi.encodePacked(hashtest.hashNormalizedString(testString)) | ||
) == keccak256(abi.encodePacked(expectedHash)), | ||
"Hashing failed" | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -292,6 +292,14 @@ contract ProtoGravNFTTestContract is ProtoGravaNFTTest { | |
assertEq(aliceTokenURIPre, aliceTokenURIPost); | ||
} | ||
|
||
/// @notice Ensure that we can hash an email address and get the expected result | ||
function testHashEmail() public { | ||
assertEq( | ||
protogravanft.hashNormalizedString("[email protected]"), | ||
"599d7678a2ae568980365f733917d796443920f39fab95dc8a590618ddf6fe8f" | ||
); | ||
} | ||
|
||
/* solhint-disable quotes */ | ||
/// @notice Check for expected ENS attributes after transfer | ||
function testAliceMintTransferENSAttributes() public { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.20; | ||
|
||
/// ============ External Imports ============ | ||
|
||
import "ds-test/test.sol"; | ||
|
||
/// ============ Internal Imports ============ | ||
|
||
import "../../LilHash.sol"; | ||
|
||
/* solhint-disable no-empty-blocks */ | ||
contract LilHashExample is LilHash { | ||
|
||
} | ||
|
||
/* solhint-enable no-empty-blocks */ | ||
|
||
abstract contract LilHashTest is DSTest { | ||
/// ============ Storage ============ | ||
|
||
/// @dev LilHash contract | ||
LilHash internal hashtest; | ||
|
||
function setUp() public virtual { | ||
hashtest = new LilHashExample(); | ||
} | ||
} |