mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-10 14:48:15 -05:00
412 lines
16 KiB
Solidity
412 lines
16 KiB
Solidity
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
pragma solidity 0.8.10;
|
|
|
|
import {Helpers} from './Helpers.sol';
|
|
import {DataTypes} from './DataTypes.sol';
|
|
import {Errors} from './Errors.sol';
|
|
import {Events} from './Events.sol';
|
|
import {Constants} from './Constants.sol';
|
|
import {IFollowModule} from '../interfaces/IFollowModule.sol';
|
|
import {ICollectModule} from '../interfaces/ICollectModule.sol';
|
|
import {IReferenceModule} from '../interfaces/IReferenceModule.sol';
|
|
|
|
/**
|
|
* @title PublishingLogic
|
|
* @author Lens
|
|
*
|
|
* @notice This is the library that contains the logic for profile creation & publication.
|
|
*
|
|
* @dev The functions are external, so they are called from the hub via `delegateCall` under the hood. Furthermore,
|
|
* expected events are emitted from this library instead of from the hub to alleviate code size concerns.
|
|
*/
|
|
library PublishingLogic {
|
|
/**
|
|
* @notice Executes the logic to create a profile with the given parameters to the given address.
|
|
*
|
|
* @param vars The CreateProfileData struct containing the following parameters:
|
|
* to: The address receiving the profile.
|
|
* handle: The handle to set for the profile, must be unique and non-empty.
|
|
* imageURI: The URI to set for the profile image.
|
|
* followModule: The follow module to use, can be the zero address.
|
|
* followModuleData: The follow module initialization data, if any
|
|
* followNFTURI: The URI to set for the follow NFT.
|
|
* @param profileId The profile ID to associate with this profile NFT (token ID).
|
|
* @param _profileIdByHandleHash The storage reference to the mapping of profile IDs by handle hash.
|
|
* @param _profileById The storage reference to the mapping of profile structs by IDs.
|
|
* @param _followModuleWhitelisted The storage reference to the mapping of whitelist status by follow module address.
|
|
*/
|
|
function createProfile(
|
|
DataTypes.CreateProfileData calldata vars,
|
|
uint256 profileId,
|
|
mapping(bytes32 => uint256) storage _profileIdByHandleHash,
|
|
mapping(uint256 => DataTypes.ProfileStruct) storage _profileById,
|
|
mapping(address => bool) storage _followModuleWhitelisted
|
|
) external {
|
|
_validateHandle(vars.handle);
|
|
|
|
bytes32 handleHash = keccak256(bytes(vars.handle));
|
|
|
|
if (_profileIdByHandleHash[handleHash] != 0) revert Errors.HandleTaken();
|
|
_profileIdByHandleHash[handleHash] = profileId;
|
|
|
|
_profileById[profileId].handle = vars.handle;
|
|
_profileById[profileId].imageURI = vars.imageURI;
|
|
_profileById[profileId].followNFTURI = vars.followNFTURI;
|
|
|
|
if (vars.followModule != address(0)) {
|
|
_profileById[profileId].followModule = vars.followModule;
|
|
}
|
|
|
|
bytes memory followModuleReturnData = _initFollowModule(
|
|
profileId,
|
|
vars.followModule,
|
|
vars.followModuleData,
|
|
_followModuleWhitelisted
|
|
);
|
|
|
|
_emitProfileCreated(profileId, vars, followModuleReturnData);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the follow module for a given profile.
|
|
*
|
|
* @param profileId The profile ID to set the follow module for.
|
|
* @param followModule The follow module to set for the given profile, if any.
|
|
* @param followModuleData The data to pass to the follow module for profile initialization.
|
|
* @param _profile The storage reference to the profile struct associated with the given profile ID.
|
|
* @param _followModuleWhitelisted The storage reference to the mapping of whitelist status by follow module address.
|
|
*/
|
|
function setFollowModule(
|
|
uint256 profileId,
|
|
address followModule,
|
|
bytes calldata followModuleData,
|
|
DataTypes.ProfileStruct storage _profile,
|
|
mapping(address => bool) storage _followModuleWhitelisted
|
|
) external {
|
|
address prevFollowModule = _profile.followModule;
|
|
if (followModule != prevFollowModule) {
|
|
_profile.followModule = followModule;
|
|
}
|
|
|
|
bytes memory followModuleReturnData = _initFollowModule(
|
|
profileId,
|
|
followModule,
|
|
followModuleData,
|
|
_followModuleWhitelisted
|
|
);
|
|
emit Events.FollowModuleSet(
|
|
profileId,
|
|
followModule,
|
|
followModuleReturnData,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Creates a post publication mapped to the given profile.
|
|
*
|
|
* @dev To avoid a stack too deep error, reference parameters are passed in memory rather than calldata.
|
|
*
|
|
* @param profileId The profile ID to associate this publication to.
|
|
* @param contentURI The URI to set for this publication.
|
|
* @param collectModule The collect module to set for this publication.
|
|
* @param collectModuleData The data to pass to the collect module for publication initialization.
|
|
* @param referenceModule The reference module to set for this publication, if any.
|
|
* @param referenceModuleData The data to pass to the reference module for publication initialization.
|
|
* @param pubId The publication ID to associate with this publication.
|
|
* @param _pubByIdByProfile The storage reference to the mapping of publications by publication ID by profile ID.
|
|
* @param _collectModuleWhitelisted The storage reference to the mapping of whitelist status by collect module address.
|
|
* @param _referenceModuleWhitelisted The storage reference to the mapping of whitelist status by reference module address.
|
|
*/
|
|
function createPost(
|
|
uint256 profileId,
|
|
string memory contentURI,
|
|
address collectModule,
|
|
bytes memory collectModuleData,
|
|
address referenceModule,
|
|
bytes memory referenceModuleData,
|
|
uint256 pubId,
|
|
mapping(uint256 => mapping(uint256 => DataTypes.PublicationStruct))
|
|
storage _pubByIdByProfile,
|
|
mapping(address => bool) storage _collectModuleWhitelisted,
|
|
mapping(address => bool) storage _referenceModuleWhitelisted
|
|
) external {
|
|
_pubByIdByProfile[profileId][pubId].contentURI = contentURI;
|
|
|
|
// Collect module initialization
|
|
bytes memory collectModuleReturnData = _initPubCollectModule(
|
|
profileId,
|
|
pubId,
|
|
collectModule,
|
|
collectModuleData,
|
|
_pubByIdByProfile,
|
|
_collectModuleWhitelisted
|
|
);
|
|
|
|
// Reference module initialization
|
|
bytes memory referenceModuleReturnData = _initPubReferenceModule(
|
|
profileId,
|
|
pubId,
|
|
referenceModule,
|
|
referenceModuleData,
|
|
_pubByIdByProfile,
|
|
_referenceModuleWhitelisted
|
|
);
|
|
|
|
emit Events.PostCreated(
|
|
profileId,
|
|
pubId,
|
|
contentURI,
|
|
collectModule,
|
|
collectModuleReturnData,
|
|
referenceModule,
|
|
referenceModuleReturnData,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Creates a comment publication mapped to the given profile.
|
|
*
|
|
* @dev This function is unique in that it requires many variables, so, unlike the other publishing functions,
|
|
* we need to pass the full CommentData struct in memory to avoid a stack too deep error.
|
|
*
|
|
* @param vars The CommentData struct to use to create the comment.
|
|
* @param pubId The publication ID to associate with this publication.
|
|
* @param _profileById The storage reference to the mapping of profile structs by IDs.
|
|
* @param _pubByIdByProfile The storage reference to the mapping of publications by publication ID by profile ID.
|
|
* @param _collectModuleWhitelisted The storage reference to the mapping of whitelist status by collect module address.
|
|
* @param _referenceModuleWhitelisted The storage reference to the mapping of whitelist status by reference module address.
|
|
*/
|
|
function createComment(
|
|
DataTypes.CommentData memory vars,
|
|
uint256 pubId,
|
|
mapping(uint256 => DataTypes.ProfileStruct) storage _profileById,
|
|
mapping(uint256 => mapping(uint256 => DataTypes.PublicationStruct))
|
|
storage _pubByIdByProfile,
|
|
mapping(address => bool) storage _collectModuleWhitelisted,
|
|
mapping(address => bool) storage _referenceModuleWhitelisted
|
|
) external {
|
|
// Validate existence of the pointed publication
|
|
uint256 pubCount = _profileById[vars.profileIdPointed].pubCount;
|
|
if (pubCount < vars.pubIdPointed || vars.pubIdPointed == 0)
|
|
revert Errors.PublicationDoesNotExist();
|
|
|
|
_pubByIdByProfile[vars.profileId][pubId].contentURI = vars.contentURI;
|
|
_pubByIdByProfile[vars.profileId][pubId].profileIdPointed = vars.profileIdPointed;
|
|
_pubByIdByProfile[vars.profileId][pubId].pubIdPointed = vars.pubIdPointed;
|
|
|
|
// Collect Module Initialization
|
|
bytes memory collectModuleReturnData = _initPubCollectModule(
|
|
vars.profileId,
|
|
pubId,
|
|
vars.collectModule,
|
|
vars.collectModuleData,
|
|
_pubByIdByProfile,
|
|
_collectModuleWhitelisted
|
|
);
|
|
|
|
// Reference module initialization
|
|
bytes memory referenceModuleReturnData = _initPubReferenceModule(
|
|
vars.profileId,
|
|
pubId,
|
|
vars.referenceModule,
|
|
vars.referenceModuleData,
|
|
_pubByIdByProfile,
|
|
_referenceModuleWhitelisted
|
|
);
|
|
|
|
// Reference module validation
|
|
address refModule = _pubByIdByProfile[vars.profileIdPointed][vars.pubIdPointed]
|
|
.referenceModule;
|
|
if (refModule != address(0)) {
|
|
IReferenceModule(refModule).processComment(
|
|
vars.profileId,
|
|
vars.profileIdPointed,
|
|
vars.pubIdPointed
|
|
);
|
|
}
|
|
|
|
// Prevents a stack too deep error
|
|
_emitCommentCreated(vars, pubId, collectModuleReturnData, referenceModuleReturnData);
|
|
}
|
|
|
|
/**
|
|
* @notice Creates a mirror publication mapped to the given profile.
|
|
*
|
|
* @param profileId The profile ID to associate this publication to.
|
|
* @param profileIdPointed The profile ID of the pointed publication's publisher.
|
|
* @param pubIdPointed The pointed publication's publication ID.
|
|
* @param referenceModule The reference module to set for this publication, if any.
|
|
* @param referenceModuleData The data to pass to the reference module for publication initialization.
|
|
* @param pubId The publication ID to associate with this publication.
|
|
* @param _pubByIdByProfile The storage reference to the mapping of publications by publication ID by profile ID.
|
|
* @param _referenceModuleWhitelisted The storage reference to the mapping of whitelist status by reference module address.
|
|
*/
|
|
function createMirror(
|
|
uint256 profileId,
|
|
uint256 profileIdPointed,
|
|
uint256 pubIdPointed,
|
|
address referenceModule,
|
|
bytes calldata referenceModuleData,
|
|
uint256 pubId,
|
|
mapping(uint256 => mapping(uint256 => DataTypes.PublicationStruct))
|
|
storage _pubByIdByProfile,
|
|
mapping(address => bool) storage _referenceModuleWhitelisted
|
|
) external {
|
|
(uint256 rootProfileIdPointed, uint256 rootPubIdPointed, ) = Helpers.getPointedIfMirror(
|
|
profileIdPointed,
|
|
pubIdPointed,
|
|
_pubByIdByProfile
|
|
);
|
|
|
|
_pubByIdByProfile[profileId][pubId].profileIdPointed = rootProfileIdPointed;
|
|
_pubByIdByProfile[profileId][pubId].pubIdPointed = rootPubIdPointed;
|
|
|
|
// Reference module initialization
|
|
bytes memory referenceModuleReturnData = _initPubReferenceModule(
|
|
profileId,
|
|
pubId,
|
|
referenceModule,
|
|
referenceModuleData,
|
|
_pubByIdByProfile,
|
|
_referenceModuleWhitelisted
|
|
);
|
|
|
|
// Reference module validation
|
|
address refModule = _pubByIdByProfile[rootProfileIdPointed][rootPubIdPointed]
|
|
.referenceModule;
|
|
if (refModule != address(0)) {
|
|
IReferenceModule(refModule).processMirror(
|
|
profileId,
|
|
rootProfileIdPointed,
|
|
rootPubIdPointed
|
|
);
|
|
}
|
|
|
|
emit Events.MirrorCreated(
|
|
profileId,
|
|
pubId,
|
|
rootProfileIdPointed,
|
|
rootPubIdPointed,
|
|
referenceModule,
|
|
referenceModuleReturnData,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
function _initPubCollectModule(
|
|
uint256 profileId,
|
|
uint256 pubId,
|
|
address collectModule,
|
|
bytes memory collectModuleData,
|
|
mapping(uint256 => mapping(uint256 => DataTypes.PublicationStruct))
|
|
storage _pubByIdByProfile,
|
|
mapping(address => bool) storage _collectModuleWhitelisted
|
|
) private returns (bytes memory) {
|
|
if (!_collectModuleWhitelisted[collectModule]) revert Errors.CollectModuleNotWhitelisted();
|
|
_pubByIdByProfile[profileId][pubId].collectModule = collectModule;
|
|
return
|
|
ICollectModule(collectModule).initializePublicationCollectModule(
|
|
profileId,
|
|
pubId,
|
|
collectModuleData
|
|
);
|
|
}
|
|
|
|
function _initPubReferenceModule(
|
|
uint256 profileId,
|
|
uint256 pubId,
|
|
address referenceModule,
|
|
bytes memory referenceModuleData,
|
|
mapping(uint256 => mapping(uint256 => DataTypes.PublicationStruct))
|
|
storage _pubByIdByProfile,
|
|
mapping(address => bool) storage _referenceModuleWhitelisted
|
|
) private returns (bytes memory) {
|
|
if (referenceModule != address(0)) {
|
|
if (!_referenceModuleWhitelisted[referenceModule])
|
|
revert Errors.ReferenceModuleNotWhitelisted();
|
|
_pubByIdByProfile[profileId][pubId].referenceModule = referenceModule;
|
|
return
|
|
IReferenceModule(referenceModule).initializeReferenceModule(
|
|
profileId,
|
|
pubId,
|
|
referenceModuleData
|
|
);
|
|
} else {
|
|
return new bytes(0);
|
|
}
|
|
}
|
|
|
|
function _initFollowModule(
|
|
uint256 profileId,
|
|
address followModule,
|
|
bytes memory followModuleData,
|
|
mapping(address => bool) storage _followModuleWhitelisted
|
|
) private returns (bytes memory) {
|
|
if (followModule != address(0)) {
|
|
if (!_followModuleWhitelisted[followModule]) revert Errors.FollowModuleNotWhitelisted();
|
|
bytes memory returnData = IFollowModule(followModule).initializeFollowModule(
|
|
profileId,
|
|
followModuleData
|
|
);
|
|
return returnData;
|
|
} else {
|
|
return new bytes(0);
|
|
}
|
|
}
|
|
|
|
function _emitCommentCreated(
|
|
DataTypes.CommentData memory vars,
|
|
uint256 pubId,
|
|
bytes memory collectModuleReturnData,
|
|
bytes memory referenceModuleReturnData
|
|
) private {
|
|
emit Events.CommentCreated(
|
|
vars.profileId,
|
|
pubId,
|
|
vars.contentURI,
|
|
vars.profileIdPointed,
|
|
vars.pubIdPointed,
|
|
vars.collectModule,
|
|
collectModuleReturnData,
|
|
vars.referenceModule,
|
|
referenceModuleReturnData,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
function _emitProfileCreated(
|
|
uint256 profileId,
|
|
DataTypes.CreateProfileData calldata vars,
|
|
bytes memory followModuleReturnData
|
|
) internal {
|
|
emit Events.ProfileCreated(
|
|
profileId,
|
|
msg.sender, // Creator is always the msg sender
|
|
vars.to,
|
|
vars.handle,
|
|
vars.imageURI,
|
|
vars.followModule,
|
|
followModuleReturnData,
|
|
vars.followNFTURI,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
function _validateHandle(string calldata handle) private pure {
|
|
bytes memory byteHandle = bytes(handle);
|
|
if (byteHandle.length == 0 || byteHandle.length > Constants.MAX_HANDLE_LENGTH)
|
|
revert Errors.HandleLengthInvalid();
|
|
|
|
for (uint256 i = 0; i < byteHandle.length; i++) {
|
|
if (
|
|
(byteHandle[i] < '0' ||
|
|
byteHandle[i] > 'z' ||
|
|
(byteHandle[i] > '9' && byteHandle[i] < 'a')) && byteHandle[i] != '.'
|
|
) revert Errors.HandleContainsInvalidCharacters();
|
|
}
|
|
}
|
|
}
|