mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-09 22:28:04 -05:00
286 lines
13 KiB
Solidity
286 lines
13 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
import {Types} from 'contracts/libraries/constants/Types.sol';
|
|
import {EIP712} from '@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol';
|
|
import {Errors} from 'contracts/libraries/constants/Errors.sol';
|
|
import {Events} from 'contracts/libraries/constants/Events.sol';
|
|
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
|
import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
|
|
import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
|
|
import {IERC721Timestamped} from 'contracts/interfaces/IERC721Timestamped.sol';
|
|
import {IReferenceModule} from 'contracts/interfaces/IReferenceModule.sol';
|
|
import {HubRestricted} from 'contracts/base/HubRestricted.sol';
|
|
import {FollowValidationLib} from 'contracts/modules/libraries/FollowValidationLib.sol';
|
|
|
|
/**
|
|
* @notice Struct representing the module configuration for certain publication.
|
|
*
|
|
* @param setUp Indicates if the publication was set up to use this module, to then allow updating params.
|
|
* @param commentsRestricted Indicates if the comment operation is restricted or open to everyone.
|
|
* @param quotesRestricted Indicates if the quote operation is restricted or open to everyone.
|
|
* @param mirrorsRestricted Indicates if the mirror operation is restricted or open to everyone.
|
|
* @param degreesOfSeparation The max degrees of separation allowed for restricted operations.
|
|
* @param sourceProfile The ID of the profile from where the follower path should be started. Expected to be set as the
|
|
* author of the root publication.
|
|
*/
|
|
struct ModuleConfig {
|
|
bool setUp;
|
|
bool commentsRestricted;
|
|
bool quotesRestricted;
|
|
bool mirrorsRestricted;
|
|
uint8 degreesOfSeparation;
|
|
uint128 sourceProfile;
|
|
}
|
|
|
|
/**
|
|
* @title DegreesOfSeparationReferenceModule
|
|
* @author Lens Protocol
|
|
*
|
|
* @notice This reference module allows to set a degree of separation `n`, and then allows to quote/comment/mirror
|
|
* only to profiles that are at most at `n` degrees of separation from the source profile, which is expected to be set
|
|
* as the author of the root publication.
|
|
*/
|
|
contract DegreesOfSeparationReferenceModule is HubRestricted, IReferenceModule {
|
|
error InvalidDegreesOfSeparation();
|
|
error OperationDisabled();
|
|
error ProfilePathExceedsDegreesOfSeparation();
|
|
error NotInheritingPointedPubConfig();
|
|
|
|
/**
|
|
* @dev Because of the "Six degrees of separation" theory, in the long term, setting up 5, 6 or more degrees of
|
|
* separation will be almost equivalent to turning off the restriction.
|
|
* If we also take into account the gas cost of performing the validations on-chain, and the cost of off-chain
|
|
* computation of the path, makes sense to only support up to 3 degrees of separation.
|
|
*/
|
|
uint8 public constant MAX_DEGREES_OF_SEPARATION = 3;
|
|
|
|
mapping(uint256 profileId => mapping(uint256 pubId => ModuleConfig config)) internal _moduleConfig;
|
|
|
|
constructor(address hub) HubRestricted(hub) {}
|
|
|
|
/**
|
|
* @inheritdoc IReferenceModule
|
|
*
|
|
* @dev The `data` param should have ABI-encoded the following information:
|
|
* - bool commentsRestricted: Indicates if the comment operation is restricted or open to everyone.
|
|
* - bool quotesRestricted: Indicates if the quote operation is restricted or open to everyone.
|
|
* - bool mirrorsRestricted: Indicates if the mirror operation is restricted or open to everyone.
|
|
* - uint8 degreesOfSeparation: The max degrees of separation allowed for restricted operations.
|
|
* - uint128 sourceProfile The ID of the profile from where the follower path should be started. Expected to be set
|
|
* as the author of the root publication.
|
|
*/
|
|
function initializeReferenceModule(
|
|
uint256 profileId,
|
|
uint256 pubId,
|
|
address /* transactionExecutor */,
|
|
bytes calldata data
|
|
) external override onlyHub returns (bytes memory) {
|
|
(
|
|
bool commentsRestricted,
|
|
bool quotesRestricted,
|
|
bool mirrorsRestricted,
|
|
uint8 degreesOfSeparation,
|
|
uint128 sourceProfile
|
|
) = abi.decode(data, (bool, bool, bool, uint8, uint128));
|
|
if (degreesOfSeparation > MAX_DEGREES_OF_SEPARATION) {
|
|
revert InvalidDegreesOfSeparation();
|
|
}
|
|
if (!IERC721Timestamped(HUB).exists(sourceProfile)) {
|
|
revert Errors.TokenDoesNotExist();
|
|
}
|
|
_moduleConfig[profileId][pubId] = ModuleConfig(
|
|
true,
|
|
commentsRestricted,
|
|
quotesRestricted,
|
|
mirrorsRestricted,
|
|
degreesOfSeparation,
|
|
sourceProfile
|
|
);
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IReferenceModule
|
|
*
|
|
* @dev It will apply the degrees of separation restriction if the publication has `commentsRestricted` enabled.
|
|
* The param `processCommentParams.data` has ABI-encoded the array of profile IDs representing the follower path
|
|
* between the source profile and the profile authoring the comment.
|
|
* In addition, if comments were restricted, inheritance of commenting restrictions will be enforced.
|
|
*/
|
|
function processComment(
|
|
Types.ProcessCommentParams calldata processCommentParams
|
|
) external view override onlyHub returns (bytes memory) {
|
|
ModuleConfig memory config = _moduleConfig[processCommentParams.pointedProfileId][
|
|
processCommentParams.pointedPubId
|
|
];
|
|
if (config.commentsRestricted) {
|
|
_validateDegreesOfSeparationRestriction({
|
|
sourceProfile: config.sourceProfile,
|
|
profileId: processCommentParams.profileId,
|
|
degreesOfSeparation: config.degreesOfSeparation,
|
|
profilePath: abi.decode(processCommentParams.data, (uint256[]))
|
|
});
|
|
_validateCommentInheritedConfigFromPointedPub({
|
|
pointedPubConfig: config,
|
|
profileId: processCommentParams.profileId
|
|
});
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IReferenceModule
|
|
*
|
|
* @dev It will apply the degrees of separation restriction if the publication has `quotesRestricted` enabled.
|
|
* The param `processQuoteParams.data` has ABI-encoded the array of profile IDs representing the follower path
|
|
* between the source profile and the profile authoring the quote.
|
|
*/
|
|
function processQuote(
|
|
Types.ProcessQuoteParams calldata processQuoteParams
|
|
) external view override onlyHub returns (bytes memory) {
|
|
if (_moduleConfig[processQuoteParams.pointedProfileId][processQuoteParams.pointedPubId].quotesRestricted) {
|
|
_validateDegreesOfSeparationRestriction({
|
|
sourceProfile: _moduleConfig[processQuoteParams.pointedProfileId][processQuoteParams.pointedPubId]
|
|
.sourceProfile,
|
|
profileId: processQuoteParams.profileId,
|
|
degreesOfSeparation: _moduleConfig[processQuoteParams.pointedProfileId][processQuoteParams.pointedPubId]
|
|
.degreesOfSeparation,
|
|
profilePath: abi.decode(processQuoteParams.data, (uint256[]))
|
|
});
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IReferenceModule
|
|
*
|
|
* @dev It will apply the degrees of separation restriction if the publication has `mirrorsRestricted` enabled.
|
|
* The param `processMirrorParams.data` has ABI-encoded the array of profile IDs representing the follower path
|
|
* between the source profile and the profile authoring the mirror.
|
|
*/
|
|
function processMirror(
|
|
Types.ProcessMirrorParams calldata processMirrorParams
|
|
) external view override onlyHub returns (bytes memory) {
|
|
if (_moduleConfig[processMirrorParams.pointedProfileId][processMirrorParams.pointedPubId].mirrorsRestricted) {
|
|
_validateDegreesOfSeparationRestriction({
|
|
sourceProfile: _moduleConfig[processMirrorParams.pointedProfileId][processMirrorParams.pointedPubId]
|
|
.sourceProfile,
|
|
profileId: processMirrorParams.profileId,
|
|
degreesOfSeparation: _moduleConfig[processMirrorParams.pointedProfileId][
|
|
processMirrorParams.pointedPubId
|
|
].degreesOfSeparation,
|
|
profilePath: abi.decode(processMirrorParams.data, (uint256[]))
|
|
});
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @notice Gets the module configuration for the given publication.
|
|
*
|
|
* @param profileId The token ID of the profile publishing the publication.
|
|
* @param pubId The associated publication's LensHub publication ID.
|
|
*
|
|
* @return ModuleConfig The module configuration set for the given publication.
|
|
*/
|
|
function getModuleConfig(uint256 profileId, uint256 pubId) external view returns (ModuleConfig memory) {
|
|
return _moduleConfig[profileId][pubId];
|
|
}
|
|
|
|
/**
|
|
* @dev The data has encoded an array of integers, each integer is a profile ID, the whole array represents a path
|
|
* of `n` profiles.
|
|
*
|
|
* Let's define `X --> Y` as `The owner of X is following Y`. Then, being `path[i]` the i-th profile in the path,
|
|
* the following condition must be met for a given path of `n` profiles:
|
|
*
|
|
* sourceProfile --> path[0] --> path[1] --> path[2] --> ... --> path[n-2] --> path[n-1] --> profileId
|
|
*
|
|
* @param sourceProfile The ID of the profile from where the follower path should be started. Most likely to be the
|
|
* root publication's author.
|
|
* @param profileId The ID of the publication being published's author.
|
|
* @param degreesOfSeparation The degrees of separations configured for the given publication.
|
|
* @param profilePath The array of profile IDs representing the follower path between the source profile and the
|
|
* profile authoring the new publication (it could be a comment, a quote or a mirror of the pointed one).
|
|
*/
|
|
function _validateDegreesOfSeparationRestriction(
|
|
uint256 sourceProfile,
|
|
uint256 profileId,
|
|
uint8 degreesOfSeparation,
|
|
uint256[] memory profilePath
|
|
) internal view {
|
|
if (degreesOfSeparation == 0) {
|
|
// If `degreesOfSeparation` was set to zero, only `sourceProfile` is allowed to interact.
|
|
if (profileId == sourceProfile) {
|
|
return;
|
|
} else {
|
|
revert OperationDisabled();
|
|
}
|
|
} else if (profilePath.length > degreesOfSeparation - 1) {
|
|
revert ProfilePathExceedsDegreesOfSeparation();
|
|
}
|
|
if (profilePath.length > 0) {
|
|
// Checks that the source profile follows the first profile in the path.
|
|
// In the previous notation: sourceProfile --> path[0]
|
|
FollowValidationLib.validateIsFollowing({
|
|
hub: HUB,
|
|
followerProfileId: sourceProfile,
|
|
followedProfileId: profilePath[0]
|
|
});
|
|
// Checks each profile owner in the path is following the profile coming next, according the order.
|
|
// In the previous notaiton: path[0] --> path[1] --> path[2] --> ... --> path[n-2] --> path[n-1]
|
|
uint256 i;
|
|
while (i < profilePath.length - 1) {
|
|
FollowValidationLib.validateIsFollowing({
|
|
hub: HUB,
|
|
followerProfileId: profilePath[i],
|
|
followedProfileId: profilePath[i + 1]
|
|
});
|
|
unchecked {
|
|
++i;
|
|
}
|
|
}
|
|
// Checks that the last profile in the path follows the profile authoring the new publication.
|
|
// In the previous notation: path[n-1] --> profileId
|
|
FollowValidationLib.validateIsFollowing({
|
|
hub: HUB,
|
|
followerProfileId: profilePath[i],
|
|
followedProfileId: profileId
|
|
});
|
|
} else {
|
|
// Checks that the source profile follows the profile authoring the new publication.
|
|
// In the previous notation: sourceProfile --> profileId
|
|
FollowValidationLib.validateIsFollowing({
|
|
hub: HUB,
|
|
followerProfileId: sourceProfile,
|
|
followedProfileId: profileId
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Validates that the comment configuration is inherited from pointed publication.
|
|
*
|
|
* @param pointedPubConfig The pointed publication's degrees of separation module configuration.
|
|
* @param profileId The ID of the profile authoring the publication being processed.
|
|
*/
|
|
function _validateCommentInheritedConfigFromPointedPub(
|
|
ModuleConfig memory pointedPubConfig,
|
|
uint256 profileId
|
|
) internal view {
|
|
// We are processing profileId's last publication, so we get the ID from his publication counter.
|
|
uint256 pubId = ILensHub(HUB).getProfile(profileId).pubCount;
|
|
// We only care about inheritance of the comment restrictions.
|
|
if (
|
|
!_moduleConfig[profileId][pubId].setUp ||
|
|
!_moduleConfig[profileId][pubId].commentsRestricted ||
|
|
_moduleConfig[profileId][pubId].sourceProfile != pointedPubConfig.sourceProfile ||
|
|
_moduleConfig[profileId][pubId].degreesOfSeparation != pointedPubConfig.degreesOfSeparation
|
|
) {
|
|
revert NotInheritingPointedPubConfig();
|
|
}
|
|
}
|
|
}
|