Files
core/contracts/modules/act/collect/ProtocolSharedRevenueMinFeeMintModule.sol
2024-04-17 15:31:02 +02:00

267 lines
11 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import {BaseFeeCollectModule} from 'contracts/modules/act/collect/base/BaseFeeCollectModule.sol';
import {BaseFeeCollectModuleInitData, BaseProfilePublicationData} from 'contracts/modules/interfaces/IBaseFeeCollectModule.sol';
import {ICollectModule} from 'contracts/modules/interfaces/ICollectModule.sol';
import {LensModuleMetadata} from 'contracts/modules/LensModuleMetadata.sol';
import {LensModule} from 'contracts/modules/LensModule.sol';
import {ImmutableOwnable} from 'contracts/misc/ImmutableOwnable.sol';
import {ModuleTypes} from 'contracts/modules/libraries/constants/ModuleTypes.sol';
import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {Errors} from 'contracts/modules/constants/Errors.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
struct ProtocolSharedRevenueMinFeeMintModuleInitData {
uint160 amount;
uint96 collectLimit;
address currency;
uint96 currentCollects;
address recipient;
uint16 referralFee;
bool followerOnly;
uint72 endTimestamp;
address creatorFrontend;
}
struct ProtocolSharedRevenueMinFeeMintModulePublicationData {
uint160 amount;
uint96 collectLimit;
address currency;
uint96 currentCollects;
address recipient;
uint16 referralFee;
bool followerOnly;
uint72 endTimestamp;
address creatorFrontend;
}
// Splits (in BPS)
struct ProtocolSharedRevenueDistribution {
uint16 creatorSplit;
uint16 protocolSplit;
uint16 creatorFrontendSplit;
uint16 executorFrontendSplit;
}
/**
* @title ProtocolSharedRevenueMinFeeMint
* @author Lens Protocol
*
* @notice This is a simple Lens CollectModule implementation, allowing customization of time to collect,
* number of collects and whether only followers can collect.
*
* You can build your own collect modules by inheriting from BaseFeeCollectModule and adding your
* functionality along with getPublicationData function.
*/
contract ProtocolSharedRevenueMinFeeMintModule is BaseFeeCollectModule, LensModuleMetadata {
using SafeERC20 for IERC20;
address mintFeeToken;
uint256 mintFeeAmount;
ProtocolSharedRevenueDistribution protocolSharedRevenueDistribution;
mapping(uint256 profileId => mapping(uint256 pubId => address creatorFrontend))
internal _creatorFrontendByPublicationByProfile;
constructor(
address hub,
address actionModule,
address moduleRegistry,
address moduleOwner
) BaseFeeCollectModule(hub, actionModule, moduleRegistry) LensModuleMetadata(moduleOwner) {}
/**
* @inheritdoc ICollectModule
* @notice This collect module levies a fee on collects and supports referrals. Thus, we need to decode data.
* @param data The arbitrary data parameter, decoded into BaseFeeCollectModuleInitData struct:
* amount: The collecting cost associated with this publication. 0 for free collect.
* collectLimit: The maximum number of collects for this publication. 0 for no limit.
* currency: The currency associated with this publication.
* referralFee: The referral fee associated with this publication.
* followerOnly: True if only followers of publisher may collect the post.
* endTimestamp: The end timestamp after which collecting is impossible. 0 for no expiry.
* recipient: Recipient of collect fees.
*
* @return An abi encoded bytes parameter, which is the same as the passed data parameter.
*/
function initializePublicationCollectModule(
uint256 profileId,
uint256 pubId,
address /* transactionExecutor */,
bytes calldata data
) external override onlyActionModule returns (bytes memory) {
ProtocolSharedRevenueMinFeeMintModuleInitData memory initData = abi.decode(
data,
(ProtocolSharedRevenueMinFeeMintModuleInitData)
);
BaseFeeCollectModuleInitData memory baseInitData = BaseFeeCollectModuleInitData({
amount: initData.amount,
collectLimit: initData.collectLimit,
currency: initData.currency,
referralFee: initData.referralFee,
followerOnly: initData.followerOnly,
endTimestamp: initData.endTimestamp,
recipient: initData.recipient
});
if (initData.creatorFrontend != address(0)) {
_creatorFrontendByPublicationByProfile[profileId][pubId] = initData.creatorFrontend;
}
_validateBaseInitData(baseInitData);
_storeBasePublicationCollectParameters(profileId, pubId, baseInitData);
return '';
}
function processCollect(
ModuleTypes.ProcessCollectParams calldata processCollectParams
) external override onlyActionModule returns (bytes memory) {
if (
_dataByPublicationByProfile[processCollectParams.publicationCollectedProfileId][
processCollectParams.publicationCollectedId
].amount == 0
) {
_handleMintFee(processCollectParams);
}
// Regular processCollect:
_validateAndStoreCollect(processCollectParams);
if (processCollectParams.referrerProfileIds.length == 0) {
_processCollect(processCollectParams);
} else {
_processCollectWithReferral(processCollectParams);
}
return '';
}
// Internal functions
function _handleMintFee(ModuleTypes.ProcessCollectParams calldata processCollectParams) internal {
if (mintFeeAmount == 0) {
return;
}
address creator = ILensHub(HUB).ownerOf(processCollectParams.publicationCollectedProfileId);
uint256 creatorAmount = (mintFeeAmount * protocolSharedRevenueDistribution.creatorSplit) / 10000;
address protocol = ILensHub(HUB).getTreasury();
uint256 protocolAmount = (mintFeeAmount * protocolSharedRevenueDistribution.protocolSplit) / 10000;
address creatorFrontend = _creatorFrontendByPublicationByProfile[
processCollectParams.publicationCollectedProfileId
][processCollectParams.publicationCollectedId];
uint256 creatorFrontendAmount = (mintFeeAmount * protocolSharedRevenueDistribution.creatorFrontendSplit) /
10000;
if (creatorFrontend != address(0)) {
IERC20(mintFeeToken).safeTransferFrom(
processCollectParams.transactionExecutor,
creatorFrontend,
creatorFrontendAmount
);
} else {
// If there's no creatorFrontend specified - we give that amount to the publication creator
creatorAmount += creatorFrontendAmount;
}
(, , address executorFrontend) = abi.decode(processCollectParams.data, (address, uint256, address));
uint256 executorFrontendAmount = (mintFeeAmount * protocolSharedRevenueDistribution.executorFrontendSplit) /
10000;
if (executorFrontend != address(0)) {
IERC20(mintFeeToken).safeTransferFrom(
processCollectParams.transactionExecutor,
executorFrontend,
executorFrontendAmount
);
} else {
// If there's no creatorFrontend specified - we give that amount to the publication creator
creatorAmount += executorFrontendAmount;
}
IERC20(mintFeeToken).safeTransferFrom(processCollectParams.transactionExecutor, creator, creatorAmount);
IERC20(mintFeeToken).safeTransferFrom(processCollectParams.transactionExecutor, protocol, protocolAmount);
}
function _validateDataIsExpected(bytes calldata data, address currency, uint256 amount) internal pure override {
(address decodedCurrency, uint256 decodedAmount, ) = abi.decode(data, (address, uint256, address));
if (decodedAmount != amount || decodedCurrency != currency) {
revert Errors.ModuleDataMismatch();
}
}
// OnlyOwner functions
function setMintFeeParams(address token, uint256 amount) external onlyOwner {
if (amount > 0 && token == address(0)) {
revert Errors.InvalidParams();
}
mintFeeToken = token;
mintFeeAmount = amount;
}
function setProtocolSharedRevenueDistribution(
ProtocolSharedRevenueDistribution memory distribution
) external onlyOwner {
if (
distribution.creatorSplit +
distribution.protocolSplit +
distribution.creatorFrontendSplit +
distribution.executorFrontendSplit !=
BPS_MAX
) {
revert Errors.InvalidParams();
}
protocolSharedRevenueDistribution = distribution;
}
// Getters
function getMintFeeParams() external view returns (address, uint256) {
return (mintFeeToken, mintFeeAmount);
}
function getProtocolSharedRevenueDistribution() external view returns (ProtocolSharedRevenueDistribution memory) {
return protocolSharedRevenueDistribution;
}
/**
* @notice Returns the publication data for a given publication, or an empty struct if that publication was not
* initialized with this module.
*
* @param profileId The token ID of the profile mapped to the publication to query.
* @param pubId The publication ID of the publication to query.
*
* @return The BaseProfilePublicationData struct mapped to that publication.
*/
function getPublicationData(
uint256 profileId,
uint256 pubId
) external view returns (ProtocolSharedRevenueMinFeeMintModulePublicationData memory) {
BaseProfilePublicationData memory baseData = getBasePublicationData(profileId, pubId);
address creatorFrontend = _creatorFrontendByPublicationByProfile[profileId][pubId];
return
ProtocolSharedRevenueMinFeeMintModulePublicationData({
amount: baseData.amount,
collectLimit: baseData.collectLimit,
currency: baseData.currency,
currentCollects: baseData.currentCollects,
recipient: baseData.recipient,
referralFee: baseData.referralFee,
followerOnly: baseData.followerOnly,
endTimestamp: baseData.endTimestamp,
creatorFrontend: creatorFrontend
});
}
function supportsInterface(
bytes4 interfaceID
) public pure override(BaseFeeCollectModule, LensModule) returns (bool) {
return BaseFeeCollectModule.supportsInterface(interfaceID) || LensModule.supportsInterface(interfaceID);
}
}