feat: Modifications required for migration of Profiles

Co-authored-by: Alan <donosonaumczuk@gmail.com>
This commit is contained in:
vicnaum
2023-03-02 19:46:45 +01:00
parent a890a81480
commit 6e394ba7e0
9 changed files with 175 additions and 22 deletions

View File

@@ -22,6 +22,10 @@ import {StorageLib} from 'contracts/libraries/StorageLib.sol';
import {FollowLib} from 'contracts/libraries/FollowLib.sol';
import {CollectLib} from 'contracts/libraries/CollectLib.sol';
///////////////////////////////////// Migration imports ////////////////////////////////////
import {LensHandles} from 'contracts/misc/namespaces/LensHandles.sol';
import {TokenHandleRegistry} from 'contracts/misc/namespaces/TokenHandleRegistry.sol';
/**
* @title LensHub
* @author Lens Protocol
@@ -41,6 +45,13 @@ contract LensHub is LensBaseERC721, VersionedInitializable, LensMultiState, Lens
address internal immutable FOLLOW_NFT_IMPL;
address internal immutable COLLECT_NFT_IMPL;
///////////////////////////////////// Migration constants ////////////////////////////////////
uint256 internal constant LENS_PROTOCOL_PROFILE_ID = 1;
address internal immutable migrator;
LensHandles internal immutable lensHandles;
TokenHandleRegistry internal immutable tokenHandleRegistry;
///////////////////////////////// End of migration constants /////////////////////////////////
/**
* @dev This modifier reverts if the caller is not the configured governance address.
*/
@@ -55,11 +66,20 @@ contract LensHub is LensBaseERC721, VersionedInitializable, LensMultiState, Lens
* @param followNFTImpl The follow NFT implementation address.
* @param collectNFTImpl The collect NFT implementation address.
*/
constructor(address followNFTImpl, address collectNFTImpl) {
constructor(
address followNFTImpl,
address collectNFTImpl,
address migratorAddress,
address lensHandlesAddress,
address tokenHandleRegistryAddress
) {
if (followNFTImpl == address(0)) revert Errors.InitParamsInvalid();
if (collectNFTImpl == address(0)) revert Errors.InitParamsInvalid();
FOLLOW_NFT_IMPL = followNFTImpl;
COLLECT_NFT_IMPL = collectNFTImpl;
migrator = migratorAddress;
lensHandles = LensHandles(lensHandlesAddress);
tokenHandleRegistry = TokenHandleRegistry(tokenHandleRegistryAddress);
}
/// @inheritdoc ILensHub
@@ -120,6 +140,49 @@ contract LensHub is LensBaseERC721, VersionedInitializable, LensMultiState, Lens
emit Events.CollectModuleWhitelisted(collectModule, whitelist, block.timestamp);
}
///////////////////////////////////////////
/// V1->V2 MIGRATION FUNCTIONS ///
///////////////////////////////////////////
function migrateProfile(uint256 profileId, bytes32 handleHash) external {
require(msg.sender == migrator, 'Only migrator');
delete _profileById[profileId].handleDeprecated;
delete _profileIdByHandleHash[handleHash];
}
event ProfileMigrated(uint256 profileId, address profileDestination, string handle, uint256 handleId);
function _migrateProfilePublic(uint256 profileId) internal {
address profileOwner = StorageLib.getTokenData(profileId).owner;
if (profileOwner != address(0)) {
string memory handle = _profileById[profileId].handleDeprecated;
bytes32 handleHash = keccak256(bytes(handle));
// "lensprotocol" is the only edge case without .lens suffix:
if (profileId != LENS_PROTOCOL_PROFILE_ID) {
assembly {
let handle_length := mload(handle)
mstore(handle, sub(handle_length, 5)) // Cut 5 chars (.lens) from the end
}
}
delete _profileById[profileId].handleDeprecated;
delete _profileIdByHandleHash[handleHash];
uint256 handleId = lensHandles.mintHandle(profileOwner, handle);
tokenHandleRegistry.migrationLinkHandleWithToken(handleId, profileId);
emit ProfileMigrated(profileId, profileOwner, handle, handleId);
}
}
function batchMigrateProfiles(uint256[] calldata profileIds) external {
for (uint256 i = 0; i < profileIds.length; i++) {
_migrateProfilePublic(profileIds[i]);
}
}
///////////////////////////////////////////
/// END OF V1->V2 MIGRATION FUNCTIONS ///
///////////////////////////////////////////
///////////////////////////////////////////
/// PROFILE OWNER FUNCTIONS ///
///////////////////////////////////////////

