feat: First minimal handle implementation

Co-authored-by: Victor Naumik <vicnaum@gmail.com>
This commit is contained in:
donosonaumczuk
2023-02-27 17:34:35 +00:00
parent 7c41d8d08f
commit ade3cf603a
3 changed files with 244 additions and 13 deletions

View File

@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import {ERC721} from '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
// TODO: Move to a Errors file
library Errors {
error NotHandleOwner();
error NotProfileOwner();
error NotHandleOrProfileOwner();
}
// TODO: Move to a Events file
library Events {
event HandleLinked(uint256 handleId, uint256 profileId);
event HandleUnlinked(uint256 handleId, uint256 profileId);
}
contract Handles is ERC721, Ownable {
address immutable LENS_HUB;
mapping(uint256 handleId => uint256 profileId) handleToProfile;
mapping(uint256 profileId => uint256 handleId) profileToHandle;
modifier onlyHandleOwner(uint256 handleId, address transactionExecutor) {
if (ownerOf(handleId) != transactionExecutor) {
revert Errors.NotHandleOwner();
}
_;
}
modifier onlyProfileOwner(uint256 profileId, address transactionExecutor) {
if (IERC721(LENS_HUB).ownerOf(profileId) != transactionExecutor) {
revert Errors.NotProfileOwner();
}
_;
}
modifier onlyHandleOrProfileOwner(
uint256 handleId,
uint256 profileId,
address transactionExecutor
) {
// The transaction executor must at least be the owner of either the handle or the profile.
// Used for unlinking (so either the handle owner or the profile owner can unlink)
if (ownerOf(handleId) != transactionExecutor && IERC721(LENS_HUB).ownerOf(profileId) != transactionExecutor) {
revert Errors.NotHandleOrProfileOwner();
}
_;
}
// NOTE: We don't need whitelisting yet as we use immutable constants for the first version.
constructor(address lensHub) ERC721('Lens Canonical Handles', '.lens') {
LENS_HUB = lensHub;
}
function linkHandleWithProfile(
uint256 handleId,
uint256 profileId
) external onlyProfileOwner(profileId, msg.sender) onlyHandleOwner(handleId, msg.sender) {
handleToProfile[handleId] = profileId;
profileToHandle[profileId] = handleId;
emit Events.HandleLinked(handleId, profileId);
}
function unlinkHandleFromProfile(
uint256 handleId,
uint256 profileId
) external onlyHandleOrProfileOwner(handleId, profileId, msg.sender) {
delete handleToProfile[handleId];
delete profileToHandle[profileId];
emit Events.HandleUnlinked(handleId, profileId);
}
function resolveProfile(uint256 profileId) external view returns (uint256) {
return profileToHandle[profileId];
}
function resolveHandle(uint256 handleId) external view returns (uint256) {
return handleToProfile[handleId];
}
}

View File

