Skip to content

Commit

Permalink
multichain
Browse files Browse the repository at this point in the history
  • Loading branch information
mejango committed Jun 6, 2024
1 parent 616764a commit f0d54c6
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 155 deletions.
8 changes: 1 addition & 7 deletions script/Deploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.16;

import {Script, stdJson} from "forge-std/Script.sol";
import {IJBPermissions} from "@bananapus/core/src/interfaces/IJBPermissions.sol";
import {IJBProjects} from "@bananapus/core/src/interfaces/IJBProjects.sol";

import "../src/JBProjectHandles.sol";
Expand Down Expand Up @@ -39,13 +38,8 @@ contract Deploy is Script {
"JBProjects"
);

address permissionsAddress = _getDeploymentAddress(
string.concat("node_modules/@bananapus/core/broadcast/Deploy.s.sol/", chain, "/run-latest.json"),
"JBPermissions"
);

vm.broadcast();
new JBProjectHandles(IJBProjects(projectAddress), IJBPermissions(permissionsAddress));
new JBProjectHandles(IJBProjects(projectAddress), address(0x0));
}

/// @notice Get the address of a contract that was deployed by the Deploy script.
Expand Down
94 changes: 68 additions & 26 deletions src/JBProjectHandles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import {ITextResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol";
import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {JBPermissioned} from "@bananapus/core/src/abstract/JBPermissioned.sol";
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import {IJBProjects} from "@bananapus/core/src/interfaces/IJBProjects.sol";
import {IJBPermissions} from "@bananapus/core/src/interfaces/IJBPermissions.sol";
import {IJBProjectHandles} from "./interfaces/IJBProjectHandles.sol";
import {JBPermissionIds} from "@bananapus/permission-ids/src/JBPermissionIds.sol";

/// @notice Manages reverse records that point from JB project IDs to ENS nodes. If the reverse record of a project ID
/// is pointed to an ENS node with a TXT record matching the ID of that project, then the ENS node will be considered
/// the "handle" for that project.
contract JBProjectHandles is IJBProjectHandles, JBPermissioned {
contract JBProjectHandles is IJBProjectHandles, ERC2771Context {
//*********************************************************************//
// --------------------------- custom errors ------------------------- //
//*********************************************************************//
Expand Down Expand Up @@ -46,8 +44,11 @@ contract JBProjectHandles is IJBProjectHandles, JBPermissioned {

/// @notice Mapping of project ID to an array of strings that make up an ENS name and its subdomains.
/// @dev ["jbx", "dao", "foo"] represents foo.dao.jbx.eth.
/// @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.
mapping(uint256 projectId => string[] ensParts) private _ensNamePartsOf;
/// @custom:param projectOwner The address of the project's owner.
mapping(uint256 chainId => mapping(uint256 projectId => mapping(address projectOwner => string[] ensParts))) private
_ensNamePartsOf;

//*********************************************************************//
// ------------------------- external views -------------------------- //
Expand All @@ -56,11 +57,22 @@ contract JBProjectHandles is IJBProjectHandles, JBPermissioned {
/// @notice Returns the handle for a project.
/// @dev Requires a TXT record for the `TEXT_KEY` that matches the `projectId`. As some handles were set in the
/// previous version, try to retrieve it too (this version takes precedence on the previous version).
/// @param chainId The chain ID of the network on which the project ID exists.
/// @param projectId The ID of the project to get the handle of.
/// @param projectOwner The address of the project's owner.
/// @return handle The project's handle.
function handleOf(uint256 projectId) external view override returns (string memory) {
function handleOf(
uint256 chainId,
uint256 projectId,
address projectOwner
)
external
view
override
returns (string memory)
{
// Get a reference to the project's ENS name parts.
string[] memory ensNameParts = _ensNamePartsOf[projectId];
string[] memory ensNameParts = _ensNamePartsOf[chainId][projectId][projectOwner];

// Return an empty string if not found.
if (ensNameParts.length == 0) return "";
Expand All @@ -75,29 +87,48 @@ contract JBProjectHandles is IJBProjectHandles, JBPermissioned {
if (textResolver == address(0)) return "";

// Find the projectId that the text record of the ENS name is mapped to.
string memory textRecordProjectId = ITextResolver(textResolver).text(hashedName, TEXT_KEY);
string memory textRecordProjectId =
ITextResolver(textResolver).text(hashedName, string.concat(TEXT_KEY, ":projectId"));

// Return empty string if text record from ENS name doesn't match projectId.
if (keccak256(bytes(textRecordProjectId)) != keccak256(bytes(Strings.toString(projectId)))) return "";
// Find the chainId that the text record of the ENS name is mapped to.
string memory textRecordChainId =
ITextResolver(textResolver).text(hashedName, string.concat(TEXT_KEY, ":chainId"));

// Return empty string if text record from ENS name doesn't match projectId or chainId.
if (
keccak256(bytes(textRecordProjectId)) != keccak256(bytes(Strings.toString(projectId)))
|| keccak256(bytes(textRecordChainId)) != keccak256(bytes(Strings.toString(chainId)))
) return "";

// Format the handle from the name parts.
return _formatHandle(ensNameParts);
}

/// @notice The parts of the stored ENS name of a project.
/// @param chainId The chain ID of the network on which the project ID exists.
/// @param projectId The ID of the project to get the ENS name of.
/// @param projectOwner The address of the project's owner.
/// @return The parts of the ENS name parts of a project.
function ensNamePartsOf(uint256 projectId) external view override returns (string[] memory) {
return _ensNamePartsOf[projectId];
function ensNamePartsOf(
uint256 chainId,
uint256 projectId,
address projectOwner
)
external
view
override
returns (string[] memory)
{
return _ensNamePartsOf[chainId][projectId][projectOwner];
}

//*********************************************************************//
// ---------------------------- constructor -------------------------- //
//*********************************************************************//

/// @param projects A contract which mints ERC-721's that represent project ownership and transfers.
/// @param permissions A contract storing permissions.
constructor(IJBProjects projects, IJBPermissions permissions) JBPermissioned(permissions) {
/// @param trustedForwarder The trusted forwarder for the ERC2771Context.
constructor(IJBProjects projects, address trustedForwarder) ERC2771Context(trustedForwarder) {
PROJECTS = projects;
}

Expand All @@ -108,31 +139,25 @@ contract JBProjectHandles is IJBProjectHandles, JBPermissioned {
/// @notice Associate an ENS name with a project.
/// @dev ["jbx", "dao", "foo"] represents foo.dao.jbx.eth.
/// @dev Only a project's owner or a designated operator can set its ENS name parts.
/// @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 projectId, string[] memory parts) external override {
// Enforce permissions.
_requirePermissionFrom({
account: PROJECTS.ownerOf(projectId),
projectId: projectId,
permissionId: JBPermissionIds.SET_ENS_NAME
});

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;

// Make sure there are ens name parts.
if (partsLength == 0) revert NO_PARTS();

// Make sure no provided parts are empty.
for (uint256 i = 0; i < partsLength; i++) {
for (uint256 i; i < partsLength; i++) {
if (bytes(parts[i]).length == 0) revert EMPTY_NAME_PART();
}

// Store the parts.
_ensNamePartsOf[projectId] = parts;
_ensNamePartsOf[chainId][projectId][_msgSender()] = parts;

emit SetEnsNameParts(projectId, _formatHandle(parts), parts, msg.sender);
emit SetEnsNameParts(projectId, _formatHandle(parts), parts, _msgSender());
}

//*********************************************************************//
Expand Down Expand Up @@ -168,8 +193,25 @@ contract JBProjectHandles is IJBProjectHandles, JBPermissioned {
uint256 nameLength = ensNameParts.length;

// Hash each part.
for (uint256 i = 0; i < nameLength; i++) {
for (uint256 i; i < nameLength; i++) {
namehash = keccak256(abi.encodePacked(namehash, keccak256(abi.encodePacked(ensNameParts[i]))));
}
}

/// @notice Returns the sender, prefered to use over `msg.sender`
/// @return sender the sender address of this call.
function _msgSender() internal view override returns (address sender) {
return ERC2771Context._msgSender();
}

/// @notice Returns the calldata, prefered to use over `msg.data`
/// @return calldata the `msg.data` of this call
function _msgData() internal view override returns (bytes calldata) {
return ERC2771Context._msgData();
}

/// @dev ERC-2771 specifies the context as being a single address (20 bytes).
function _contextSuffixLength() internal view virtual override returns (uint256) {
return super._contextSuffixLength();
}
}
13 changes: 10 additions & 3 deletions src/interfaces/IJBProjectHandles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import "@bananapus/core/src/interfaces/IJBProjects.sol";
interface IJBProjectHandles {
event SetEnsNameParts(uint256 indexed projectId, string indexed handle, string[] parts, address caller);

function setEnsNamePartsFor(uint256 _projectId, string[] memory _parts) external;
function setEnsNamePartsFor(uint256 chainId, uint256 projectId, string[] memory parts) external;

function ensNamePartsOf(uint256 _projectId) external view returns (string[] memory);
function ensNamePartsOf(
uint256 chainId,
uint256 projectId,
address projectOwner
)
external
view
returns (string[] memory);

function TEXT_KEY() external view returns (string memory);

function PROJECTS() external view returns (IJBProjects);

function handleOf(uint256 _projectId) external view returns (string memory);
function handleOf(uint256 chainId, uint256 projectId, address projectOwner) external view returns (string memory);
}
Loading

0 comments on commit f0d54c6

Please sign in to comment.