diff --git a/src/JBProjectHandles.sol b/src/JBProjectHandles.sol index 1e1fd9f..4106bca 100644 --- a/src/JBProjectHandles.sol +++ b/src/JBProjectHandles.sol @@ -29,8 +29,7 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { /// @notice The ENS registry contract address. /// @dev Same on every network - ENS public constant ENS_REGISTRY = - ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e); + ENS public constant ENS_REGISTRY = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e); //*********************************************************************// // --------------- public immutable stored properties ---------------- // @@ -48,8 +47,8 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { /// @custom:param chainId The chain ID of the network on which the project ID exists. /// @custom:param projectId The ID of the project to get an ENS name for. /// @custom:param projectOwner The address of the project's owner. - mapping(uint256 chainId => mapping(uint256 projectId => mapping(address projectOwner => string[] ensParts))) - private _ensNamePartsOf; + mapping(uint256 chainId => mapping(uint256 projectId => mapping(address projectOwner => string[] ensParts))) private + _ensNamePartsOf; //*********************************************************************// // ------------------------- external views -------------------------- // @@ -66,11 +65,14 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { uint256 chainId, uint256 projectId, address projectOwner - ) external view override returns (string memory) { + ) + external + view + override + returns (string memory) + { // Get a reference to the project's ENS name parts. - string[] memory ensNameParts = _ensNamePartsOf[chainId][projectId][ - projectOwner - ]; + string[] memory ensNameParts = _ensNamePartsOf[chainId][projectId][projectOwner]; // Return an empty string if not found. if (ensNameParts.length == 0) return ""; @@ -85,25 +87,22 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { if (textResolver == address(0)) return ""; // Find the projectId that the text record of the ENS name is mapped to. - string memory textRecord = ITextResolver(textResolver).text( - hashedName, - TEXT_KEY - ); + string memory textRecord = ITextResolver(textResolver).text(hashedName, TEXT_KEY); // Return empty string if text record from ENS name doesn't match projectId or chainId. if ( - keccak256(bytes(textRecord)) != - keccak256( - bytes( - string.concat( - Strings.toString(chainId), - ":", - Strings.toString(projectId), - ":", - Strings.toHexString(uint256(uint160(projectOwner))) + keccak256(bytes(textRecord)) + != keccak256( + bytes( + string.concat( + Strings.toString(chainId), + ":", + Strings.toString(projectId), + ":", + Strings.toHexString(uint256(uint160(projectOwner))) + ) ) ) - ) ) return ""; // Format the handle from the name parts. @@ -119,7 +118,12 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { uint256 chainId, uint256 projectId, address projectOwner - ) external view override returns (string[] memory) { + ) + external + view + override + returns (string[] memory) + { return _ensNamePartsOf[chainId][projectId][projectOwner]; } @@ -129,10 +133,7 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { /// @param projects A contract which mints ERC-721's that represent project ownership and transfers. /// @param trustedForwarder The trusted forwarder for the ERC2771Context. - constructor( - IJBProjects projects, - address trustedForwarder - ) ERC2771Context(trustedForwarder) { + constructor(IJBProjects projects, address trustedForwarder) ERC2771Context(trustedForwarder) { PROJECTS = projects; } @@ -146,11 +147,7 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { /// @param chainId The chain ID of the network on which the project ID exists. /// @param projectId The ID of the project to set an ENS handle for. /// @param parts The parts of the ENS domain to use as the project handle, excluding the trailing .eth. - function setEnsNamePartsFor( - uint256 chainId, - uint256 projectId, - string[] memory parts - ) external override { + function setEnsNamePartsFor(uint256 chainId, uint256 projectId, string[] memory parts) external override { // Get a reference to the number of parts are in the ENS name. uint256 partsLength = parts.length; @@ -165,12 +162,7 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { // Store the parts. _ensNamePartsOf[chainId][projectId][_msgSender()] = parts; - emit SetEnsNameParts( - projectId, - _formatHandle(parts), - parts, - _msgSender() - ); + emit SetEnsNameParts(projectId, _formatHandle(parts), parts, _msgSender()); } //*********************************************************************// @@ -180,18 +172,14 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { /// @notice Formats ENS name parts into a handle. /// @param ensNameParts The ENS name parts to format into a handle. /// @return handle The formatted ENS handle. - function _formatHandle( - string[] memory ensNameParts - ) internal pure returns (string memory handle) { + function _formatHandle(string[] memory ensNameParts) internal pure returns (string memory handle) { // Get a reference to the number of parts are in the ENS name. uint256 partsLength = ensNameParts.length; // Concatenate each name part. for (uint256 i = 1; i <= partsLength; i++) { // Compute the handle. - handle = string( - abi.encodePacked(handle, ensNameParts[partsLength - i]) - ); + handle = string(abi.encodePacked(handle, ensNameParts[partsLength - i])); // Add a dot if this part isn't the last. if (i < partsLength) handle = string(abi.encodePacked(handle, ".")); @@ -202,25 +190,16 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { /// @dev See https://eips.ethereum.org/EIPS/eip-137. /// @param ensNameParts The parts of an ENS name to hash. /// @return namehash The namehash for an ENS name parts. - function _namehash( - string[] memory ensNameParts - ) internal pure returns (bytes32 namehash) { + function _namehash(string[] memory ensNameParts) internal pure returns (bytes32 namehash) { // Hash the trailing "eth" suffix. - namehash = keccak256( - abi.encodePacked(namehash, keccak256(abi.encodePacked("eth"))) - ); + namehash = keccak256(abi.encodePacked(namehash, keccak256(abi.encodePacked("eth")))); // Get a reference to the number of parts are in the ENS name. uint256 nameLength = ensNameParts.length; // Hash each part. for (uint256 i; i < nameLength; i++) { - namehash = keccak256( - abi.encodePacked( - namehash, - keccak256(abi.encodePacked(ensNameParts[i])) - ) - ); + namehash = keccak256(abi.encodePacked(namehash, keccak256(abi.encodePacked(ensNameParts[i])))); } } @@ -237,13 +216,7 @@ contract JBProjectHandles is IJBProjectHandles, ERC2771Context { } /// @dev ERC-2771 specifies the context as being a single address (20 bytes). - function _contextSuffixLength() - internal - view - virtual - override - returns (uint256) - { + function _contextSuffixLength() internal view virtual override returns (uint256) { return super._contextSuffixLength(); } } diff --git a/src/interfaces/IJBProjectHandles.sol b/src/interfaces/IJBProjectHandles.sol index 167f9be..5cc3c95 100644 --- a/src/interfaces/IJBProjectHandles.sol +++ b/src/interfaces/IJBProjectHandles.sol @@ -5,32 +5,22 @@ import "@ensdomains/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol import "@bananapus/core/src/interfaces/IJBProjects.sol"; interface IJBProjectHandles { - event SetEnsNameParts( - uint256 indexed projectId, - string indexed handle, - string[] parts, - address caller - ); + event SetEnsNameParts(uint256 indexed projectId, string indexed handle, string[] parts, address caller); - function setEnsNamePartsFor( - uint256 chainId, - uint256 projectId, - string[] memory parts - ) external; + function setEnsNamePartsFor(uint256 chainId, uint256 projectId, string[] memory parts) external; function ensNamePartsOf( uint256 chainId, uint256 projectId, address projectOwner - ) external view returns (string[] memory); + ) + external + view + returns (string[] memory); function TEXT_KEY() external view returns (string memory); function PROJECTS() external view returns (IJBProjects); - function handleOf( - uint256 chainId, - uint256 projectId, - address projectOwner - ) external view returns (string memory); + function handleOf(uint256 chainId, uint256 projectId, address projectOwner) external view returns (string memory); } diff --git a/test/JBProjectHandles.t.sol b/test/JBProjectHandles.t.sol index 93b8707..c88c9a4 100644 --- a/test/JBProjectHandles.t.sol +++ b/test/JBProjectHandles.t.sol @@ -13,18 +13,11 @@ import "../src/JBProjectHandles.sol"; import {JBPermissionIds} from "@bananapus/permission-ids/src/JBPermissionIds.sol"; ENS constant ensRegistry = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e); -IJBProjectHandles constant oldHandle = IJBProjectHandles( - 0x41126eC99F8A989fEB503ac7bB4c5e5D40E06FA4 -); +IJBProjectHandles constant oldHandle = IJBProjectHandles(0x41126eC99F8A989fEB503ac7bB4c5e5D40E06FA4); contract ContractTest is Test { // For testing the event emitted - event SetEnsNameParts( - uint256 indexed projectId, - string indexed ensName, - string[] parts, - address caller - ); + event SetEnsNameParts(uint256 indexed projectId, string indexed ensName, string[] parts, address caller); address projectOwner = address(6_942_069); @@ -48,9 +41,7 @@ contract ContractTest is Test { // ------------------------ SetEnsNamePartsFor(..) ------------------- // //*********************************************************************// - function testSetEnsNamePartsFor_passIfCallerIsProjectOwnerAndOnlyName( - string calldata name - ) public { + function testSetEnsNamePartsFor_passIfCallerIsProjectOwnerAndOnlyName(string calldata name) public { vm.assume(bytes(name).length != 0); uint256 projectId = jbProjects.createFor(projectOwner); @@ -67,22 +58,17 @@ contract ContractTest is Test { projectHandle.setEnsNamePartsFor(chainId, projectId, nameParts); // Control: correct ENS name? - assertEq( - projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), - nameParts - ); + assertEq(projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), nameParts); } function testSetEnsNameWithSubdomainFor_passIfMultipleSubdomainLevels( string memory name, string memory subdomain, string memory subsubdomain - ) public { - vm.assume( - bytes(name).length > 0 && - bytes(subdomain).length > 0 && - bytes(subsubdomain).length > 0 - ); + ) + public + { + vm.assume(bytes(name).length > 0 && bytes(subdomain).length > 0 && bytes(subsubdomain).length > 0); uint256 projectId = jbProjects.createFor(projectOwner); uint256 chainId = 1; @@ -93,9 +79,7 @@ contract ContractTest is Test { nameParts[1] = subdomain; nameParts[2] = name; - string memory fullName = string( - abi.encodePacked(name, ".", subdomain, ".", subsubdomain) - ); + string memory fullName = string(abi.encodePacked(name, ".", subdomain, ".", subsubdomain)); // Test event vm.expectEmit(true, true, true, true); @@ -105,22 +89,17 @@ contract ContractTest is Test { projectHandle.setEnsNamePartsFor(chainId, projectId, nameParts); // Control: ENS has correct name and domain - assertEq( - projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), - nameParts - ); + assertEq(projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), nameParts); } function testSetEnsNameWithSubdomainFor_RevertIfEmptyElementInNameParts( string memory name, string memory subdomain, string memory subsubdomain - ) public { - vm.assume( - bytes(name).length == 0 || - bytes(subdomain).length == 0 || - bytes(subsubdomain).length == 0 - ); + ) + public + { + vm.assume(bytes(name).length == 0 || bytes(subdomain).length == 0 || bytes(subsubdomain).length == 0); uint256 projectId = jbProjects.createFor(projectOwner); uint256 chainId = 1; @@ -136,10 +115,7 @@ contract ContractTest is Test { projectHandle.setEnsNamePartsFor(chainId, projectId, nameParts); // Control: ENS has correct name and domain - assertEq( - projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), - new string[](0) - ); + assertEq(projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), new string[](0)); } function testSetEnsNameWithSubdomainFor_RevertIfEmptyNameParts() public { @@ -154,27 +130,18 @@ contract ContractTest is Test { projectHandle.setEnsNamePartsFor(chainId, projectId, nameParts); // Control: ENS has correct name and domain - assertEq( - projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), - new string[](0) - ); + assertEq(projectHandle.ensNamePartsOf(chainId, projectId, projectOwner), new string[](0)); } //*********************************************************************// // ---------------------------- handleOf(..) ------------------------- // //*********************************************************************// - function testHandleOf_returnsEmptyStringIfNoHandleSet( - uint256 chainId, - uint256 projectId - ) public { + function testHandleOf_returnsEmptyStringIfNoHandleSet(uint256 chainId, uint256 projectId) public { // No handle set on the previous JBProjectHandle version neither vm.mockCall( address(oldHandle), - abi.encodeCall( - IJBProjectHandles.ensNamePartsOf, - (chainId, projectId, projectOwner) - ), + abi.encodeCall(IJBProjectHandles.ensNamePartsOf, (chainId, projectId, projectOwner)), abi.encode(new string[](0)) ); @@ -185,12 +152,10 @@ contract ContractTest is Test { string calldata name, string calldata subdomain, string calldata subsubdomain - ) public { - vm.assume( - bytes(name).length > 0 && - bytes(subdomain).length > 0 && - bytes(subsubdomain).length > 0 - ); + ) + public + { + vm.assume(bytes(name).length > 0 && bytes(subdomain).length > 0 && bytes(subsubdomain).length > 0); uint256 projectId = jbProjects.createFor(projectOwner); uint256 chainId = 1; @@ -220,11 +185,7 @@ contract ContractTest is Test { vm.mockCall( address(ensTextResolver), - abi.encodeWithSelector( - ITextResolver.text.selector, - _namehash(nameParts), - KEY - ), + abi.encodeWithSelector(ITextResolver.text.selector, _namehash(nameParts), KEY), abi.encode( string.concat( Strings.toString(chainId), @@ -239,10 +200,7 @@ contract ContractTest is Test { // Mock the registration on the previous version vm.mockCall( address(oldHandle), - abi.encodeCall( - IJBProjectHandles.ensNamePartsOf, - (chainId, projectId, projectOwner) - ), + abi.encodeCall(IJBProjectHandles.ensNamePartsOf, (chainId, projectId, projectOwner)), abi.encode(oldNamePart) ); @@ -260,16 +218,15 @@ contract ContractTest is Test { string calldata name, string calldata subdomain, string calldata subsubdomain - ) public { + ) + public + { vm.assume(projectId != reverseId); // No handle set on the previous JBProjectHandle version vm.mockCall( address(oldHandle), - abi.encodeCall( - IJBProjectHandles.ensNamePartsOf, - (chainId, projectId, projectOwner) - ), + abi.encodeCall(IJBProjectHandles.ensNamePartsOf, (chainId, projectId, projectOwner)), abi.encode(new string[](0)) ); @@ -295,16 +252,15 @@ contract ContractTest is Test { string calldata name, string calldata subdomain, string calldata subsubdomain - ) public { + ) + public + { vm.assume(projectId != reverseId); // No handle set on the previous JBProjectHandle version vm.mockCall( address(oldHandle), - abi.encodeCall( - IJBProjectHandles.ensNamePartsOf, - (chainId, projectId, projectOwner) - ), + abi.encodeCall(IJBProjectHandles.ensNamePartsOf, (chainId, projectId, projectOwner)), abi.encode(new string[](0)) ); @@ -324,11 +280,7 @@ contract ContractTest is Test { vm.mockCall( address(ensTextResolver), - abi.encodeWithSelector( - ITextResolver.text.selector, - _namehash(nameParts), - KEY - ), + abi.encodeWithSelector(ITextResolver.text.selector, _namehash(nameParts), KEY), abi.encode(Strings.toString(reverseId)) ); @@ -339,12 +291,10 @@ contract ContractTest is Test { string calldata name, string calldata subdomain, string calldata subsubdomain - ) public { - vm.assume( - bytes(name).length > 0 && - bytes(subdomain).length > 0 && - bytes(subsubdomain).length > 0 - ); + ) + public + { + vm.assume(bytes(name).length > 0 && bytes(subdomain).length > 0 && bytes(subsubdomain).length > 0); uint256 projectId = jbProjects.createFor(projectOwner); uint256 chainId = 1; @@ -368,11 +318,7 @@ contract ContractTest is Test { vm.mockCall( address(ensTextResolver), - abi.encodeWithSelector( - ITextResolver.text.selector, - _namehash(nameParts), - KEY - ), + abi.encodeWithSelector(ITextResolver.text.selector, _namehash(nameParts), KEY), abi.encode( string.concat( Strings.toString(chainId), @@ -402,24 +348,15 @@ contract ContractTest is Test { } } - function _namehash( - string[] memory ensName - ) internal pure returns (bytes32 namehash) { - namehash = keccak256( - abi.encodePacked(namehash, keccak256(abi.encodePacked("eth"))) - ); + function _namehash(string[] memory ensName) internal pure returns (bytes32 namehash) { + namehash = keccak256(abi.encodePacked(namehash, keccak256(abi.encodePacked("eth")))); // Get a reference to the number of parts are in the ENS name. uint256 nameLength = ensName.length; // Hash each part. for (uint256 i = 0; i < nameLength; i++) { - namehash = keccak256( - abi.encodePacked( - namehash, - keccak256(abi.encodePacked(ensName[i])) - ) - ); + namehash = keccak256(abi.encodePacked(namehash, keccak256(abi.encodePacked(ensName[i])))); } } }