View File

@@ -74,8 +74,8 @@ library Types {
* @param signer The address of the signer.
* @param v The signature's recovery parameter.
* @param r The signature's r parameter.
* @param s The signature's s parameter
* @param deadline The signature's deadline
* @param s The signature's s parameter.
* @param deadline The signature's deadline.
*/
struct EIP712Signature {
address signer;
@@ -90,8 +90,8 @@ library Types {
*
* @param pubCount The number of publications made to this profile.
* @param followModule The address of the current follow module in use by this profile, can be empty.
* @param followNFT The address of the followNFT associated with this profile, can be empty..
* @param handleDeprecated The deprecated handle slot, no longer used. .
* @param followNFT The address of the followNFT associated with this profile, can be empty.
* @param handleDeprecated The deprecated handle slot, no longer used.
* @param imageURI The URI to be used for the profile's image.
* @param followNFTURI The URI to be used for the follow NFT.
*/

View File

@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {LensHub} from 'contracts/LensHub.sol';
import {LensHandles} from 'contracts/misc/namespaces/LensHandles.sol';
import {TokenHandleRegistry} from 'contracts/misc/namespaces/TokenHandleRegistry.sol';
contract ProfileMigration is Ownable {
LensHub public immutable lensHub;
LensHandles public immutable lensHandles;
TokenHandleRegistry public immutable tokenHandleRegistry;
event ProfileMigrated(uint256 profileId, address profileDestination, string handle, uint256 handleId);
constructor(
address ownerAddress,
address lensHubAddress,
address lensHandlesAddress,
address tokenHandleRegistryAddress
) {
Ownable._transferOwnership(ownerAddress);
lensHub = LensHub(lensHubAddress);
lensHandles = LensHandles(lensHandlesAddress);
tokenHandleRegistry = TokenHandleRegistry(tokenHandleRegistryAddress);
}
struct ProfileMigrationData {
uint256 profileId;
address profileDestination;
string handle;
bytes32 handleHash;
}
// TODO: Assume we pause everything - creating, transfer, etc.
function _migrateProfile(ProfileMigrationData calldata profileMigrationData) internal {
lensHub.migrateProfile(profileMigrationData.profileId, profileMigrationData.handleHash);
uint256 handleId = lensHandles.mintHandle(profileMigrationData.profileDestination, profileMigrationData.handle);
tokenHandleRegistry.migrationLinkHandleWithToken(handleId, profileMigrationData.profileId);
emit ProfileMigrated(
profileMigrationData.profileId,
profileMigrationData.profileDestination,
profileMigrationData.handle,
handleId
);
}
function batchMigrateProfiles(ProfileMigrationData[] calldata profileMigrationDatas) external onlyOwner {
for (uint256 i = 0; i < profileMigrationDatas.length; i++) {
_migrateProfile(profileMigrationDatas[i]);
}
}
}

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ImmutableOwnable {
address immutable OWNER;
address immutable LENS_HUB;
error OnlyOwner();
error OnlyOwnerOrHub();
modifier onlyOwner() {
if (msg.sender != OWNER) {
revert OnlyOwner();
}
_;
}
modifier onlyOwnerOrHub() {
if (msg.sender != OWNER && msg.sender != LENS_HUB) {
revert OnlyOwnerOrHub();
}
_;
}
constructor(address owner, address lensHub) {
OWNER = owner;
LENS_HUB = lensHub;
}
}

View File

@@ -3,27 +3,21 @@
pragma solidity ^0.8.19;
import {ERC721} from '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {VersionedInitializable} from 'contracts/base/upgradeability/VersionedInitializable.sol';
import {ImmutableOwnable} from 'contracts/misc/migrations/ImmutableOwnable.sol';
library Events {
event HandleMinted(string handle, string namespace, uint256 handleId, address to);
}
// TODO list:
// 1. Code a contract that can batch-mint those handles
// 2. Code a contract that can batch-link handles to profiles
contract LensHandles is ERC721, Ownable, VersionedInitializable {
contract LensHandles is ERC721, VersionedInitializable, ImmutableOwnable {
// Constant for upgradeability purposes, see VersionedInitializable. Do not confuse with EIP-712 revision number.
uint256 internal constant REVISION = 1;
string constant NAMESPACE = 'lens';
bytes32 constant NAMESPACE_HASH = keccak256(bytes(NAMESPACE));
constructor(address owner) ERC721('', '') {
Ownable._transferOwnership(owner);
}
constructor(address owner, address lensHub) ERC721('', '') ImmutableOwnable(owner, lensHub) {}
function name() public pure override returns (string memory) {
return string.concat(symbol(), ' Handles');
@@ -33,9 +27,7 @@ contract LensHandles is ERC721, Ownable, VersionedInitializable {
return string.concat('.', NAMESPACE);
}
function initialize(address owner) external initializer {
Ownable._transferOwnership(owner);
}
function initialize() external initializer {}
/**
* @notice Mints a handle in the given namespace.
@@ -45,7 +37,7 @@ contract LensHandles is ERC721, Ownable, VersionedInitializable {
* @param to The address where the handle is being minted to.
* @param localName The local name of the handle.
*/
function mintHandle(address to, string calldata localName) external onlyOwner returns (uint256) {
function mintHandle(address to, string calldata localName) external onlyOwnerOrHub returns (uint256) {
bytes32 localNameHash = keccak256(bytes(localName));
bytes32 handleHash = keccak256(abi.encodePacked(localNameHash, NAMESPACE_HASH));
uint256 handleId = uint256(handleHash);

View File

@@ -43,6 +43,9 @@ contract TokenHandleRegistry is VersionedInitializable {
address immutable LENS_HUB;
address immutable LENS_HANDLES;
// Migration constants
address immutable migrator;
/// 1to1 mapping for now, can be replaced to support multiple handles per token if using mappings
/// NOTE: Using bytes32 _handleHash(Handle) and _tokenHash(Token) as keys because solidity doesn't support structs as keys.
mapping(bytes32 handle => Token token) handleToToken;
@@ -78,13 +81,24 @@ contract TokenHandleRegistry is VersionedInitializable {
}
// NOTE: We don't need whitelisting yet as we use immutable constants for the first version.
constructor(address lensHub, address lensHandles) {
constructor(address lensHub, address lensHandles, address migratorAddress) {
LENS_HUB = lensHub;
LENS_HANDLES = lensHandles;
migrator = migratorAddress;
}
function initialize() external initializer {}
// V1->V2 Migration function
function migrationLinkHandleWithToken(uint256 handleId, uint256 tokenId) external {
require(msg.sender == migrator, 'Only migrator');
Handle memory handle = Handle({collection: LENS_HANDLES, id: handleId});
Token memory token = Token({collection: LENS_HUB, id: tokenId});
handleToToken[_handleHash(handle)] = token;
tokenToHandle[_tokenHash(token)] = handle;
emit Events.HandleLinked(handle, token);
}
// NOTE: Simplified interfaces for the first version - Namespace and LensHub are constants
// TODO: Custom logic for linking/unlinking handles and tokens (modules, with bytes passed)
function linkHandleWithToken(uint256 handleId, uint256 tokenId) external {

View File

@@ -39,7 +39,7 @@ contract EventTest is BaseTest {
hubProxyAddr = predictContractAddress(deployer, 3);
// Deploy implementation contracts.
hubImpl = new LensHub(followNFTAddr, collectNFTAddr);
hubImpl = new LensHub(followNFTAddr, collectNFTAddr, address(0), address(0), address(0));
followNFT = new FollowNFT(hubProxyAddr);
collectNFT = new CollectNFT(hubProxyAddr);

View File

@@ -168,7 +168,7 @@ contract TestSetup is Test, ForkManagement, ArrayHelpers {
hubProxyAddr = computeCreateAddress(deployer, 3);
// Deploy implementation contracts.
hubImpl = new LensHub(followNFTAddr, collectNFTAddr);
hubImpl = new LensHub(followNFTAddr, collectNFTAddr, address(0), address(0), address(0));
followNFT = new FollowNFT(hubProxyAddr);
collectNFT = new CollectNFT(hubProxyAddr);

View File

@@ -355,7 +355,7 @@ contract UpgradeForkTest is BaseTest {
address collectNFTAddr = computeCreateAddress(deployer, 2);
// Deploy implementation contracts.
hubImpl = new LensHub(followNFTAddr, collectNFTAddr);
hubImpl = new LensHub(followNFTAddr, collectNFTAddr, address(0), address(0), address(0));
followNFT = new FollowNFT(hubProxyAddr);
collectNFT = new CollectNFT(hubProxyAddr);