Skip to content

Commit

Permalink
✨ Start using new sha256 gravatar addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
davisshaver committed Sep 14, 2024
1 parent 226bdcd commit c84188d
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 2 deletions.
96 changes: 96 additions & 0 deletions src/LilHash.sol
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);
}
}
19 changes: 17 additions & 2 deletions src/ProtoGravaNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import "solmate/tokens/ERC721.sol";
import "./LilBase64.sol";
import "./LilENS.sol";
import "./LilOwnable.sol";
import "./LilHash.sol";

/*//////////////////////////////////////////////////////////////
DEFAULTS
Expand Down Expand Up @@ -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
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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": []';
Expand All @@ -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", ',
Expand Down
57 changes: 57 additions & 0 deletions src/test/LilHashTest.t.sol
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"
);
}
}
8 changes: 8 additions & 0 deletions src/test/ProtoGravaNFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions src/test/utils/LilHashTest.sol
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();
}
}

0 comments on commit c84188d

Please sign in to comment.