@@ -0,0 +1,153 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
struct Token {
address collection;
uint96 _gap;
uint256 id;
}
// TODO: Move to a Errors file
library Errors {
error NotHandleOwner();
error NotProfileOwner();
error NotHandleOrProfileOwner();
}
// TODO: Move to a Events file
library Events {
event HandleLinked(uint256 handleId, Token token);
event HandleUnlinked(uint256 handleId, Token token);
}
// TODO: Make upgradeable?
contract NamespaceRegistry {
address immutable LENS_HUB;
address immutable NAMESPACE;
/// 1to1 mapping for now, can be replaced to support multiple handles per profile if using mappings
/// NOTE: Using bytes32 _handleHash(Handle) and _profileHash(Profile) as keys because solidity doesn't support structs as keys.
mapping (uint256 handleId => Token token) handleToToken;
mapping (bytes32 token => uint256 handleId) profileToHandle;
modifier onlyHandleOwner(uint256 handleId, address transactionExecutor) {
if (IERC721(handle.namespaceCollection).ownerOf(handle.handleId) != transactionExecutor) {
revert Errors.NotHandleOwner();
}
_;
}
modifier onlyProfileOwner(Profile memory profile, address transactionExecutor) {
if (IERC721(profile.profileCollection).ownerOf(profile.profileId) != transactionExecutor) {
revert Errors.NotProfileOwner();
}
_;
}
modifier onlyHandleOrProfileOwner(Handle memory handle, Profile memory profile, address transactionExecutor) {
// The transaction executor must be the owner of the handle or the profile (or both).
if (!(IERC721(handle.namespace).ownerOf(handle.handleId) == transactionExecutor || IERC721(profile.lensHub).ownerOf(profile.profileId) == transactionExecutor)) {
revert Errors.NotHandleOrProfileOwner();
}
_;
}
// NOTE: We don't need whitelisting yet as we use immutable constants for the first version.
constructor(address lensHub, address namespace) {
LENS_HUB = lensHub;
NAMESPACE = namespace;
}
// NOTE: Simplified interfaces for the first version - Namespace and LensHub are constants
// TODO: Custom logic for linking/unlinking handles and profiles (modules, with bytes passed)
function linkHandleWithProfile(uint256 handleId, uint256 profileId) external {
_linkHandleWithProfile(Handle({ namespace: NAMESPACE, handleId: handleId}), Profile({ lensHub: LENS_HUB, profileId: profileId}));
}
function unlinkHandleFromProfile(uint256 handleId, uint256 profileId) external {
_unlinkHandleFromProfile(Handle({ namespace: NAMESPACE, handleId: handleId}), Profile({ lensHub: LENS_HUB, profileId: profileId}));
}
// TODO: Think of better name?
// handleToProfile(handleId)?
// resolveProfileByHandle(handleId)?
function resolveProfile(uint256 handleId) external view returns (uint256) {
return _resolveProfile(Handle({ namespace: NAMESPACE, handleId: handleId})).profileId;
}
// TODO: Same here - think of better name?
// profileToHandle(profileId)?
// resolveHandleByProfile(profileId)?
function resolveHandle(uint256 profileId) external view returns (uint256) {
return _resolveHandle(Profile({ lensHub: LENS_HUB, profileId: profileId})).handleId;
}
// Internal functions
function _resolveProfile(Handle memory handle) internal view returns (Profile storage) {
return handleToProfile[_handleHash(handle)];
}
function _resolveHandle(Profile memory profile) internal view returns (Handle storage) {
return profileToHandle[_profileHash(profile)];
}
function _linkHandleWithProfile(Handle memory handle, Profile memory profile) internal onlyProfileOwner(profile, msg.sender) onlyHandleOwner(handle, msg.sender) {
handleToProfile[_handleHash(handle)] = profile;
profileToHandle[_profileHash(profile)] = handle;
emit Events.HandleLinked(handle, profile);
}
function _unlinkHandleFromProfile(Handle memory handle, Profile memory profile) internal onlyHandleOrProfileOwner(handle, profile, msg.sender) {
delete handleToProfile[_handleHash(handle)];
delete profileToHandle[_profileHash(profile)];
emit Events.HandleUnlinked(handle, profile);
}
// Utility functions for mappings
function _handleHash(Handle memory handle) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(handle.namespace, handle.handleId));
}
function _profileHash(Profile memory profile) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(profile.lensHub, profile.profileId));
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[Upgradeable Beacon] => [Beacon Implementation]
^
|
[Lens namespace collection - aka .lens handle collection - Proxy] // Storage 1
[Lens namespace collection - aka .lens handle collection - Proxy] // Storage 2
[Lens namespace collection - aka .lens handle collection - Proxy] // Storage 3
---------------------------------------------
[Beacon] // Storage
|
V
[Generic namespace collection impl]
-----
[TransparentProxy] // Storage
|
v
[Lens namespace collection - aka .lens handle collection - Implementation]
===============

View File

@@ -4,15 +4,8 @@ pragma solidity ^0.8.19;
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
// TODO: Move to a Types
struct Handle {
address namespace;
uint256 handleId;
}
struct Profile {
address lensHub;
uint256 profileId;
struct Namespace {
}
// TODO: Move to a Errors file
@@ -36,18 +29,18 @@ contract NamespaceRegistry {
/// 1to1 mapping for now, can be replaced to support multiple handles per profile if using mappings
/// NOTE: Using bytes32 _handleHash(Handle) and _profileHash(Profile) as keys because solidity doesn't support structs as keys.
mapping (bytes32 handle => Profile profile) handleToProfile;
mapping (bytes32 profile => Handle handle) profileToHandle;
mapping (string namespace => address owner) handleToProfile;
mapping (address => Handle handle) profileToHandle;
modifier onlyHandleOwner(Handle memory handle, address transactionExecutor) {
if (IERC721(handle.namespace).ownerOf(handle.handleId) != transactionExecutor) {
if (IERC721(handle.namespaceCollection).ownerOf(handle.handleId) != transactionExecutor) {
revert Errors.NotHandleOwner();
}
_;
}
modifier onlyProfileOwner(Profile memory profile, address transactionExecutor) {
if (IERC721(profile.lensHub).ownerOf(profile.profileId) != transactionExecutor) {
if (IERC721(profile.profileCollection).ownerOf(profile.profileId) != transactionExecutor) {
revert Errors.NotProfileOwner();
}
_;