mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
feat: LensModule interface with ERC165 standard and metadata [P-21] T-16815
Co-authored-by: Alan <donosonaumczuk@gmail.com>
This commit is contained in:
@@ -4,6 +4,11 @@ pragma solidity ^0.8.15;
|
||||
|
||||
import {IModuleRegistry} from 'contracts/interfaces/IModuleRegistry.sol';
|
||||
import {IERC20Metadata} from '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
|
||||
import {ILensModule} from 'contracts/modules/interfaces/ILensModule.sol';
|
||||
|
||||
import {IPublicationActionModule} from 'contracts/interfaces/IPublicationActionModule.sol';
|
||||
import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
|
||||
import {IReferenceModule} from 'contracts/interfaces/IReferenceModule.sol';
|
||||
|
||||
/**
|
||||
* @title ModuleRegistry
|
||||
@@ -12,7 +17,14 @@ import {IERC20Metadata} from '@openzeppelin/contracts/token/ERC20/extensions/IER
|
||||
* @custom:upgradeable Transparent upgradeable proxy without initializer.
|
||||
*/
|
||||
contract ModuleRegistry is IModuleRegistry {
|
||||
event ModuleRegistered(address indexed moduleAddress, uint256 indexed moduleType, uint256 timestamp);
|
||||
bytes4 private constant LENS_MODULE_INTERFACE_ID = bytes4(keccak256(abi.encodePacked('LENS_MODULE')));
|
||||
|
||||
event ModuleRegistered(
|
||||
address indexed moduleAddress,
|
||||
uint256 indexed moduleType,
|
||||
string metadata,
|
||||
uint256 timestamp
|
||||
);
|
||||
|
||||
event erc20CurrencyRegistered(
|
||||
address indexed erc20CurrencyAddress,
|
||||
@@ -22,6 +34,9 @@ contract ModuleRegistry is IModuleRegistry {
|
||||
uint256 timestamp
|
||||
);
|
||||
|
||||
error NotLensModule();
|
||||
error ModuleDoesNotSupportType(uint256 moduleType);
|
||||
|
||||
mapping(address moduleAddress => uint256 moduleTypesBitmap) internal registeredModules;
|
||||
|
||||
mapping(address erc20CurrencyAddress => bool) internal registeredErc20Currencies;
|
||||
@@ -44,12 +59,36 @@ contract ModuleRegistry is IModuleRegistry {
|
||||
if (isAlreadyRegisteredAsThatType) {
|
||||
return false;
|
||||
} else {
|
||||
emit ModuleRegistered(moduleAddress, moduleType, block.timestamp);
|
||||
if (!ILensModule(moduleAddress).supportsInterface(LENS_MODULE_INTERFACE_ID)) {
|
||||
revert NotLensModule();
|
||||
}
|
||||
|
||||
validateModuleSupportsType(moduleAddress, moduleType);
|
||||
|
||||
string memory metadata = ILensModule(moduleAddress).getModuleMetadataURI();
|
||||
emit ModuleRegistered(moduleAddress, moduleType, metadata, block.timestamp);
|
||||
registeredModules[moduleAddress] |= (1 << moduleType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function validateModuleSupportsType(address moduleAddress, uint256 moduleType) internal view {
|
||||
bool supportsInterface;
|
||||
if (moduleType == uint256(IModuleRegistry.ModuleType.PUBLICATION_ACTION_MODULE)) {
|
||||
supportsInterface = ILensModule(moduleAddress).supportsInterface(
|
||||
type(IPublicationActionModule).interfaceId
|
||||
);
|
||||
} else if (moduleType == uint256(IModuleRegistry.ModuleType.FOLLOW_MODULE)) {
|
||||
supportsInterface = ILensModule(moduleAddress).supportsInterface(type(IFollowModule).interfaceId);
|
||||
} else if (moduleType == uint256(IModuleRegistry.ModuleType.REFERENCE_MODULE)) {
|
||||
supportsInterface = ILensModule(moduleAddress).supportsInterface(type(IReferenceModule).interfaceId);
|
||||
}
|
||||
|
||||
if (!supportsInterface) {
|
||||
revert ModuleDoesNotSupportType(moduleType);
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleTypes(address moduleAddress) public view returns (uint256) {
|
||||
return registeredModules[moduleAddress];
|
||||
}
|
||||
|
||||
11
contracts/modules/LensModule.sol
Normal file
11
contracts/modules/LensModule.sol
Normal file
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.18;
|
||||
|
||||
import {ILensModule} from 'contracts/modules/interfaces/ILensModule.sol';
|
||||
|
||||
abstract contract LensModule is ILensModule {
|
||||
/// @inheritdoc ILensModule
|
||||
function supportsInterface(bytes4 interfaceID) public pure virtual override returns (bool) {
|
||||
return interfaceID == bytes4(keccak256(abi.encodePacked('LENS_MODULE')));
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,19 @@ import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
|
||||
import {Errors} from 'contracts/modules/constants/Errors.sol';
|
||||
import {HubRestricted} from 'contracts/base/HubRestricted.sol';
|
||||
|
||||
contract CollectPublicationAction is HubRestricted, IPublicationActionModule {
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @title CollectPublicationAction
|
||||
* @author LensProtocol
|
||||
* @notice An Publication Action module that allows users to collect publications.
|
||||
* @custom:upgradeable Transparent upgradeable proxy without initializer.
|
||||
*/
|
||||
contract CollectPublicationAction is LensModule, HubRestricted, IPublicationActionModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IPublicationActionModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
struct CollectData {
|
||||
address collectModule;
|
||||
address collectNFT;
|
||||
@@ -204,4 +216,8 @@ contract CollectPublicationAction is HubRestricted, IPublicationActionModule {
|
||||
function isCollectModuleRegistered(address collectModule) external view returns (bool) {
|
||||
return _collectModuleRegistered[collectModule];
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ import {PublicDrop} from '@seadrop/lib/SeaDropStructs.sol';
|
||||
import {LensSeaDropCollection} from 'contracts/modules/act/seadrop/LensSeaDropCollection.sol';
|
||||
import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
|
||||
|
||||
contract SeaDropMintPublicationAction is HubRestricted, IPublicationActionModule {
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
contract SeaDropMintPublicationAction is LensModule, HubRestricted, IPublicationActionModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IPublicationActionModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
uint256 constant MAX_BPS = 10_000;
|
||||
|
||||
ISeaDrop public immutable SEADROP;
|
||||
@@ -274,4 +280,8 @@ contract SeaDropMintPublicationAction is HubRestricted, IPublicationActionModule
|
||||
_rescaleFees(profileId, pubId, lensTreasuryFeeBps, publicDrop);
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
||||
import {HubRestricted} from 'contracts/base/HubRestricted.sol';
|
||||
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @notice A struct containing the necessary data to execute follow actions on a given profile.
|
||||
*
|
||||
@@ -28,7 +30,11 @@ struct FeeConfig {
|
||||
*
|
||||
* @notice This follow module charges a fee for every follow.
|
||||
*/
|
||||
contract FeeFollowModule is FeeModuleBase, HubRestricted, IFollowModule {
|
||||
contract FeeFollowModule is LensModule, FeeModuleBase, HubRestricted, IFollowModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IFollowModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
mapping(uint256 profileId => FeeConfig config) internal _feeConfig;
|
||||
@@ -116,4 +122,8 @@ contract FeeFollowModule is FeeModuleBase, HubRestricted, IFollowModule {
|
||||
function getFeeConfig(uint256 profileId) external view returns (FeeConfig memory) {
|
||||
return _feeConfig[profileId];
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,23 @@ pragma solidity ^0.8.10;
|
||||
import {Errors} from 'contracts/modules/constants/Errors.sol';
|
||||
import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
|
||||
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @title RevertFollowModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This follow module rejects all follow attempts.
|
||||
*/
|
||||
contract RevertFollowModule is IFollowModule {
|
||||
contract RevertFollowModule is LensModule, IFollowModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IFollowModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowModule
|
||||
function initializeFollowModule(
|
||||
uint256, /* profileId */
|
||||
address, /* transactionExecutor */
|
||||
uint256 /* profileId */,
|
||||
address /* transactionExecutor */,
|
||||
bytes calldata /* data */
|
||||
) external pure override returns (bytes memory) {
|
||||
return '';
|
||||
@@ -26,12 +32,16 @@ contract RevertFollowModule is IFollowModule {
|
||||
* @notice Processes a follow by rejecting it, reverting the transaction. Parameters are ignored.
|
||||
*/
|
||||
function processFollow(
|
||||
uint256, /* followerProfileId */
|
||||
uint256, /* followTokenId */
|
||||
address, /* transactionExecutor */
|
||||
uint256, /* profileId */
|
||||
uint256 /* followerProfileId */,
|
||||
uint256 /* followTokenId */,
|
||||
address /* transactionExecutor */,
|
||||
uint256 /* profileId */,
|
||||
bytes calldata /* data */
|
||||
) external pure override returns (bytes memory) {
|
||||
revert Errors.FollowInvalid();
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
18
contracts/modules/interfaces/ILensModule.sol
Normal file
18
contracts/modules/interfaces/ILensModule.sol
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.6.0;
|
||||
|
||||
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
|
||||
|
||||
interface ILensModule is IERC165 {
|
||||
/// @dev for now we check for keccak('LENS_MODULE');
|
||||
/// Override this and add the type(IModuleInterface).interfaceId for corresponding module type
|
||||
function supportsInterface(bytes4 interfaceID) external view returns (bool);
|
||||
|
||||
/// @notice Human-readable description of the module
|
||||
// Can be JSON
|
||||
// Can be contract source code
|
||||
// Can be github link
|
||||
// Can be ipfs with documentation
|
||||
// etc
|
||||
function getModuleMetadataURI() external view returns (string memory);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import {IReferenceModule} from 'contracts/interfaces/IReferenceModule.sol';
|
||||
import {HubRestricted} from 'contracts/base/HubRestricted.sol';
|
||||
import {FollowValidationLib} from 'contracts/modules/libraries/FollowValidationLib.sol';
|
||||
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @notice Struct representing the module configuration for certain publication.
|
||||
*
|
||||
@@ -40,7 +42,11 @@ struct ModuleConfig {
|
||||
* 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 {
|
||||
contract DegreesOfSeparationReferenceModule is LensModule, HubRestricted, IReferenceModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IReferenceModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
error InvalidDegreesOfSeparation();
|
||||
error OperationDisabled();
|
||||
error ProfilePathExceedsDegreesOfSeparation();
|
||||
@@ -301,4 +307,8 @@ contract DegreesOfSeparationReferenceModule is HubRestricted, IReferenceModule {
|
||||
revert NotInheritingPointedPubConfig();
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import {HubRestricted} from 'contracts/base/HubRestricted.sol';
|
||||
import {Types} from 'contracts/libraries/constants/Types.sol';
|
||||
import {FollowValidationLib} from 'contracts/modules/libraries/FollowValidationLib.sol';
|
||||
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @title FollowerOnlyReferenceModule
|
||||
* @author Lens Protocol
|
||||
@@ -14,7 +16,11 @@ import {FollowValidationLib} from 'contracts/modules/libraries/FollowValidationL
|
||||
* @notice A simple reference module that validates that comments, quotes or mirrors originate from a profile that
|
||||
* follows the profile of the original publication.
|
||||
*/
|
||||
contract FollowerOnlyReferenceModule is HubRestricted, IReferenceModule {
|
||||
contract FollowerOnlyReferenceModule is LensModule, HubRestricted, IReferenceModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IReferenceModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
constructor(address hub) HubRestricted(hub) {}
|
||||
|
||||
/**
|
||||
@@ -85,4 +91,8 @@ contract FollowerOnlyReferenceModule is HubRestricted, IReferenceModule {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import {Errors} from 'contracts/modules/constants/Errors.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
import {Types} from 'contracts/libraries/constants/Types.sol';
|
||||
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
interface IToken {
|
||||
/**
|
||||
* @dev Returns the amount of ERC20/ERC721 tokens owned by `account`.
|
||||
@@ -32,7 +34,11 @@ struct GateParams {
|
||||
*
|
||||
* @notice A reference module that validates that the user who tries to reference has a required minimum balance of ERC20/ERC721 token.
|
||||
*/
|
||||
contract TokenGatedReferenceModule is HubRestricted, IReferenceModule {
|
||||
contract TokenGatedReferenceModule is LensModule, HubRestricted, IReferenceModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IReferenceModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
uint256 internal constant UINT256_BYTES = 32;
|
||||
|
||||
event TokenGatedReferencePublicationCreated(
|
||||
@@ -154,4 +160,8 @@ contract TokenGatedReferenceModule is HubRestricted, IReferenceModule {
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import {MockModule} from 'test/mocks/MockModule.sol';
|
||||
* @dev This is a simple mock Action module to be used for testing revert cases on processAction.
|
||||
*/
|
||||
contract MockActionModule is MockModule, IPublicationActionModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IPublicationActionModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
error MockActionModuleReverted();
|
||||
|
||||
function testMockActionModule() public {
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
pragma solidity ^0.8.15;
|
||||
|
||||
import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @dev This is a simple mock follow module to be used for testing.
|
||||
*/
|
||||
contract MockFollowModule is IFollowModule {
|
||||
contract MockFollowModule is LensModule, IFollowModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IFollowModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
function testMockFollowModule() public {
|
||||
// Prevents being counted in Foundry Coverage
|
||||
}
|
||||
@@ -29,4 +34,8 @@ contract MockFollowModule is IFollowModule {
|
||||
uint256 profileId,
|
||||
bytes calldata data
|
||||
) external pure override returns (bytes memory) {}
|
||||
|
||||
function getModuleMetadataURI() external pure override returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
pragma solidity ^0.8.15;
|
||||
|
||||
import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
/**
|
||||
* @dev This is a simple mock follow module to be used for testing revert cases on processFollow.
|
||||
*/
|
||||
contract MockFollowModuleWithRevertFlag is IFollowModule {
|
||||
contract MockFollowModuleWithRevertFlag is LensModule, IFollowModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IFollowModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
error MockFollowModuleReverted();
|
||||
|
||||
function testMockFollowModuleWithRevertFlag() public {
|
||||
@@ -34,4 +39,8 @@ contract MockFollowModuleWithRevertFlag is IFollowModule {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure override returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
pragma solidity ^0.8.15;
|
||||
|
||||
abstract contract MockModule {
|
||||
import {LensModule} from 'contracts/modules/LensModule.sol';
|
||||
|
||||
abstract contract MockModule is LensModule {
|
||||
error MockModuleReverted();
|
||||
|
||||
function testMockModule() public {
|
||||
@@ -17,4 +19,8 @@ abstract contract MockModule {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function getModuleMetadataURI() external pure override returns (string memory) {
|
||||
return 'https://docs.lens.xyz/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import {MockModule} from 'test/mocks/MockModule.sol';
|
||||
* @dev This is a simple mock follow module to be used for testing.
|
||||
*/
|
||||
contract MockReferenceModule is MockModule, IReferenceModule {
|
||||
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
|
||||
return interfaceID == type(IReferenceModule).interfaceId || super.supportsInterface(interfaceID);
|
||||
}
|
||||
|
||||
function testMockReferenceModule() public {
|
||||
// Prevents being counted in Foundry Coverage
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user