// SPDX-License-Identifier: MIT pragma solidity ^0.8.15; // Interfaces import {ILensProtocol} from 'contracts/interfaces/ILensProtocol.sol'; import {IFollowNFT} from 'contracts/interfaces/IFollowNFT.sol'; // Constants import {Types} from 'contracts/libraries/constants/Types.sol'; import {Errors} from 'contracts/libraries/constants/Errors.sol'; // Lens Hub Components import {LensHubStorage} from 'contracts/base/LensHubStorage.sol'; import {LensImplGetters} from 'contracts/base/LensImplGetters.sol'; import {LensGovernable} from 'contracts/base/LensGovernable.sol'; import {LensProfiles} from 'contracts/base/LensProfiles.sol'; import {LensHubEventHooks} from 'contracts/base/LensHubEventHooks.sol'; import {LensVersion} from 'contracts/base/LensVersion.sol'; // Libraries import {ActionLib} from 'contracts/libraries/ActionLib.sol'; import {LegacyCollectLib} from 'contracts/libraries/LegacyCollectLib.sol'; import {FollowLib} from 'contracts/libraries/FollowLib.sol'; import {MetaTxLib} from 'contracts/libraries/MetaTxLib.sol'; import {ProfileLib} from 'contracts/libraries/ProfileLib.sol'; import {PublicationLib} from 'contracts/libraries/PublicationLib.sol'; import {StorageLib} from 'contracts/libraries/StorageLib.sol'; import {ValidationLib} from 'contracts/libraries/ValidationLib.sol'; // Lens Migrations V1 to V2 import {LensV2Migration} from 'contracts/misc/LensV2Migration.sol'; /** * @title LensHub * @author Lens Protocol * * @notice This is the main entry point of the Lens Protocol. It contains governance functionality as well as * publishing and profile interaction functionality. * * NOTE: The Lens Protocol is unique in that frontend operators need to track a potentially overwhelming * number of NFT contracts and interactions at once. For that reason, we've made two quirky design decisions: * 1. Both Follow & Collect NFTs invoke a LensHub callback on transfer with the sole purpose of emitting an event. * 2. Almost every event in the protocol emits the current block timestamp, reducing the need to fetch it manually. * * @custom:upgradeable Transparent upgradeable proxy. In this version, without initializer. * See `../misc/LensHubInitializable.sol` for the initializable version. */ contract LensHub is LensProfiles, LensGovernable, LensV2Migration, LensImplGetters, LensHubEventHooks, LensHubStorage, LensVersion, ILensProtocol { modifier onlyProfileOwnerOrDelegatedExecutor(address expectedOwnerOrDelegatedExecutor, uint256 profileId) { ValidationLib.validateAddressIsProfileOwnerOrDelegatedExecutor(expectedOwnerOrDelegatedExecutor, profileId); _; } modifier whenPublishingEnabled() { if (StorageLib.getState() != Types.ProtocolState.Unpaused) { revert Errors.PublishingPaused(); } _; } constructor( address followNFTImpl, address legacyCollectNFTImpl, // We still pass the deprecated CollectNFTImpl for legacy Collects to work address moduleRegistry, uint256 tokenGuardianCooldown, Types.MigrationParams memory migrationParams ) LensV2Migration(migrationParams) LensProfiles(tokenGuardianCooldown) LensImplGetters(followNFTImpl, legacyCollectNFTImpl, moduleRegistry) {} /// @inheritdoc ILensProtocol function createProfile( Types.CreateProfileParams calldata createProfileParams ) external override whenNotPaused returns (uint256) { ValidationLib.validateProfileCreatorWhitelisted(msg.sender); unchecked { uint256 profileId = ++_profileCounter; _mint(createProfileParams.to, profileId); ProfileLib.createProfile(createProfileParams, profileId); return profileId; } } /////////////////////////////////////////// /// PROFILE OWNER FUNCTIONS /// /////////////////////////////////////////// /// @inheritdoc ILensProtocol function setProfileMetadataURI( uint256 profileId, string calldata metadataURI ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, profileId) { ProfileLib.setProfileMetadataURI(profileId, metadataURI, msg.sender); } /// @inheritdoc ILensProtocol function setProfileMetadataURIWithSig( uint256 profileId, string calldata metadataURI, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, profileId) { MetaTxLib.validateSetProfileMetadataURISignature(signature, profileId, metadataURI); ProfileLib.setProfileMetadataURI(profileId, metadataURI, signature.signer); } /// @inheritdoc ILensProtocol function setFollowModule( uint256 profileId, address followModule, bytes calldata followModuleInitData ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, profileId) { ProfileLib.setFollowModule(profileId, followModule, followModuleInitData, msg.sender); } /// @inheritdoc ILensProtocol function setFollowModuleWithSig( uint256 profileId, address followModule, bytes calldata followModuleInitData, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, profileId) { MetaTxLib.validateSetFollowModuleSignature(signature, profileId, followModule, followModuleInitData); ProfileLib.setFollowModule(profileId, followModule, followModuleInitData, signature.signer); } /// @inheritdoc ILensProtocol function changeDelegatedExecutorsConfig( uint256 delegatorProfileId, address[] calldata delegatedExecutors, bool[] calldata approvals, uint64 configNumber, bool switchToGivenConfig ) external override whenNotPaused onlyProfileOwner(msg.sender, delegatorProfileId) { ProfileLib.changeGivenDelegatedExecutorsConfig( delegatorProfileId, delegatedExecutors, approvals, configNumber, switchToGivenConfig ); } function changeDelegatedExecutorsConfig( uint256 delegatorProfileId, address[] calldata delegatedExecutors, bool[] calldata approvals ) external override whenNotPaused onlyProfileOwner(msg.sender, delegatorProfileId) { ProfileLib.changeDelegatedExecutorsConfig(delegatorProfileId, delegatedExecutors, approvals); } /// @inheritdoc ILensProtocol function changeDelegatedExecutorsConfigWithSig( uint256 delegatorProfileId, address[] calldata delegatedExecutors, bool[] calldata approvals, uint64 configNumber, bool switchToGivenConfig, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwner(signature.signer, delegatorProfileId) { MetaTxLib.validateChangeDelegatedExecutorsConfigSignature( signature, delegatorProfileId, delegatedExecutors, approvals, configNumber, switchToGivenConfig ); ProfileLib.changeGivenDelegatedExecutorsConfig( delegatorProfileId, delegatedExecutors, approvals, configNumber, switchToGivenConfig ); } //////////////////////////////////////// /// PUBLISHING FUNCTIONS /// //////////////////////////////////////// /// @inheritdoc ILensProtocol function post( Types.PostParams calldata postParams ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(msg.sender, postParams.profileId) returns (uint256) { return PublicationLib.post({postParams: postParams, transactionExecutor: msg.sender}); } /// @inheritdoc ILensProtocol function postWithSig( Types.PostParams calldata postParams, Types.EIP712Signature calldata signature ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(signature.signer, postParams.profileId) returns (uint256) { MetaTxLib.validatePostSignature(signature, postParams); return PublicationLib.post({postParams: postParams, transactionExecutor: signature.signer}); } /// @inheritdoc ILensProtocol function comment( Types.CommentParams calldata commentParams ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(msg.sender, commentParams.profileId) returns (uint256) { return PublicationLib.comment({commentParams: commentParams, transactionExecutor: msg.sender}); } /// @inheritdoc ILensProtocol function commentWithSig( Types.CommentParams calldata commentParams, Types.EIP712Signature calldata signature ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(signature.signer, commentParams.profileId) returns (uint256) { MetaTxLib.validateCommentSignature(signature, commentParams); return PublicationLib.comment({commentParams: commentParams, transactionExecutor: signature.signer}); } /// @inheritdoc ILensProtocol function mirror( Types.MirrorParams calldata mirrorParams ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(msg.sender, mirrorParams.profileId) returns (uint256) { return PublicationLib.mirror({mirrorParams: mirrorParams, transactionExecutor: msg.sender}); } /// @inheritdoc ILensProtocol function mirrorWithSig( Types.MirrorParams calldata mirrorParams, Types.EIP712Signature calldata signature ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(signature.signer, mirrorParams.profileId) returns (uint256) { MetaTxLib.validateMirrorSignature(signature, mirrorParams); return PublicationLib.mirror({mirrorParams: mirrorParams, transactionExecutor: signature.signer}); } /// @inheritdoc ILensProtocol function quote( Types.QuoteParams calldata quoteParams ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(msg.sender, quoteParams.profileId) returns (uint256) { return PublicationLib.quote({quoteParams: quoteParams, transactionExecutor: msg.sender}); } /// @inheritdoc ILensProtocol function quoteWithSig( Types.QuoteParams calldata quoteParams, Types.EIP712Signature calldata signature ) external override whenPublishingEnabled onlyProfileOwnerOrDelegatedExecutor(signature.signer, quoteParams.profileId) returns (uint256) { MetaTxLib.validateQuoteSignature(signature, quoteParams); return PublicationLib.quote({quoteParams: quoteParams, transactionExecutor: signature.signer}); } ///////////////////////////////////////////////// /// PROFILE INTERACTION FUNCTIONS /// ///////////////////////////////////////////////// /// @inheritdoc ILensProtocol function follow( uint256 followerProfileId, uint256[] calldata idsOfProfilesToFollow, uint256[] calldata followTokenIds, bytes[] calldata datas ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, followerProfileId) returns (uint256[] memory) { return FollowLib.follow({ followerProfileId: followerProfileId, idsOfProfilesToFollow: idsOfProfilesToFollow, followTokenIds: followTokenIds, followModuleDatas: datas, transactionExecutor: msg.sender }); } /// @inheritdoc ILensProtocol function followWithSig( uint256 followerProfileId, uint256[] calldata idsOfProfilesToFollow, uint256[] calldata followTokenIds, bytes[] calldata datas, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, followerProfileId) returns (uint256[] memory) { MetaTxLib.validateFollowSignature(signature, followerProfileId, idsOfProfilesToFollow, followTokenIds, datas); return FollowLib.follow({ followerProfileId: followerProfileId, idsOfProfilesToFollow: idsOfProfilesToFollow, followTokenIds: followTokenIds, followModuleDatas: datas, transactionExecutor: signature.signer }); } /// @inheritdoc ILensProtocol function unfollow( uint256 unfollowerProfileId, uint256[] calldata idsOfProfilesToUnfollow ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, unfollowerProfileId) { FollowLib.unfollow({ unfollowerProfileId: unfollowerProfileId, idsOfProfilesToUnfollow: idsOfProfilesToUnfollow, transactionExecutor: msg.sender }); } /// @inheritdoc ILensProtocol function unfollowWithSig( uint256 unfollowerProfileId, uint256[] calldata idsOfProfilesToUnfollow, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, unfollowerProfileId) { MetaTxLib.validateUnfollowSignature(signature, unfollowerProfileId, idsOfProfilesToUnfollow); FollowLib.unfollow({ unfollowerProfileId: unfollowerProfileId, idsOfProfilesToUnfollow: idsOfProfilesToUnfollow, transactionExecutor: signature.signer }); } /// @inheritdoc ILensProtocol function setBlockStatus( uint256 byProfileId, uint256[] calldata idsOfProfilesToSetBlockStatus, bool[] calldata blockStatus ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, byProfileId) { ProfileLib.setBlockStatus(byProfileId, idsOfProfilesToSetBlockStatus, blockStatus, msg.sender); } /// @inheritdoc ILensProtocol function setBlockStatusWithSig( uint256 byProfileId, uint256[] calldata idsOfProfilesToSetBlockStatus, bool[] calldata blockStatus, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, byProfileId) { MetaTxLib.validateSetBlockStatusSignature(signature, byProfileId, idsOfProfilesToSetBlockStatus, blockStatus); ProfileLib.setBlockStatus(byProfileId, idsOfProfilesToSetBlockStatus, blockStatus, signature.signer); } /// @inheritdoc ILensProtocol function collectLegacy( Types.LegacyCollectParams calldata collectParams ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, collectParams.collectorProfileId) returns (uint256) { return LegacyCollectLib.collect({ collectParams: collectParams, transactionExecutor: msg.sender, collectorProfileOwner: ownerOf(collectParams.collectorProfileId), collectNFTImpl: this.getLegacyCollectNFTImpl() }); } /// @inheritdoc ILensProtocol function collectLegacyWithSig( Types.LegacyCollectParams calldata collectParams, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, collectParams.collectorProfileId) returns (uint256) { MetaTxLib.validateLegacyCollectSignature(signature, collectParams); return LegacyCollectLib.collect({ collectParams: collectParams, transactionExecutor: signature.signer, collectorProfileOwner: ownerOf(collectParams.collectorProfileId), collectNFTImpl: this.getLegacyCollectNFTImpl() }); } /// @inheritdoc ILensProtocol function act( Types.PublicationActionParams calldata publicationActionParams ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(msg.sender, publicationActionParams.actorProfileId) returns (bytes memory) { return ActionLib.act({ publicationActionParams: publicationActionParams, transactionExecutor: msg.sender, actorProfileOwner: ownerOf(publicationActionParams.actorProfileId) }); } /// @inheritdoc ILensProtocol function actWithSig( Types.PublicationActionParams calldata publicationActionParams, Types.EIP712Signature calldata signature ) external override whenNotPaused onlyProfileOwnerOrDelegatedExecutor(signature.signer, publicationActionParams.actorProfileId) returns (bytes memory) { MetaTxLib.validateActSignature(signature, publicationActionParams); return ActionLib.act({ publicationActionParams: publicationActionParams, transactionExecutor: signature.signer, actorProfileOwner: ownerOf(publicationActionParams.actorProfileId) }); } /// @inheritdoc ILensProtocol function incrementNonce(uint8 increment) external { MetaTxLib.incrementNonce(increment); } /////////////////////////////////////////// /// EXTERNAL VIEW FUNCTIONS /// /////////////////////////////////////////// /// @inheritdoc ILensProtocol function isFollowing(uint256 followerProfileId, uint256 followedProfileId) external view returns (bool) { address followNFT = _profiles[followedProfileId].followNFT; return followNFT != address(0) && IFollowNFT(followNFT).isFollowing(followerProfileId); } /// @inheritdoc ILensProtocol function isDelegatedExecutorApproved( uint256 delegatorProfileId, address delegatedExecutor, uint64 configNumber ) external view returns (bool) { return StorageLib.getDelegatedExecutorsConfig(delegatorProfileId).isApproved[configNumber][delegatedExecutor]; } /// @inheritdoc ILensProtocol function isDelegatedExecutorApproved( uint256 delegatorProfileId, address delegatedExecutor ) external view returns (bool) { return ProfileLib.isExecutorApproved(delegatorProfileId, delegatedExecutor); } /// @inheritdoc ILensProtocol function getDelegatedExecutorsConfigNumber(uint256 delegatorProfileId) external view returns (uint64) { return StorageLib.getDelegatedExecutorsConfig(delegatorProfileId).configNumber; } /// @inheritdoc ILensProtocol function getDelegatedExecutorsPrevConfigNumber(uint256 delegatorProfileId) external view returns (uint64) { return StorageLib.getDelegatedExecutorsConfig(delegatorProfileId).prevConfigNumber; } /// @inheritdoc ILensProtocol function getDelegatedExecutorsMaxConfigNumberSet(uint256 delegatorProfileId) external view returns (uint64) { return StorageLib.getDelegatedExecutorsConfig(delegatorProfileId).maxConfigNumberSet; } /// @inheritdoc ILensProtocol function isBlocked(uint256 profileId, uint256 byProfileId) external view returns (bool) { return _blockedStatus[byProfileId][profileId]; } /// @inheritdoc ILensProtocol function getContentURI(uint256 profileId, uint256 pubId) external view override returns (string memory) { // This function is used by the Collect NFTs' tokenURI function. return PublicationLib.getContentURI(profileId, pubId); } /// @inheritdoc ILensProtocol function getProfile(uint256 profileId) external view override returns (Types.Profile memory) { return _profiles[profileId]; } /// @inheritdoc ILensProtocol function getPublication( uint256 profileId, uint256 pubId ) external pure override returns (Types.PublicationMemory memory) { return StorageLib.getPublicationMemory(profileId, pubId); } /// @inheritdoc ILensProtocol function isActionModuleEnabledInPublication( uint256 profileId, uint256 pubId, address module ) external view returns (bool) { return StorageLib.getPublication(profileId, pubId).actionModuleEnabled[module]; } /// @inheritdoc ILensProtocol function getPublicationType( uint256 profileId, uint256 pubId ) external view override returns (Types.PublicationType) { return PublicationLib.getPublicationType(profileId, pubId); } function getFollowModule(uint256 profileId) external view returns (address) { if (__DEPRECATED__collectModuleWhitelisted[msg.sender]) { // Injecting LensHub as follow module when a Lens V1 collect module is performing the call. // This is a hack to make legacy collect work when configured for followers only. return address(this); } else { return StorageLib.getProfile(profileId).followModule; } } function isFollowing( uint256 followedProfileId, address followerAddress, uint256 /* tokenId */ ) external view returns (bool) { if (__DEPRECATED__collectModuleWhitelisted[msg.sender]) { // This state was pre-filled at LegacyCollectLib and it is a hack to make legacy collect work when // configured for followers only. return _legacyCollectFollowValidationHelper[followerAddress] == followedProfileId || ProfileLib.isExecutorApproved(followedProfileId, followerAddress) || ProfileLib.ownerOf(followedProfileId) == followerAddress; } else { revert Errors.ExecutorInvalid(); } } }