mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
misc: Created deprecated modules and minor cleanups.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedCollectModule} from '../../../../interfaces/IDeprecatedCollectModule.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {FeeModuleBase} from '../../FeeModuleBase.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {FollowValidationModuleBase} from '../../FollowValidationModuleBase.sol';
|
||||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @notice A struct containing the necessary data to execute collect actions on a publication.
|
||||
*
|
||||
* @param amount The collecting cost associated with this publication.
|
||||
* @param currency The currency associated with this publication.
|
||||
* @param recipient The recipient address associated with this publication.
|
||||
* @param referralFee The referral fee associated with this publication.
|
||||
* @param followerOnly Whether only followers should be able to collect.
|
||||
*/
|
||||
struct ProfilePublicationData {
|
||||
uint256 amount;
|
||||
address currency;
|
||||
address recipient;
|
||||
uint16 referralFee;
|
||||
bool followerOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title FeeCollectModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface and
|
||||
* the FeeCollectModuleBase abstract contract.
|
||||
*
|
||||
* This module works by allowing unlimited collects for a publication at a given price.
|
||||
*/
|
||||
contract DeprecatedFeeCollectModule is FeeModuleBase, FollowValidationModuleBase, IDeprecatedCollectModule {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
mapping(uint256 => mapping(uint256 => ProfilePublicationData))
|
||||
internal _dataByPublicationByProfile;
|
||||
|
||||
constructor(address hub, address moduleGlobals) FeeModuleBase(moduleGlobals) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This collect module levies a fee on collects and supports referrals. Thus, we need to decode data.
|
||||
*
|
||||
* @param profileId The token ID of the profile of the publisher, passed by the hub.
|
||||
* @param pubId The publication ID of the newly created publication, passed by the hub.
|
||||
* @param data The arbitrary data parameter, decoded into:
|
||||
* uint256 amount: The currency total amount to levy.
|
||||
* address currency: The currency address, must be internally whitelisted.
|
||||
* address recipient: The custom recipient address to direct earnings to.
|
||||
* uint16 referralFee: The referral fee to set.
|
||||
* bool followerOnly: Whether only followers should be able to collect.
|
||||
*
|
||||
* @return bytes An abi encoded bytes parameter, which is the same as the passed data parameter.
|
||||
*/
|
||||
function initializePublicationCollectModule(
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub returns (bytes memory) {
|
||||
(
|
||||
uint256 amount,
|
||||
address currency,
|
||||
address recipient,
|
||||
uint16 referralFee,
|
||||
bool followerOnly
|
||||
) = abi.decode(data, (uint256, address, address, uint16, bool));
|
||||
if (
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].amount = amount;
|
||||
_dataByPublicationByProfile[profileId][pubId].currency = currency;
|
||||
_dataByPublicationByProfile[profileId][pubId].recipient = recipient;
|
||||
_dataByPublicationByProfile[profileId][pubId].referralFee = referralFee;
|
||||
_dataByPublicationByProfile[profileId][pubId].followerOnly = followerOnly;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a collect by:
|
||||
* 1. Ensuring the collector is a follower
|
||||
* 2. Charging a fee
|
||||
*/
|
||||
function processCollect(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external virtual override onlyHub {
|
||||
if (_dataByPublicationByProfile[profileId][pubId].followerOnly)
|
||||
_checkFollowValidity(profileId, collector);
|
||||
if (referrerProfileId == profileId) {
|
||||
_processCollect(collector, profileId, pubId, data);
|
||||
} else {
|
||||
_processCollectWithReferral(referrerProfileId, collector, profileId, pubId, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ProfilePublicationData The ProfilePublicationData struct mapped to that publication.
|
||||
*/
|
||||
function getPublicationData(uint256 profileId, uint256 pubId)
|
||||
external
|
||||
view
|
||||
returns (ProfilePublicationData memory)
|
||||
{
|
||||
return _dataByPublicationByProfile[profileId][pubId];
|
||||
}
|
||||
|
||||
function _processCollect(
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
(address treasury, uint16 treasuryFee) = _treasuryData();
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
uint256 treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
uint256 referralFee = _dataByPublicationByProfile[profileId][pubId].referralFee;
|
||||
address treasury;
|
||||
uint256 treasuryAmount;
|
||||
|
||||
// Avoids stack too deep
|
||||
{
|
||||
uint16 treasuryFee;
|
||||
(treasury, treasuryFee) = _treasuryData();
|
||||
treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
}
|
||||
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
if (referralFee != 0) {
|
||||
// The reason we levy the referral fee on the adjusted amount is so that referral fees
|
||||
// don't bypass the treasury fee, in essence referrals pay their fair share to the treasury.
|
||||
uint256 referralAmount = (adjustedAmount * referralFee) / BPS_MAX;
|
||||
adjustedAmount = adjustedAmount - referralAmount;
|
||||
|
||||
address referralRecipient = IERC721(HUB).ownerOf(referrerProfileId);
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, referralRecipient, referralAmount);
|
||||
}
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedCollectModule} from '../../../../interfaces/IDeprecatedCollectModule.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {FollowValidationModuleBase} from '../../FollowValidationModuleBase.sol';
|
||||
|
||||
/**
|
||||
* @title FreeCollectModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface.
|
||||
*
|
||||
* This module works by allowing all collects.
|
||||
*/
|
||||
contract DeprecatedFreeCollectModule is FollowValidationModuleBase, IDeprecatedCollectModule {
|
||||
constructor(address hub) ModuleBase(hub) {}
|
||||
|
||||
mapping(uint256 => mapping(uint256 => bool)) internal _followerOnlyByPublicationByProfile;
|
||||
|
||||
/**
|
||||
* @dev There is nothing needed at initialization.
|
||||
*/
|
||||
function initializePublicationCollectModule(
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub returns (bytes memory) {
|
||||
bool followerOnly = abi.decode(data, (bool));
|
||||
if (followerOnly) _followerOnlyByPublicationByProfile[profileId][pubId] = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a collect by:
|
||||
* 1. Ensuring the collector is a follower, if needed
|
||||
*/
|
||||
function processCollect(
|
||||
uint256,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata
|
||||
) external view override {
|
||||
if (_followerOnlyByPublicationByProfile[profileId][pubId])
|
||||
_checkFollowValidity(profileId, collector);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedCollectModule} from '../../../../interfaces/IDeprecatedCollectModule.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {FeeModuleBase} from '../../FeeModuleBase.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {FollowValidationModuleBase} from '../../FollowValidationModuleBase.sol';
|
||||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @notice A struct containing the necessary data to execute collect actions on a publication.
|
||||
*
|
||||
* @param collectLimit The maximum number of collects for this publication.
|
||||
* @param currentCollects The current number of collects for this publication.
|
||||
* @param amount The collecting cost associated with this publication.
|
||||
* @param currency The currency associated with this publication.
|
||||
* @param recipient The recipient address associated with this publication.
|
||||
* @param referralFee The referral fee associated with this publication.
|
||||
* @param followerOnly Whether only followers should be able to collect.
|
||||
*/
|
||||
struct ProfilePublicationData {
|
||||
uint256 collectLimit;
|
||||
uint256 currentCollects;
|
||||
uint256 amount;
|
||||
address currency;
|
||||
address recipient;
|
||||
uint16 referralFee;
|
||||
bool followerOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title LimitedFeeCollectModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface and
|
||||
* the FeeCollectModuleBase abstract contract.
|
||||
*
|
||||
* This module works by allowing limited collects for a publication indefinitely.
|
||||
*/
|
||||
contract DeprecatedLimitedFeeCollectModule is FeeModuleBase, FollowValidationModuleBase, IDeprecatedCollectModule {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
mapping(uint256 => mapping(uint256 => ProfilePublicationData))
|
||||
internal _dataByPublicationByProfile;
|
||||
|
||||
constructor(address hub, address moduleGlobals) FeeModuleBase(moduleGlobals) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This collect module levies a fee on collects and supports referrals. Thus, we need to decode data.
|
||||
*
|
||||
* @param profileId The profile ID of the publication to initialize this module for's publishing profile.
|
||||
* @param pubId The publication ID of the publication to initialize this module for.
|
||||
* @param data The arbitrary data parameter, decoded into:
|
||||
* uint256 collectLimit: The maximum amount of collects.
|
||||
* uint256 amount: The currency total amount to levy.
|
||||
* address currency: The currency address, must be internally whitelisted.
|
||||
* address recipient: The custom recipient address to direct earnings to.
|
||||
* uint16 referralFee: The referral fee to set.
|
||||
* bool followerOnly: Whether only followers should be able to collect.
|
||||
*
|
||||
* @return bytes An abi encoded bytes parameter, which is the same as the passed data parameter.
|
||||
*/
|
||||
function initializePublicationCollectModule(
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub returns (bytes memory) {
|
||||
(
|
||||
uint256 collectLimit,
|
||||
uint256 amount,
|
||||
address currency,
|
||||
address recipient,
|
||||
uint16 referralFee,
|
||||
bool followerOnly
|
||||
) = abi.decode(data, (uint256, uint256, address, address, uint16, bool));
|
||||
if (
|
||||
collectLimit == 0 ||
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].collectLimit = collectLimit;
|
||||
_dataByPublicationByProfile[profileId][pubId].amount = amount;
|
||||
_dataByPublicationByProfile[profileId][pubId].currency = currency;
|
||||
_dataByPublicationByProfile[profileId][pubId].recipient = recipient;
|
||||
_dataByPublicationByProfile[profileId][pubId].referralFee = referralFee;
|
||||
_dataByPublicationByProfile[profileId][pubId].followerOnly = followerOnly;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a collect by:
|
||||
* 1. Ensuring the collector is a follower
|
||||
* 2. Ensuring the collect does not pass the collect limit
|
||||
* 3. Charging a fee
|
||||
*/
|
||||
function processCollect(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub {
|
||||
if (_dataByPublicationByProfile[profileId][pubId].followerOnly)
|
||||
_checkFollowValidity(profileId, collector);
|
||||
if (
|
||||
_dataByPublicationByProfile[profileId][pubId].currentCollects >=
|
||||
_dataByPublicationByProfile[profileId][pubId].collectLimit
|
||||
) {
|
||||
revert Errors.MintLimitExceeded();
|
||||
} else {
|
||||
++_dataByPublicationByProfile[profileId][pubId].currentCollects;
|
||||
if (referrerProfileId == profileId) {
|
||||
_processCollect(collector, profileId, pubId, data);
|
||||
} else {
|
||||
_processCollectWithReferral(referrerProfileId, collector, profileId, pubId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ProfilePublicationData The ProfilePublicationData struct mapped to that publication.
|
||||
*/
|
||||
function getPublicationData(uint256 profileId, uint256 pubId)
|
||||
external
|
||||
view
|
||||
returns (ProfilePublicationData memory)
|
||||
{
|
||||
return _dataByPublicationByProfile[profileId][pubId];
|
||||
}
|
||||
|
||||
function _processCollect(
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
(address treasury, uint16 treasuryFee) = _treasuryData();
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
uint256 treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
uint256 referralFee = _dataByPublicationByProfile[profileId][pubId].referralFee;
|
||||
address treasury;
|
||||
uint256 treasuryAmount;
|
||||
|
||||
// Avoids stack too deep
|
||||
{
|
||||
uint16 treasuryFee;
|
||||
(treasury, treasuryFee) = _treasuryData();
|
||||
treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
}
|
||||
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
if (referralFee != 0) {
|
||||
// The reason we levy the referral fee on the adjusted amount is so that referral fees
|
||||
// don't bypass the treasury fee, in essence referrals pay their fair share to the treasury.
|
||||
uint256 referralAmount = (adjustedAmount * referralFee) / BPS_MAX;
|
||||
adjustedAmount = adjustedAmount - referralAmount;
|
||||
|
||||
address referralRecipient = IERC721(HUB).ownerOf(referrerProfileId);
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, referralRecipient, referralAmount);
|
||||
}
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedCollectModule} from '../../../../interfaces/IDeprecatedCollectModule.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {FeeModuleBase} from '../../FeeModuleBase.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {FollowValidationModuleBase} from '../../FollowValidationModuleBase.sol';
|
||||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @notice A struct containing the necessary data to execute collect actions on a publication.
|
||||
*
|
||||
* @param collectLimit The maximum number of collects for this publication.
|
||||
* @param currentCollects The current number of collects for this publication.
|
||||
* @param amount The collecting cost associated with this publication.
|
||||
* @param currency The currency associated with this publication.
|
||||
* @param recipient The recipient address associated with this publication.
|
||||
* @param referralFee The referral fee associated with this publication.
|
||||
* @param endTimestamp The end timestamp after which collecting is impossible.
|
||||
* @param followerOnly Whether only followers should be able to collect.
|
||||
*/
|
||||
struct ProfilePublicationData {
|
||||
uint256 collectLimit;
|
||||
uint256 currentCollects;
|
||||
uint256 amount;
|
||||
address currency;
|
||||
address recipient;
|
||||
uint16 referralFee;
|
||||
bool followerOnly;
|
||||
uint40 endTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title LimitedTimedFeeCollectModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface and
|
||||
* the FeeCollectModuleBase abstract contract. To optimize on gas, this module uses a constant 24 hour maximum
|
||||
* collection time.
|
||||
*
|
||||
* This module works by allowing limited collects for a publication within the allotted time with a given fee.
|
||||
*/
|
||||
contract DeprecatedLimitedTimedFeeCollectModule is FeeModuleBase, FollowValidationModuleBase, IDeprecatedCollectModule {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
uint24 internal constant ONE_DAY = 24 hours;
|
||||
|
||||
mapping(uint256 => mapping(uint256 => ProfilePublicationData))
|
||||
internal _dataByPublicationByProfile;
|
||||
|
||||
constructor(address hub, address moduleGlobals) FeeModuleBase(moduleGlobals) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This collect module levies a fee on collects and supports referrals. Thus, we need to decode data.
|
||||
*
|
||||
* @param profileId The profile ID of the publication to initialize this module for's publishing profile.
|
||||
* @param pubId The publication ID of the publication to initialize this module for.
|
||||
* @param data The arbitrary data parameter, decoded into:
|
||||
* uint256 collectLimit: The maximum amount of collects.
|
||||
* uint256 amount: The currency total amount to levy.
|
||||
* address currency: The currency address, must be internally whitelisted.
|
||||
* address recipient: The custom recipient address to direct earnings to.
|
||||
* uint16 referralFee: The referral fee to set.
|
||||
* bool followerOnly: Whether only followers should be able to collect.
|
||||
*
|
||||
* @return bytes An abi encoded bytes parameter, containing (in order): collectLimit, amount, currency, recipient, referral fee & end timestamp.
|
||||
*/
|
||||
function initializePublicationCollectModule(
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub returns (bytes memory) {
|
||||
unchecked {
|
||||
uint40 endTimestamp = uint40(block.timestamp) + ONE_DAY;
|
||||
|
||||
(
|
||||
uint256 collectLimit,
|
||||
uint256 amount,
|
||||
address currency,
|
||||
address recipient,
|
||||
uint16 referralFee,
|
||||
bool followerOnly
|
||||
) = abi.decode(data, (uint256, uint256, address, address, uint16, bool));
|
||||
if (
|
||||
collectLimit == 0 ||
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].collectLimit = collectLimit;
|
||||
_dataByPublicationByProfile[profileId][pubId].amount = amount;
|
||||
_dataByPublicationByProfile[profileId][pubId].currency = currency;
|
||||
_dataByPublicationByProfile[profileId][pubId].recipient = recipient;
|
||||
_dataByPublicationByProfile[profileId][pubId].referralFee = referralFee;
|
||||
_dataByPublicationByProfile[profileId][pubId].followerOnly = followerOnly;
|
||||
_dataByPublicationByProfile[profileId][pubId].endTimestamp = endTimestamp;
|
||||
|
||||
return
|
||||
abi.encode(
|
||||
collectLimit,
|
||||
amount,
|
||||
currency,
|
||||
recipient,
|
||||
referralFee,
|
||||
followerOnly,
|
||||
endTimestamp
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a collect by:
|
||||
* 1. Ensuring the collector is a follower
|
||||
* 2. Ensuring the current timestamp is less than or equal to the collect end timestamp
|
||||
* 3. Ensuring the collect does not pass the collect limit
|
||||
* 4. Charging a fee
|
||||
*/
|
||||
function processCollect(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub {
|
||||
if (_dataByPublicationByProfile[profileId][pubId].followerOnly)
|
||||
_checkFollowValidity(profileId, collector);
|
||||
uint256 endTimestamp = _dataByPublicationByProfile[profileId][pubId].endTimestamp;
|
||||
if (block.timestamp > endTimestamp) revert Errors.CollectExpired();
|
||||
|
||||
if (
|
||||
_dataByPublicationByProfile[profileId][pubId].currentCollects >=
|
||||
_dataByPublicationByProfile[profileId][pubId].collectLimit
|
||||
) {
|
||||
revert Errors.MintLimitExceeded();
|
||||
} else {
|
||||
++_dataByPublicationByProfile[profileId][pubId].currentCollects;
|
||||
if (referrerProfileId == profileId) {
|
||||
_processCollect(collector, profileId, pubId, data);
|
||||
} else {
|
||||
_processCollectWithReferral(referrerProfileId, collector, profileId, pubId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ProfilepublicationData The ProfilePublicationData struct mapped to that publication.
|
||||
*/
|
||||
function getPublicationData(uint256 profileId, uint256 pubId)
|
||||
external
|
||||
view
|
||||
returns (ProfilePublicationData memory)
|
||||
{
|
||||
return _dataByPublicationByProfile[profileId][pubId];
|
||||
}
|
||||
|
||||
function _processCollect(
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
(address treasury, uint16 treasuryFee) = _treasuryData();
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
uint256 treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
uint256 referralFee = _dataByPublicationByProfile[profileId][pubId].referralFee;
|
||||
address treasury;
|
||||
uint256 treasuryAmount;
|
||||
|
||||
// Avoids stack too deep
|
||||
{
|
||||
uint16 treasuryFee;
|
||||
(treasury, treasuryFee) = _treasuryData();
|
||||
treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
}
|
||||
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
if (referralFee != 0) {
|
||||
// The reason we levy the referral fee on the adjusted amount is so that referral fees
|
||||
// don't bypass the treasury fee, in essence referrals pay their fair share to the treasury.
|
||||
uint256 referralAmount = (adjustedAmount * referralFee) / BPS_MAX;
|
||||
adjustedAmount = adjustedAmount - referralAmount;
|
||||
|
||||
address referralRecipient = IERC721(HUB).ownerOf(referrerProfileId);
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, referralRecipient, referralAmount);
|
||||
}
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedCollectModule} from '../../../../interfaces/IDeprecatedCollectModule.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
|
||||
/**
|
||||
* @title RevertCollectModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface.
|
||||
*
|
||||
* This module works by disallowing all collects.
|
||||
*/
|
||||
contract DeprecatedRevertCollectModule is IDeprecatedCollectModule {
|
||||
/**
|
||||
* @dev There is nothing needed at initialization.
|
||||
*/
|
||||
function initializePublicationCollectModule(
|
||||
uint256,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external pure override returns (bytes memory) {
|
||||
return new bytes(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a collect by:
|
||||
* 1. Always reverting
|
||||
*/
|
||||
function processCollect(
|
||||
uint256,
|
||||
address,
|
||||
uint256,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external pure override {
|
||||
revert Errors.CollectNotAllowed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedCollectModule} from '../../../../interfaces/IDeprecatedCollectModule.sol';
|
||||
import {ILensHub} from '../../../../interfaces/ILensHub.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {FeeModuleBase} from '../../FeeModuleBase.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {FollowValidationModuleBase} from '../../FollowValidationModuleBase.sol';
|
||||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @notice A struct containing the necessary data to execute collect actions on a publication.
|
||||
*
|
||||
* @param amount The collecting cost associated with this publication.
|
||||
* @param currency The currency associated with this publication.
|
||||
* @param recipient The recipient address associated with this publication.
|
||||
* @param referralFee The referral fee associated with this publication.
|
||||
* @param endTimestamp The end timestamp after which collecting is impossible.
|
||||
* @param followerOnly Whether only followers should be able to collect.
|
||||
*/
|
||||
struct ProfilePublicationData {
|
||||
uint256 amount;
|
||||
address currency;
|
||||
address recipient;
|
||||
uint16 referralFee;
|
||||
bool followerOnly;
|
||||
uint40 endTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title TimedFeeCollectModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface and
|
||||
* the FeeCollectModuleBase abstract contract. To optimize on gas, this module uses a constant 24 hour maximum
|
||||
* collection time.
|
||||
*
|
||||
* This module works by allowing unlimited collects for a publication within the allotted time with a given fee.
|
||||
*
|
||||
* NOTE: If data passed on initialization is empty, this module will only check for the time limit.
|
||||
*/
|
||||
contract DeprecatedTimedFeeCollectModule is FeeModuleBase, FollowValidationModuleBase, IDeprecatedCollectModule {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
uint24 internal constant ONE_DAY = 24 hours;
|
||||
|
||||
mapping(uint256 => mapping(uint256 => ProfilePublicationData))
|
||||
internal _dataByPublicationByProfile;
|
||||
|
||||
constructor(address hub, address moduleGlobals) FeeModuleBase(moduleGlobals) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This collect module levies a fee on collects and supports referrals. Thus, we need to decode data.
|
||||
*
|
||||
* @param profileId The profile ID of the publication to initialize this module for's publishing profile.
|
||||
* @param pubId The publication ID of the publication to initialize this module for.
|
||||
* @param data The arbitrary data parameter, decoded into:
|
||||
* uint256 amount: The currency total amount to levy.
|
||||
* address currency: The currency address, must be internally whitelisted.
|
||||
* address recipient: The custom recipient address to direct earnings to.
|
||||
* uint16 referralFee: The referral fee to set.
|
||||
* bool followerOnly: Whether only followers should be able to collect.
|
||||
*
|
||||
* @return bytes An abi encoded bytes parameter, containing (in order): amount, currency, recipient, referral fee & end timestamp.
|
||||
*/
|
||||
function initializePublicationCollectModule(
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub returns (bytes memory) {
|
||||
unchecked {
|
||||
uint40 endTimestamp = uint40(block.timestamp) + ONE_DAY;
|
||||
|
||||
(
|
||||
uint256 amount,
|
||||
address currency,
|
||||
address recipient,
|
||||
uint16 referralFee,
|
||||
bool followerOnly
|
||||
) = abi.decode(data, (uint256, address, address, uint16, bool));
|
||||
if (
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].amount = amount;
|
||||
_dataByPublicationByProfile[profileId][pubId].currency = currency;
|
||||
_dataByPublicationByProfile[profileId][pubId].recipient = recipient;
|
||||
_dataByPublicationByProfile[profileId][pubId].referralFee = referralFee;
|
||||
_dataByPublicationByProfile[profileId][pubId].followerOnly = followerOnly;
|
||||
_dataByPublicationByProfile[profileId][pubId].endTimestamp = endTimestamp;
|
||||
|
||||
return abi.encode(amount, currency, recipient, referralFee, followerOnly, endTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a collect by:
|
||||
* 1. Ensuring the collector is a follower
|
||||
* 2. Ensuring the current timestamp is less than or equal to the collect end timestamp
|
||||
* 3. Charging a fee
|
||||
*/
|
||||
function processCollect(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) external override onlyHub {
|
||||
if (_dataByPublicationByProfile[profileId][pubId].followerOnly)
|
||||
_checkFollowValidity(profileId, collector);
|
||||
uint256 endTimestamp = _dataByPublicationByProfile[profileId][pubId].endTimestamp;
|
||||
if (block.timestamp > endTimestamp) revert Errors.CollectExpired();
|
||||
|
||||
if (referrerProfileId == profileId) {
|
||||
_processCollect(collector, profileId, pubId, data);
|
||||
} else {
|
||||
_processCollectWithReferral(referrerProfileId, collector, profileId, pubId, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ProfilePublicationData The ProfilePublicationData struct mapped to that publication.
|
||||
*/
|
||||
function getPublicationData(uint256 profileId, uint256 pubId)
|
||||
external
|
||||
view
|
||||
returns (ProfilePublicationData memory)
|
||||
{
|
||||
return _dataByPublicationByProfile[profileId][pubId];
|
||||
}
|
||||
|
||||
function _processCollect(
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
(address treasury, uint16 treasuryFee) = _treasuryData();
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
uint256 treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
uint256 referrerProfileId,
|
||||
address collector,
|
||||
uint256 profileId,
|
||||
uint256 pubId,
|
||||
bytes calldata data
|
||||
) internal {
|
||||
uint256 amount = _dataByPublicationByProfile[profileId][pubId].amount;
|
||||
address currency = _dataByPublicationByProfile[profileId][pubId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
uint256 referralFee = _dataByPublicationByProfile[profileId][pubId].referralFee;
|
||||
address treasury;
|
||||
uint256 treasuryAmount;
|
||||
|
||||
// Avoids stack too deep
|
||||
{
|
||||
uint16 treasuryFee;
|
||||
(treasury, treasuryFee) = _treasuryData();
|
||||
treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
}
|
||||
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
if (referralFee != 0) {
|
||||
// The reason we levy the referral fee on the adjusted amount is so that referral fees
|
||||
// don't bypass the treasury fee, in essence referrals pay their fair share to the treasury.
|
||||
uint256 referralAmount = (adjustedAmount * referralFee) / BPS_MAX;
|
||||
adjustedAmount = adjustedAmount - referralAmount;
|
||||
|
||||
address referralRecipient = IERC721(HUB).ownerOf(referrerProfileId);
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, referralRecipient, referralAmount);
|
||||
}
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {Events} from '../../../../libraries/Events.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {DeprecatedFollowValidatorFollowModuleBase} from './DeprecatedFollowValidatorFollowModuleBase.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @title ApprovalFollowModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This follow module only allows addresses that are approved for a profile by the profile owner to follow.
|
||||
*/
|
||||
contract DeprecatedApprovalFollowModule is DeprecatedFollowValidatorFollowModuleBase {
|
||||
// We use a triple nested mapping so that, on profile transfer, the previous approved address list is invalid;
|
||||
mapping(address => mapping(uint256 => mapping(address => bool)))
|
||||
internal _approvedByProfileByOwner;
|
||||
|
||||
constructor(address hub) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice A custom function that allows profile owners to customize approved addresses.
|
||||
*
|
||||
* @param profileId The profile ID to approve/disapprove follower addresses for.
|
||||
* @param addresses The addresses to approve/disapprove for following the profile.
|
||||
* @param toApprove Whether to approve or disapprove the addresses for following the profile.
|
||||
*/
|
||||
function approve(
|
||||
uint256 profileId,
|
||||
address[] calldata addresses,
|
||||
bool[] calldata toApprove
|
||||
) external {
|
||||
if (addresses.length != toApprove.length) revert Errors.InitParamsInvalid();
|
||||
address owner = IERC721(HUB).ownerOf(profileId);
|
||||
if (msg.sender != owner) revert Errors.NotProfileOwner();
|
||||
|
||||
uint256 addressesLength = addresses.length;
|
||||
for (uint256 i = 0; i < addressesLength; ) {
|
||||
_approvedByProfileByOwner[owner][profileId][addresses[i]] = toApprove[i];
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
emit Events.FollowsApproved(owner, profileId, addresses, toApprove, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This follow module works on custom profile owner approvals.
|
||||
*
|
||||
* @param profileId The profile ID of the profile to initialize this module for.
|
||||
* @param data The arbitrary data parameter, decoded into:
|
||||
* address[] addresses: The array of addresses to approve initially.
|
||||
*
|
||||
* @return bytes An abi encoded bytes parameter, which is the same as the passed data parameter.
|
||||
*/
|
||||
function initializeFollowModule(uint256 profileId, bytes calldata data)
|
||||
external
|
||||
override
|
||||
onlyHub
|
||||
returns (bytes memory)
|
||||
{
|
||||
address owner = IERC721(HUB).ownerOf(profileId);
|
||||
|
||||
if (data.length > 0) {
|
||||
address[] memory addresses = abi.decode(data, (address[]));
|
||||
uint256 addressesLength = addresses.length;
|
||||
for (uint256 i = 0; i < addressesLength; ) {
|
||||
_approvedByProfileByOwner[owner][profileId][addresses[i]] = true;
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a follow by:
|
||||
* 1. Validating that the follower has been approved for that profile by the profile owner
|
||||
*/
|
||||
function processFollow(
|
||||
address follower,
|
||||
uint256 profileId,
|
||||
bytes calldata
|
||||
) external override onlyHub {
|
||||
address owner = IERC721(HUB).ownerOf(profileId);
|
||||
if (!_approvedByProfileByOwner[owner][profileId][follower])
|
||||
revert Errors.FollowNotApproved();
|
||||
_approvedByProfileByOwner[owner][profileId][follower] = false; // prevents repeat follows
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev We don't need to execute any additional logic on transfers in this follow module.
|
||||
*/
|
||||
function followModuleTransferHook(
|
||||
uint256 profileId,
|
||||
address from,
|
||||
address to,
|
||||
uint256 followNFTTokenId
|
||||
) external override {}
|
||||
|
||||
/**
|
||||
* @notice Returns whether the given address is approved for the profile owned by a given address.
|
||||
*
|
||||
* @param profileOwner The profile owner of the profile to query the approval with.
|
||||
* @param profileId The token ID of the profile to query approval with.
|
||||
* @param toCheck The address to query approval for.
|
||||
*
|
||||
* @return bool True if the address is approved and false otherwise.
|
||||
*/
|
||||
function isApproved(
|
||||
address profileOwner,
|
||||
uint256 profileId,
|
||||
address toCheck
|
||||
) external view returns (bool) {
|
||||
return _approvedByProfileByOwner[profileOwner][profileId][toCheck];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns whether the given addresses are approved for the profile owned by a given address.
|
||||
*
|
||||
* @param profileOwner The profile owner of the profile to query the approvals with.
|
||||
* @param profileId The token ID of the profile to query approvals with.
|
||||
* @param toCheck The address array to query approvals for.
|
||||
*
|
||||
* @return bool[] true if the address at the specified index is approved and false otherwise.
|
||||
*/
|
||||
function isApprovedArray(
|
||||
address profileOwner,
|
||||
uint256 profileId,
|
||||
address[] calldata toCheck
|
||||
) external view returns (bool[] memory) {
|
||||
bool[] memory approved = new bool[](toCheck.length);
|
||||
uint256 toCheckLength = toCheck.length;
|
||||
for (uint256 i = 0; i < toCheckLength; ) {
|
||||
approved[i] = _approvedByProfileByOwner[profileOwner][profileId][toCheck[i]];
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return approved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {ILensHub} from '../../../../interfaces/ILensHub.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {FeeModuleBase} from '../../FeeModuleBase.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {DeprecatedFollowValidatorFollowModuleBase} from './DeprecatedFollowValidatorFollowModuleBase.sol';
|
||||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @notice A struct containing the necessary data to execute follow actions on a given profile.
|
||||
*
|
||||
* @param currency The currency associated with this profile.
|
||||
* @param amount The following cost associated with this profile.
|
||||
* @param recipient The recipient address associated with this profile.
|
||||
*/
|
||||
struct ProfileData {
|
||||
address currency;
|
||||
uint256 amount;
|
||||
address recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title FeeFollowModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is a simple Lens FollowModule implementation, inheriting from the IFollowModule interface, but with additional
|
||||
* variables that can be controlled by governance, such as the governance & treasury addresses as well as the treasury fee.
|
||||
*/
|
||||
contract DeprecatedFeeFollowModule is FeeModuleBase, DeprecatedFollowValidatorFollowModuleBase {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
mapping(uint256 => ProfileData) internal _dataByProfile;
|
||||
|
||||
constructor(address hub, address moduleGlobals) FeeModuleBase(moduleGlobals) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This follow module levies a fee on follows.
|
||||
*
|
||||
* @param profileId The profile ID of the profile to initialize this module for.
|
||||
* @param data The arbitrary data parameter, decoded into:
|
||||
* address currency: The currency address, must be internally whitelisted.
|
||||
* uint256 amount: The currency total amount to levy.
|
||||
* address recipient: The custom recipient address to direct earnings to.
|
||||
*
|
||||
* @return bytes An abi encoded bytes parameter, which is the same as the passed data parameter.
|
||||
*/
|
||||
function initializeFollowModule(uint256 profileId, bytes calldata data)
|
||||
external
|
||||
override
|
||||
onlyHub
|
||||
returns (bytes memory)
|
||||
{
|
||||
(uint256 amount, address currency, address recipient) = abi.decode(
|
||||
data,
|
||||
(uint256, address, address)
|
||||
);
|
||||
if (!_currencyWhitelisted(currency) || recipient == address(0) || amount == 0)
|
||||
revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByProfile[profileId].amount = amount;
|
||||
_dataByProfile[profileId].currency = currency;
|
||||
_dataByProfile[profileId].recipient = recipient;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a follow by:
|
||||
* 1. Charging a fee
|
||||
*/
|
||||
function processFollow(
|
||||
address follower,
|
||||
uint256 profileId,
|
||||
bytes calldata data
|
||||
) external override onlyHub {
|
||||
uint256 amount = _dataByProfile[profileId].amount;
|
||||
address currency = _dataByProfile[profileId].currency;
|
||||
_validateDataIsExpected(data, currency, amount);
|
||||
|
||||
(address treasury, uint16 treasuryFee) = _treasuryData();
|
||||
address recipient = _dataByProfile[profileId].recipient;
|
||||
uint256 treasuryAmount = (amount * treasuryFee) / BPS_MAX;
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(follower, recipient, adjustedAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(follower, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev We don't need to execute any additional logic on transfers in this follow module.
|
||||
*/
|
||||
function followModuleTransferHook(
|
||||
uint256 profileId,
|
||||
address from,
|
||||
address to,
|
||||
uint256 followNFTTokenId
|
||||
) external override {}
|
||||
|
||||
/**
|
||||
* @notice Returns the profile data for a given profile, or an empty struct if that profile was not initialized
|
||||
* with this module.
|
||||
*
|
||||
* @param profileId The token ID of the profile to query.
|
||||
*
|
||||
* @return ProfileData The ProfileData struct mapped to that profile.
|
||||
*/
|
||||
function getProfileData(uint256 profileId) external view returns (ProfileData memory) {
|
||||
return _dataByProfile[profileId];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedFollowModule} from '../../../../interfaces/IDeprecatedFollowModule.sol';
|
||||
import {ILensHub} from '../../../../interfaces/ILensHub.sol';
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @title FollowValidatorFollowModuleBase
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This abstract contract adds the default expected behavior for follow validation in a follow module
|
||||
* to inheriting contracts.
|
||||
*/
|
||||
abstract contract DeprecatedFollowValidatorFollowModuleBase is ModuleBase, IDeprecatedFollowModule {
|
||||
/**
|
||||
* @notice Standard function to validate follow NFT ownership. This module is agnostic to follow NFT token IDs
|
||||
* and other properties.
|
||||
*/
|
||||
function isFollowing(
|
||||
uint256 profileId,
|
||||
address follower,
|
||||
uint256 followNFTTokenId
|
||||
) external view override returns (bool) {
|
||||
address followNFT = ILensHub(HUB).getFollowNFT(profileId);
|
||||
if (followNFT == address(0)) {
|
||||
return false;
|
||||
} else {
|
||||
return
|
||||
followNFTTokenId == 0
|
||||
? IERC721(followNFT).balanceOf(follower) != 0
|
||||
: IERC721(followNFT).ownerOf(followNFTTokenId) == follower;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {DeprecatedFollowValidatorFollowModuleBase} from './DeprecatedFollowValidatorFollowModuleBase.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @title ProfileFollowModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice A Lens Profile NFT token-gated follow module with single follow per token validation.
|
||||
*/
|
||||
contract DeprecatedProfileFollowModule is DeprecatedFollowValidatorFollowModuleBase {
|
||||
/**
|
||||
* Given two profile IDs tells if the former has already been used to follow the latter.
|
||||
*/
|
||||
mapping(uint256 => mapping(uint256 => bool)) public isProfileFollowing;
|
||||
|
||||
constructor(address hub) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This follow module allows users to follow using a profile once.
|
||||
*
|
||||
* @return bytes Empty bytes.
|
||||
*/
|
||||
function initializeFollowModule(uint256, bytes calldata)
|
||||
external
|
||||
view
|
||||
override
|
||||
onlyHub
|
||||
returns (bytes memory)
|
||||
{
|
||||
return new bytes(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a follow by:
|
||||
* 1. Validating that the follower owns the profile passed through the data param.
|
||||
* 2. Validating that the profile that is being used to execute the follow was not already used for following the
|
||||
* given profile.
|
||||
*/
|
||||
function processFollow(
|
||||
address follower,
|
||||
uint256 profileId,
|
||||
bytes calldata data
|
||||
) external override onlyHub {
|
||||
uint256 followerProfileId = abi.decode(data, (uint256));
|
||||
if (IERC721(HUB).ownerOf(followerProfileId) != follower) {
|
||||
revert Errors.NotProfileOwner();
|
||||
}
|
||||
if (isProfileFollowing[followerProfileId][profileId]) {
|
||||
revert Errors.FollowInvalid();
|
||||
} else {
|
||||
isProfileFollowing[followerProfileId][profileId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev We don't need to execute any additional logic on transfers in this follow module.
|
||||
*/
|
||||
function followModuleTransferHook(
|
||||
uint256 profileId,
|
||||
address from,
|
||||
address to,
|
||||
uint256 followNFTTokenId
|
||||
) external override {}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {Errors} from '../../../../libraries/Errors.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {DeprecatedFollowValidatorFollowModuleBase} from './DeprecatedFollowValidatorFollowModuleBase.sol';
|
||||
|
||||
/**
|
||||
* @title RevertFollowModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This follow module rejects all follow attempts.
|
||||
*/
|
||||
contract DeprecatedRevertFollowModule is DeprecatedFollowValidatorFollowModuleBase {
|
||||
constructor(address hub) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @notice This follow module always reverts.
|
||||
*
|
||||
* @return bytes Empty bytes.
|
||||
*/
|
||||
function initializeFollowModule(uint256, bytes calldata)
|
||||
external
|
||||
view
|
||||
override
|
||||
onlyHub
|
||||
returns (bytes memory)
|
||||
{
|
||||
return new bytes(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a follow by rejecting it reverting the transaction.
|
||||
*/
|
||||
function processFollow(
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external view override onlyHub {
|
||||
revert Errors.FollowInvalid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev We don't need to execute any additional logic on transfers in this follow module.
|
||||
*/
|
||||
function followModuleTransferHook(
|
||||
uint256 profileId,
|
||||
address from,
|
||||
address to,
|
||||
uint256 followNFTTokenId
|
||||
) external override {}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IDeprecatedReferenceModule} from '../../../../interfaces/IDeprecatedReferenceModule.sol';
|
||||
import {ModuleBase} from '../../ModuleBase.sol';
|
||||
import {FollowValidationModuleBase} from '../../FollowValidationModuleBase.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
|
||||
/**
|
||||
* @title FollowerOnlyReferenceModule
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice A simple reference module that validates that comments or mirrors originate from a profile owned
|
||||
* by a follower.
|
||||
*/
|
||||
contract DeprecatedFollowerOnlyReferenceModule is FollowValidationModuleBase, IDeprecatedReferenceModule {
|
||||
constructor(address hub) ModuleBase(hub) {}
|
||||
|
||||
/**
|
||||
* @dev There is nothing needed at initialization.
|
||||
*/
|
||||
function initializeReferenceModule(
|
||||
uint256,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external pure override returns (bytes memory) {
|
||||
return new bytes(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Validates that the commenting profile's owner is a follower.
|
||||
*
|
||||
* NOTE: We don't need to care what the pointed publication is in this context.
|
||||
*/
|
||||
function processComment(
|
||||
uint256 profileId,
|
||||
uint256 profileIdPointed,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external view override {
|
||||
address commentCreator = IERC721(HUB).ownerOf(profileId);
|
||||
_checkFollowValidity(profileIdPointed, commentCreator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Validates that the commenting profile's owner is a follower.
|
||||
*
|
||||
* NOTE: We don't need to care what the pointed publication is in this context.
|
||||
*/
|
||||
function processMirror(
|
||||
uint256 profileId,
|
||||
uint256 profileIdPointed,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external view override {
|
||||
address mirrorCreator = IERC721(HUB).ownerOf(profileId);
|
||||
_checkFollowValidity(profileIdPointed, mirrorCreator);
|
||||
}
|
||||
}
|
||||
@@ -846,7 +846,6 @@ library GeneralLib {
|
||||
collectModuleInitData
|
||||
);
|
||||
|
||||
// Reference module initialization
|
||||
bytes memory referenceModuleReturnData = _initPubReferenceModule(
|
||||
profileId,
|
||||
executor,
|
||||
|
||||
Reference in New Issue
Block a user