mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
337 lines
13 KiB
Solidity
337 lines
13 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity 0.8.15;
|
|
|
|
import {GeneralHelpers} from './helpers/GeneralHelpers.sol';
|
|
import {MetaTxHelpers} from './helpers/MetaTxHelpers.sol';
|
|
import {DataTypes} from './DataTypes.sol';
|
|
import {Errors} from './Errors.sol';
|
|
import {Events} from './Events.sol';
|
|
import {IFollowModule} from '../interfaces/IFollowModule.sol';
|
|
import './Constants.sol';
|
|
|
|
library ProfileLib {
|
|
/**
|
|
* @notice Creates a profile with the given parameters to the given address. Minting happens
|
|
* in the hub.
|
|
*
|
|
* @param vars The CreateProfileData struct containing the following parameters:
|
|
* to: The address receiving the profile.
|
|
* imageURI: The URI to set for the profile image.
|
|
* followModule: The follow module to use, can be the zero address.
|
|
* followModuleInitData: 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).
|
|
*/
|
|
function createProfile(DataTypes.CreateProfileData calldata vars, uint256 profileId) external {
|
|
_validateProfileCreatorWhitelisted();
|
|
|
|
if (bytes(vars.imageURI).length > MAX_PROFILE_IMAGE_URI_LENGTH)
|
|
revert Errors.ProfileImageURILengthInvalid();
|
|
|
|
_setProfileString(profileId, PROFILE_IMAGE_URI_OFFSET, vars.imageURI);
|
|
_setProfileString(profileId, PROFILE_FOLLOW_NFT_URI_OFFSET, vars.followNFTURI);
|
|
|
|
bytes memory followModuleReturnData;
|
|
if (vars.followModule != address(0)) {
|
|
// Load the follow module to be used in the next assembly block.
|
|
address followModule = vars.followModule;
|
|
|
|
// Store the follow module for the new profile. We opt not to use the
|
|
// _setFollowModule() private function to avoid unnecessary checks.
|
|
assembly {
|
|
mstore(0, profileId)
|
|
mstore(32, PROFILE_BY_ID_MAPPING_SLOT)
|
|
let slot := add(keccak256(0, 64), PROFILE_FOLLOW_MODULE_OFFSET)
|
|
sstore(slot, followModule)
|
|
}
|
|
|
|
// @note We don't need to check for deprecated modules here because deprecated modules
|
|
// are no longer whitelisted.
|
|
// Initialize the follow module.
|
|
followModuleReturnData = _initFollowModule(
|
|
profileId,
|
|
vars.to,
|
|
vars.followModule,
|
|
vars.followModuleInitData
|
|
);
|
|
}
|
|
emit Events.ProfileCreated(
|
|
profileId,
|
|
msg.sender,
|
|
vars.to,
|
|
vars.imageURI,
|
|
vars.followModule,
|
|
followModuleReturnData,
|
|
vars.followNFTURI,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the profile image URI for a given profile.
|
|
*
|
|
* @param profileId The profile ID.
|
|
* @param imageURI The image URI to set.
|
|
|
|
*/
|
|
function setProfileImageURI(uint256 profileId, string calldata imageURI) external {
|
|
GeneralHelpers.validateAddressIsProfileOwnerOrDelegatedExecutor(msg.sender, profileId);
|
|
_setProfileImageURI(profileId, imageURI);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the profile image URI via signature for a given profile.
|
|
*
|
|
* @param vars the SetProfileImageURIWithSigData struct containing the relevant parameters.
|
|
*/
|
|
function setProfileImageURIWithSig(DataTypes.SetProfileImageURIWithSigData calldata vars)
|
|
external
|
|
{
|
|
address signer = GeneralHelpers.getOriginatorOrDelegatedExecutorSigner(
|
|
vars.profileId,
|
|
vars.delegatedSigner
|
|
);
|
|
MetaTxHelpers.baseSetProfileImageURIWithSig(signer, vars);
|
|
_setProfileImageURI(vars.profileId, vars.imageURI);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the follow NFT URI for a given profile.
|
|
*
|
|
* @param profileId The profile ID.
|
|
* @param followNFTURI The follow NFT URI to set.
|
|
*/
|
|
function setFollowNFTURI(uint256 profileId, string calldata followNFTURI) external {
|
|
GeneralHelpers.validateAddressIsProfileOwnerOrDelegatedExecutor(msg.sender, profileId);
|
|
_setFollowNFTURI(profileId, followNFTURI);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the follow NFT URI via signature for a given profile.
|
|
*
|
|
* @param vars the SetFollowNFTURIWithSigData struct containing the relevant parameters.
|
|
*/
|
|
function setFollowNFTURIWithSig(DataTypes.SetFollowNFTURIWithSigData calldata vars) external {
|
|
address signer = GeneralHelpers.getOriginatorOrDelegatedExecutorSigner(
|
|
vars.profileId,
|
|
vars.delegatedSigner
|
|
);
|
|
MetaTxHelpers.baseSetFollowNFTURIWithSig(signer, vars);
|
|
_setFollowNFTURI(vars.profileId, vars.followNFTURI);
|
|
}
|
|
|
|
/**
|
|
* @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 followModuleInitData The data to pass to the follow module for profile initialization.
|
|
*/
|
|
function setFollowModule(
|
|
uint256 profileId,
|
|
address followModule,
|
|
bytes calldata followModuleInitData
|
|
) external {
|
|
GeneralHelpers.validateAddressIsProfileOwnerOrDelegatedExecutor(msg.sender, profileId);
|
|
_setFollowModule(profileId, msg.sender, followModule, followModuleInitData);
|
|
}
|
|
|
|
/**
|
|
* @notice sets the follow module via signature for a given profile.
|
|
*
|
|
* @param vars the SetFollowModuleWithSigData struct containing the relevant parameters.
|
|
*/
|
|
function setFollowModuleWithSig(DataTypes.SetFollowModuleWithSigData calldata vars) external {
|
|
address signer = GeneralHelpers.getOriginatorOrDelegatedExecutorSigner(
|
|
vars.profileId,
|
|
vars.delegatedSigner
|
|
);
|
|
MetaTxHelpers.baseSetFollowModuleWithSig(signer, vars);
|
|
_setFollowModule(vars.profileId, signer, vars.followModule, vars.followModuleInitData);
|
|
}
|
|
|
|
function setProfileMetadataURI(uint256 profileId, string calldata metadataURI) external {
|
|
GeneralHelpers.validateAddressIsProfileOwnerOrDelegatedExecutor(msg.sender, profileId);
|
|
_setProfileMetadataURI(profileId, metadataURI);
|
|
}
|
|
|
|
function setProfileMetadataURIWithSig(DataTypes.SetProfileMetadataURIWithSigData calldata vars)
|
|
external
|
|
{
|
|
address signer = GeneralHelpers.getOriginatorOrDelegatedExecutorSigner(
|
|
vars.profileId,
|
|
vars.delegatedSigner
|
|
);
|
|
MetaTxHelpers.baseSetProfileMetadataURIWithSig(signer, vars);
|
|
_setProfileMetadataURI(vars.profileId, vars.metadataURI);
|
|
}
|
|
|
|
function _setProfileString(
|
|
uint256 profileId,
|
|
uint256 profileOffset,
|
|
string calldata value
|
|
) private {
|
|
assembly {
|
|
let length := value.length
|
|
let cdOffset := value.offset
|
|
mstore(0, profileId)
|
|
mstore(32, PROFILE_BY_ID_MAPPING_SLOT)
|
|
let slot := add(keccak256(0, 64), profileOffset)
|
|
|
|
// If the length is greater than 31, storage rules are different.
|
|
switch gt(length, 31)
|
|
case 1 {
|
|
// The length is > 31, so we need to store the actual string in a new slot,
|
|
// equivalent to keccak256(startSlot), and store length*2+1 in startSlot.
|
|
sstore(slot, add(shl(1, length), 1))
|
|
|
|
// Calculate the amount of storage slots we need to store the full string.
|
|
// This is equivalent to (string.length + 31)/32.
|
|
let totalStorageSlots := shr(5, add(length, 31))
|
|
|
|
// Compute the slot where the actual string will begin, which is the keccak256
|
|
// hash of the slot where we stored the modified length.
|
|
mstore(0, slot)
|
|
slot := keccak256(0, 32)
|
|
|
|
// Write the actual string to storage starting at the computed slot.
|
|
// prettier-ignore
|
|
for { let i := 0 } lt(i, totalStorageSlots) { i := add(i, 1) } {
|
|
sstore(add(slot, i), calldataload(add(cdOffset, mul(32, i))))
|
|
}
|
|
}
|
|
default {
|
|
// The length is <= 31 so store the string and the length*2 in the same slot.
|
|
sstore(slot, or(calldataload(cdOffset), shl(1, length)))
|
|
}
|
|
}
|
|
}
|
|
|
|
function _setProfileMetadataURI(uint256 profileId, string calldata metadataURI) private {
|
|
assembly {
|
|
let length := metadataURI.length
|
|
let cdOffset := metadataURI.offset
|
|
mstore(0, profileId)
|
|
mstore(32, PROFILE_METADATA_MAPPING_SLOT)
|
|
let slot := keccak256(0, 64)
|
|
|
|
// If the length is greater than 31, storage rules are different.
|
|
switch gt(length, 31)
|
|
case 1 {
|
|
// The length is > 31, so we need to store the actual string in a new slot,
|
|
// equivalent to keccak256(startSlot), and store length*2+1 in startSlot.
|
|
sstore(slot, add(shl(1, length), 1))
|
|
|
|
// Calculate the amount of storage slots we need to store the full string.
|
|
// This is equivalent to (string.length + 31)/32.
|
|
let totalStorageSlots := shr(5, add(length, 31))
|
|
|
|
// Compute the slot where the actual string will begin, which is the keccak256
|
|
// hash of the slot where we stored the modified length.
|
|
mstore(0, slot)
|
|
slot := keccak256(0, 32)
|
|
|
|
// Write the actual string to storage starting at the computed slot.
|
|
// prettier-ignore
|
|
for { let i := 0 } lt(i, totalStorageSlots) { i := add(i, 1) } {
|
|
sstore(add(slot, i), calldataload(add(cdOffset, mul(32, i))))
|
|
}
|
|
}
|
|
default {
|
|
// The length is <= 31 so store the string and the length*2 in the same slot.
|
|
sstore(slot, or(calldataload(cdOffset), shl(1, length)))
|
|
}
|
|
}
|
|
emit Events.ProfileMetadataSet(profileId, metadataURI, block.timestamp);
|
|
}
|
|
|
|
function _setFollowModule(
|
|
uint256 profileId,
|
|
address executor,
|
|
address followModule,
|
|
bytes calldata followModuleInitData
|
|
) private {
|
|
// Store the follow module in the appropriate slot for the given profile ID, but
|
|
// only if it is not the same as the previous follow module.
|
|
assembly {
|
|
mstore(0, profileId)
|
|
mstore(32, PROFILE_BY_ID_MAPPING_SLOT)
|
|
let slot := add(keccak256(0, 64), PROFILE_FOLLOW_MODULE_OFFSET)
|
|
let currentFollowModule := sload(slot)
|
|
if iszero(eq(followModule, currentFollowModule)) {
|
|
sstore(slot, followModule)
|
|
}
|
|
}
|
|
|
|
// Initialize the follow module if it is non-zero.
|
|
bytes memory followModuleReturnData;
|
|
if (followModule != address(0))
|
|
followModuleReturnData = _initFollowModule(
|
|
profileId,
|
|
executor,
|
|
followModule,
|
|
followModuleInitData
|
|
);
|
|
emit Events.FollowModuleSet(
|
|
profileId,
|
|
followModule,
|
|
followModuleReturnData,
|
|
block.timestamp
|
|
);
|
|
}
|
|
|
|
function _initFollowModule(
|
|
uint256 profileId,
|
|
address executor,
|
|
address followModule,
|
|
bytes memory followModuleInitData
|
|
) private returns (bytes memory) {
|
|
_validateFollowModuleWhitelisted(followModule);
|
|
return
|
|
IFollowModule(followModule).initializeFollowModule(
|
|
profileId,
|
|
executor,
|
|
followModuleInitData
|
|
);
|
|
}
|
|
|
|
function _setProfileImageURI(uint256 profileId, string calldata imageURI) private {
|
|
if (bytes(imageURI).length > MAX_PROFILE_IMAGE_URI_LENGTH)
|
|
revert Errors.ProfileImageURILengthInvalid();
|
|
_setProfileString(profileId, PROFILE_IMAGE_URI_OFFSET, imageURI);
|
|
emit Events.ProfileImageURISet(profileId, imageURI, block.timestamp);
|
|
}
|
|
|
|
function _setFollowNFTURI(uint256 profileId, string calldata followNFTURI) private {
|
|
_setProfileString(profileId, PROFILE_FOLLOW_NFT_URI_OFFSET, followNFTURI);
|
|
emit Events.FollowNFTURISet(profileId, followNFTURI, block.timestamp);
|
|
}
|
|
|
|
function _validateFollowModuleWhitelisted(address followModule) private view {
|
|
bool whitelist;
|
|
|
|
// Load whether the given follow module is whitelisted.
|
|
assembly {
|
|
mstore(0, followModule)
|
|
mstore(32, FOLLOW_MODULE_WHITELIST_MAPPING_SLOT)
|
|
let slot := keccak256(0, 64)
|
|
whitelist := sload(slot)
|
|
}
|
|
if (!whitelist) revert Errors.FollowModuleNotWhitelisted();
|
|
}
|
|
|
|
function _validateProfileCreatorWhitelisted() private view {
|
|
bool whitelisted;
|
|
|
|
// Load whether the caller is whitelisted as a profile creator.
|
|
assembly {
|
|
mstore(0, caller())
|
|
mstore(32, PROFILE_CREATOR_WHITELIST_MAPPING_SLOT)
|
|
let slot := keccak256(0, 64)
|
|
whitelisted := sload(slot)
|
|
}
|
|
if (!whitelisted) revert Errors.ProfileCreatorNotWhitelisted();
|
|
}
|
|
}
|