mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-09 14:18:04 -05:00
feat: ProtocolSharedRevenueMinFeeMintModule
This commit is contained in:
@@ -34,7 +34,7 @@ abstract contract FeeModuleBase {
|
|||||||
return HUB.getTreasuryData();
|
return HUB.getTreasuryData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _validateDataIsExpected(bytes calldata data, address currency, uint256 amount) internal pure {
|
function _validateDataIsExpected(bytes calldata data, address currency, uint256 amount) internal pure virtual {
|
||||||
(address decodedCurrency, uint256 decodedAmount) = abi.decode(data, (address, uint256));
|
(address decodedCurrency, uint256 decodedAmount) = abi.decode(data, (address, uint256));
|
||||||
if (decodedAmount != amount || decodedCurrency != currency) {
|
if (decodedAmount != amount || decodedCurrency != currency) {
|
||||||
revert Errors.ModuleDataMismatch();
|
revert Errors.ModuleDataMismatch();
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -245,7 +245,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, passedAmount)
|
data: _getCollectParamsData(address(currency), passedAmount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(passedCurrency, amount)
|
data: _getCollectParamsData(passedCurrency, exampleInitData.amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, exampleInitData.amount)
|
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -378,7 +378,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, exampleInitData.amount)
|
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -422,7 +422,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, exampleInitData.amount)
|
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -439,7 +439,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, exampleInitData.amount)
|
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -517,7 +517,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, amount)
|
data: _getCollectParamsData(address(currency), amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -563,7 +563,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
referrerProfileIds: _emptyUint256Array(),
|
referrerProfileIds: _emptyUint256Array(),
|
||||||
referrerPubIds: _emptyUint256Array(),
|
referrerPubIds: _emptyUint256Array(),
|
||||||
referrerPubTypes: _emptyPubTypesArray(),
|
referrerPubTypes: _emptyPubTypesArray(),
|
||||||
data: abi.encode(currency, exampleInitData.amount)
|
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -571,6 +571,10 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
|||||||
assertEq(fetchedData.currentCollects, collects);
|
assertEq(fetchedData.currentCollects, collects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getCollectParamsData(address currency, uint160 amount) internal virtual returns (bytes memory) {
|
||||||
|
return abi.encode(currency, amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contract BaseFeeCollectModule_FeeDistribution is BaseFeeCollectModuleBase {
|
contract BaseFeeCollectModule_FeeDistribution is BaseFeeCollectModuleBase {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ contract MultirecipientCollectModule_Initialization is
|
|||||||
return MultirecipientCollectModuleBase.getEncodedInitData();
|
return MultirecipientCollectModuleBase.getEncodedInitData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: WTF?
|
||||||
function testCannotInitializeWithNonWhitelistedCurrency(
|
function testCannotInitializeWithNonWhitelistedCurrency(
|
||||||
uint256 profileId,
|
uint256 profileId,
|
||||||
uint256 pubId,
|
uint256 pubId,
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.10;
|
||||||
|
|
||||||
|
import 'forge-std/Test.sol';
|
||||||
|
import {ProtocolSharedRevenueDistribution, ProtocolSharedRevenueMinFeeMintModule, ProtocolSharedRevenueMinFeeMintModuleInitData} from 'contracts/modules/act/collect/ProtocolSharedRevenueMinFeeMintModule.sol';
|
||||||
|
import {BaseFeeCollectModuleBase} from 'test/modules/act/collect/BaseFeeCollectModule.base.t.sol';
|
||||||
|
import {MockCurrency} from 'test/mocks/MockCurrency.sol';
|
||||||
|
|
||||||
|
contract ProtocolSharedRevenueMinFeeMintModuleBase is BaseFeeCollectModuleBase {
|
||||||
|
function testProtocolSharedRevenueMinFeeMintModuleBase() public {
|
||||||
|
// Prevents being counted in Foundry Coverage
|
||||||
|
}
|
||||||
|
|
||||||
|
using stdJson for string;
|
||||||
|
|
||||||
|
uint16 constant BPS_MAX = 10000;
|
||||||
|
|
||||||
|
address creatorFrontendAddress = makeAddr('CREATOR_FRONTEND');
|
||||||
|
address executorFrontendAddress = makeAddr('EXECUTOR_FRONTEND');
|
||||||
|
|
||||||
|
MockCurrency bonsai;
|
||||||
|
uint256 mintFee = 10 ether;
|
||||||
|
ProtocolSharedRevenueMinFeeMintModule mintFeeModule;
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleInitData mintFeeModuleExampleInitData;
|
||||||
|
|
||||||
|
function setUp() public virtual override {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
// Deploy & Whitelist ProtocolSharedRevenueMinFeeMintModule
|
||||||
|
if (fork && keyExists(json, string(abi.encodePacked('.', forkEnv, '.ProtocolSharedRevenueMinFeeMintModule')))) {
|
||||||
|
mintFeeModule = ProtocolSharedRevenueMinFeeMintModule(
|
||||||
|
json.readAddress(string(abi.encodePacked('.', forkEnv, '.ProtocolSharedRevenueMinFeeMintModule')))
|
||||||
|
);
|
||||||
|
console.log('Testing against already deployed module at:', address(mintFeeModule));
|
||||||
|
} else {
|
||||||
|
vm.prank(deployer);
|
||||||
|
mintFeeModule = new ProtocolSharedRevenueMinFeeMintModule(
|
||||||
|
address(hub),
|
||||||
|
collectPublicationAction,
|
||||||
|
address(moduleRegistry),
|
||||||
|
address(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFeeCollectModule = address(mintFeeModule);
|
||||||
|
if (address(currency) == address(0)) {
|
||||||
|
currency = new MockCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
bonsai = new MockCurrency();
|
||||||
|
|
||||||
|
vm.startPrank(mintFeeModule.owner());
|
||||||
|
mintFeeModule.setMintFeeParams(address(bonsai), mintFee);
|
||||||
|
mintFeeModule.setProtocolSharedRevenueDistribution(
|
||||||
|
ProtocolSharedRevenueDistribution({
|
||||||
|
creatorSplit: 5000,
|
||||||
|
protocolSplit: 2000,
|
||||||
|
creatorFrontendSplit: 1500,
|
||||||
|
executorFrontendSplit: 1500
|
||||||
|
})
|
||||||
|
);
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEncodedInitData() internal virtual override returns (bytes memory) {
|
||||||
|
mintFeeModuleExampleInitData.amount = exampleInitData.amount;
|
||||||
|
mintFeeModuleExampleInitData.collectLimit = exampleInitData.collectLimit;
|
||||||
|
mintFeeModuleExampleInitData.currency = exampleInitData.currency;
|
||||||
|
mintFeeModuleExampleInitData.referralFee = exampleInitData.referralFee;
|
||||||
|
mintFeeModuleExampleInitData.followerOnly = exampleInitData.followerOnly;
|
||||||
|
mintFeeModuleExampleInitData.endTimestamp = exampleInitData.endTimestamp;
|
||||||
|
mintFeeModuleExampleInitData.recipient = exampleInitData.recipient;
|
||||||
|
mintFeeModuleExampleInitData.creatorFrontend = creatorFrontendAddress;
|
||||||
|
|
||||||
|
return abi.encode(mintFeeModuleExampleInitData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,612 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.10;
|
||||||
|
|
||||||
|
import 'forge-std/Test.sol';
|
||||||
|
import {ProtocolSharedRevenueMinFeeMintModuleBase} from 'test/modules/act/collect/ProtocolSharedRevenueMinFeeMintModule.base.t.sol';
|
||||||
|
import {IBaseFeeCollectModule} from 'contracts/modules/interfaces/IBaseFeeCollectModule.sol';
|
||||||
|
import {BaseFeeCollectModule_Initialization, BaseFeeCollectModule_ProcessCollect, BaseFeeCollectModule_FeeDistribution} from 'test/modules/act/collect/BaseFeeCollectModule.t.sol';
|
||||||
|
import {BaseFeeCollectModuleBase} from 'test/modules/act/collect/BaseFeeCollectModule.base.t.sol';
|
||||||
|
import {ProtocolSharedRevenueDistribution, ProtocolSharedRevenueMinFeeMintModule, ProtocolSharedRevenueMinFeeMintModulePublicationData} from 'contracts/modules/act/collect/ProtocolSharedRevenueMinFeeMintModule.sol';
|
||||||
|
import {Errors as ModuleErrors} from 'contracts/modules/constants/Errors.sol';
|
||||||
|
import {MockCurrency} from 'test/mocks/MockCurrency.sol';
|
||||||
|
import {ModuleTypes} from 'contracts/modules/libraries/constants/ModuleTypes.sol';
|
||||||
|
|
||||||
|
/////////
|
||||||
|
// Publication Creation with ProtocolSharedRevenueMinFeeMintModule
|
||||||
|
//
|
||||||
|
contract ProtocolSharedRevenueMinFeeMintModule_Initialization is
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleBase,
|
||||||
|
BaseFeeCollectModule_Initialization
|
||||||
|
{
|
||||||
|
function setUp() public override(ProtocolSharedRevenueMinFeeMintModuleBase, BaseFeeCollectModuleBase) {
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleBase.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEncodedInitData()
|
||||||
|
internal
|
||||||
|
override(ProtocolSharedRevenueMinFeeMintModuleBase, BaseFeeCollectModuleBase)
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
return ProtocolSharedRevenueMinFeeMintModuleBase.getEncodedInitData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negatives
|
||||||
|
|
||||||
|
// TODO: WTF?
|
||||||
|
function testCannotInitializeWithNonWhitelistedCurrency(
|
||||||
|
uint256 profileId,
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor
|
||||||
|
) public {
|
||||||
|
vm.assume(profileId != 0);
|
||||||
|
vm.assume(pubId != 0);
|
||||||
|
vm.assume(transactionExecutor != address(0));
|
||||||
|
|
||||||
|
exampleInitData.amount = 0;
|
||||||
|
|
||||||
|
vm.expectRevert(ModuleErrors.InitParamsInvalid.selector);
|
||||||
|
vm.prank(collectPublicationAction);
|
||||||
|
IBaseFeeCollectModule(baseFeeCollectModule).initializePublicationCollectModule(
|
||||||
|
profileId,
|
||||||
|
pubId,
|
||||||
|
transactionExecutor,
|
||||||
|
getEncodedInitData()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios
|
||||||
|
|
||||||
|
function testInitializeWithCorrectInitData(
|
||||||
|
uint256 profileId,
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint160 amount,
|
||||||
|
uint96 collectLimit,
|
||||||
|
address whitelistedCurrency,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient
|
||||||
|
) public override {}
|
||||||
|
|
||||||
|
function testInitializeWithCorrectInitData(
|
||||||
|
uint256 profileId,
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint160 amount,
|
||||||
|
uint96 collectLimit,
|
||||||
|
address whitelistedCurrency,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient,
|
||||||
|
address creatorFrontend
|
||||||
|
) public {
|
||||||
|
vm.assume(profileId != 0);
|
||||||
|
vm.assume(pubId != 0);
|
||||||
|
vm.assume(amount != 0);
|
||||||
|
vm.assume(transactionExecutor != address(0));
|
||||||
|
vm.assume(whitelistedCurrency != address(0));
|
||||||
|
|
||||||
|
if (endTimestamp > 0) {
|
||||||
|
currentTimestamp = uint72(bound(uint256(currentTimestamp), 0, uint256(endTimestamp) - 1));
|
||||||
|
}
|
||||||
|
vm.warp(currentTimestamp);
|
||||||
|
|
||||||
|
mintFeeModuleExampleInitData.amount = amount;
|
||||||
|
mintFeeModuleExampleInitData.collectLimit = collectLimit;
|
||||||
|
mintFeeModuleExampleInitData.currency = whitelistedCurrency;
|
||||||
|
mintFeeModuleExampleInitData.referralFee = uint16(bound(uint256(referralFee), 0, BPS_MAX));
|
||||||
|
mintFeeModuleExampleInitData.followerOnly = followerOnly;
|
||||||
|
mintFeeModuleExampleInitData.endTimestamp = endTimestamp;
|
||||||
|
mintFeeModuleExampleInitData.recipient = recipient;
|
||||||
|
mintFeeModuleExampleInitData.creatorFrontend = creatorFrontend;
|
||||||
|
|
||||||
|
vm.prank(collectPublicationAction);
|
||||||
|
IBaseFeeCollectModule(baseFeeCollectModule).initializePublicationCollectModule(
|
||||||
|
profileId,
|
||||||
|
pubId,
|
||||||
|
transactionExecutor,
|
||||||
|
getEncodedInitData()
|
||||||
|
);
|
||||||
|
|
||||||
|
ProtocolSharedRevenueMinFeeMintModulePublicationData memory fetchedData = ProtocolSharedRevenueMinFeeMintModule(
|
||||||
|
baseFeeCollectModule
|
||||||
|
).getPublicationData(profileId, pubId);
|
||||||
|
assertEq(fetchedData.currency, mintFeeModuleExampleInitData.currency, 'MockCurrency initialization mismatch');
|
||||||
|
assertEq(fetchedData.amount, mintFeeModuleExampleInitData.amount, 'Amount initialization mismatch');
|
||||||
|
assertEq(
|
||||||
|
fetchedData.referralFee,
|
||||||
|
mintFeeModuleExampleInitData.referralFee,
|
||||||
|
'Referral fee initialization mismatch'
|
||||||
|
);
|
||||||
|
assertEq(
|
||||||
|
fetchedData.followerOnly,
|
||||||
|
mintFeeModuleExampleInitData.followerOnly,
|
||||||
|
'Follower only initialization mismatch'
|
||||||
|
);
|
||||||
|
assertEq(
|
||||||
|
fetchedData.endTimestamp,
|
||||||
|
mintFeeModuleExampleInitData.endTimestamp,
|
||||||
|
'End timestamp initialization mismatch'
|
||||||
|
);
|
||||||
|
assertEq(
|
||||||
|
fetchedData.collectLimit,
|
||||||
|
mintFeeModuleExampleInitData.collectLimit,
|
||||||
|
'Collect limit initialization mismatch'
|
||||||
|
);
|
||||||
|
assertEq(
|
||||||
|
fetchedData.creatorFrontend,
|
||||||
|
mintFeeModuleExampleInitData.creatorFrontend,
|
||||||
|
'CreatorFrontend initialization mismatch'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Collect with ProtocolSharedRevenueMinFeeMintModule
|
||||||
|
//
|
||||||
|
contract ProtocolSharedRevenueMinFeeMintModule_ProcessCollect is
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleBase,
|
||||||
|
BaseFeeCollectModule_ProcessCollect
|
||||||
|
{
|
||||||
|
function testProtocolSharedRevenueMinFeeMintModule_Collect() public {
|
||||||
|
// Prevents being counted in Foundry Coverage
|
||||||
|
}
|
||||||
|
|
||||||
|
address exampleExecutorFrontend = executorFrontendAddress;
|
||||||
|
|
||||||
|
function setUp() public override(ProtocolSharedRevenueMinFeeMintModuleBase, BaseFeeCollectModuleBase) {
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleBase.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEncodedInitData()
|
||||||
|
internal
|
||||||
|
override(ProtocolSharedRevenueMinFeeMintModuleBase, BaseFeeCollectModuleBase)
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
return ProtocolSharedRevenueMinFeeMintModuleBase.getEncodedInitData();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getCollectParamsData(address currency, uint160 amount) internal override returns (bytes memory) {
|
||||||
|
return abi.encode(currency, amount, exampleExecutorFrontend);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios
|
||||||
|
|
||||||
|
function testCanCollectIfAllConditionsAreMet(
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint160 amount,
|
||||||
|
uint96 collectLimit,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient,
|
||||||
|
address collectorProfileOwner
|
||||||
|
) public override {}
|
||||||
|
|
||||||
|
function testCanCollectIfAllConditionsAreMet(
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint160 amount,
|
||||||
|
uint96 collectLimit,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient
|
||||||
|
) public {
|
||||||
|
address collectorProfileOwner = makeAddr('COLLECTOR_PROFILE_OWNER');
|
||||||
|
address executorFrontend = makeAddr('EXECUTOR_FRONTEND');
|
||||||
|
exampleExecutorFrontend = executorFrontend;
|
||||||
|
|
||||||
|
bonsai.mint(collectorProfileOwner, 10 ether);
|
||||||
|
|
||||||
|
vm.prank(collectorProfileOwner);
|
||||||
|
bonsai.approve(baseFeeCollectModule, 10 ether);
|
||||||
|
|
||||||
|
super.testCanCollectIfAllConditionsAreMet(
|
||||||
|
pubId,
|
||||||
|
transactionExecutor,
|
||||||
|
amount,
|
||||||
|
collectLimit,
|
||||||
|
referralFee,
|
||||||
|
followerOnly,
|
||||||
|
currentTimestamp,
|
||||||
|
endTimestamp,
|
||||||
|
recipient,
|
||||||
|
collectorProfileOwner
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Balances {
|
||||||
|
uint256 creator;
|
||||||
|
uint256 protocol;
|
||||||
|
uint256 creatorFrontend;
|
||||||
|
uint256 executorFrontend;
|
||||||
|
uint256 collector;
|
||||||
|
}
|
||||||
|
|
||||||
|
Balances balancesBefore;
|
||||||
|
Balances balancesAfter;
|
||||||
|
Balances balancesChange;
|
||||||
|
|
||||||
|
function testMintFeeDistribution_FreePost(
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint96 collectLimit,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient
|
||||||
|
) public {
|
||||||
|
address collectorProfileOwner = makeAddr('COLLECTOR_PROFILE_OWNER');
|
||||||
|
address executorFrontend = makeAddr('EXECUTOR_FRONTEND');
|
||||||
|
|
||||||
|
bonsai.mint(collectorProfileOwner, 10 ether);
|
||||||
|
|
||||||
|
vm.prank(collectorProfileOwner);
|
||||||
|
bonsai.approve(baseFeeCollectModule, 10 ether);
|
||||||
|
|
||||||
|
balancesBefore = Balances({
|
||||||
|
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||||
|
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||||
|
creatorFrontend: bonsai.balanceOf(creatorFrontendAddress),
|
||||||
|
executorFrontend: bonsai.balanceOf(executorFrontend),
|
||||||
|
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||||
|
});
|
||||||
|
|
||||||
|
exampleExecutorFrontend = executorFrontend;
|
||||||
|
super.testCanCollectIfAllConditionsAreMet(
|
||||||
|
pubId,
|
||||||
|
transactionExecutor,
|
||||||
|
0,
|
||||||
|
collectLimit,
|
||||||
|
referralFee,
|
||||||
|
followerOnly,
|
||||||
|
currentTimestamp,
|
||||||
|
endTimestamp,
|
||||||
|
recipient,
|
||||||
|
collectorProfileOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
balancesAfter = Balances({
|
||||||
|
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||||
|
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||||
|
creatorFrontend: bonsai.balanceOf(creatorFrontendAddress),
|
||||||
|
executorFrontend: bonsai.balanceOf(executorFrontend),
|
||||||
|
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||||
|
});
|
||||||
|
|
||||||
|
balancesChange = Balances({
|
||||||
|
creator: balancesAfter.creator - balancesBefore.creator,
|
||||||
|
protocol: balancesAfter.protocol - balancesBefore.protocol,
|
||||||
|
creatorFrontend: balancesAfter.creatorFrontend - balancesBefore.creatorFrontend,
|
||||||
|
executorFrontend: balancesAfter.executorFrontend - balancesBefore.executorFrontend,
|
||||||
|
collector: balancesBefore.collector - balancesAfter.collector
|
||||||
|
});
|
||||||
|
|
||||||
|
uint256 expectedCreatorFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().creatorSplit) /
|
||||||
|
BPS_MAX;
|
||||||
|
uint256 expectedProtocolFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().protocolSplit) /
|
||||||
|
BPS_MAX;
|
||||||
|
uint256 expectedCreatorFrontendFee = (mintFee *
|
||||||
|
mintFeeModule.getProtocolSharedRevenueDistribution().creatorFrontendSplit) / BPS_MAX;
|
||||||
|
uint256 expectedExecutorFrontendFee = (mintFee *
|
||||||
|
mintFeeModule.getProtocolSharedRevenueDistribution().executorFrontendSplit) / BPS_MAX;
|
||||||
|
|
||||||
|
assertEq(balancesChange.creator, expectedCreatorFee, 'Creator balance change wrong');
|
||||||
|
assertEq(balancesChange.protocol, expectedProtocolFee, 'Protocol balance change wrong');
|
||||||
|
assertEq(balancesChange.creatorFrontend, expectedCreatorFrontendFee, 'CreatorFrontend balance change wrong');
|
||||||
|
assertEq(balancesChange.executorFrontend, expectedExecutorFrontendFee, 'ExecutorFrontend balance change wrong');
|
||||||
|
assertEq(balancesChange.collector, mintFee, 'Collector balance change wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMintFeeDistribution_FreePost_WithoutFrontends(
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint96 collectLimit,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient
|
||||||
|
) public {
|
||||||
|
address collectorProfileOwner = makeAddr('COLLECTOR_PROFILE_OWNER');
|
||||||
|
|
||||||
|
address executorFrontend = address(0);
|
||||||
|
creatorFrontendAddress = address(0);
|
||||||
|
|
||||||
|
bonsai.mint(collectorProfileOwner, 10 ether);
|
||||||
|
|
||||||
|
vm.prank(collectorProfileOwner);
|
||||||
|
bonsai.approve(baseFeeCollectModule, 10 ether);
|
||||||
|
|
||||||
|
balancesBefore = Balances({
|
||||||
|
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||||
|
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||||
|
creatorFrontend: bonsai.balanceOf(creatorFrontendAddress),
|
||||||
|
executorFrontend: bonsai.balanceOf(executorFrontend),
|
||||||
|
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('creatorFrontend balance before: %s', balancesBefore.creatorFrontend);
|
||||||
|
|
||||||
|
exampleExecutorFrontend = executorFrontend;
|
||||||
|
super.testCanCollectIfAllConditionsAreMet(
|
||||||
|
pubId,
|
||||||
|
transactionExecutor,
|
||||||
|
0,
|
||||||
|
collectLimit,
|
||||||
|
referralFee,
|
||||||
|
followerOnly,
|
||||||
|
currentTimestamp,
|
||||||
|
endTimestamp,
|
||||||
|
recipient,
|
||||||
|
collectorProfileOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
balancesAfter = Balances({
|
||||||
|
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||||
|
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||||
|
creatorFrontend: bonsai.balanceOf(creatorFrontendAddress),
|
||||||
|
executorFrontend: bonsai.balanceOf(executorFrontend),
|
||||||
|
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||||
|
});
|
||||||
|
|
||||||
|
balancesChange = Balances({
|
||||||
|
creator: balancesAfter.creator - balancesBefore.creator,
|
||||||
|
protocol: balancesAfter.protocol - balancesBefore.protocol,
|
||||||
|
creatorFrontend: balancesAfter.creatorFrontend - balancesBefore.creatorFrontend,
|
||||||
|
executorFrontend: balancesAfter.executorFrontend - balancesBefore.executorFrontend,
|
||||||
|
collector: balancesBefore.collector - balancesAfter.collector
|
||||||
|
});
|
||||||
|
|
||||||
|
uint256 expectedCreatorFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().creatorSplit) /
|
||||||
|
BPS_MAX;
|
||||||
|
uint256 expectedProtocolFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().protocolSplit) /
|
||||||
|
BPS_MAX;
|
||||||
|
uint256 expectedCreatorFrontendFee = (mintFee *
|
||||||
|
mintFeeModule.getProtocolSharedRevenueDistribution().creatorFrontendSplit) / BPS_MAX;
|
||||||
|
uint256 expectedExecutorFrontendFee = (mintFee *
|
||||||
|
mintFeeModule.getProtocolSharedRevenueDistribution().executorFrontendSplit) / BPS_MAX;
|
||||||
|
|
||||||
|
assertEq(
|
||||||
|
balancesChange.creator,
|
||||||
|
expectedCreatorFee + expectedCreatorFrontendFee + expectedExecutorFrontendFee,
|
||||||
|
'Creator balance change wrong'
|
||||||
|
);
|
||||||
|
assertEq(balancesChange.protocol, expectedProtocolFee, 'Protocol balance change wrong');
|
||||||
|
assertEq(balancesChange.creatorFrontend, 0, 'CreatorFrontend balance change wrong');
|
||||||
|
assertEq(balancesChange.executorFrontend, 0, 'ExecutorFrontend balance change wrong');
|
||||||
|
assertEq(balancesChange.collector, mintFee, 'Collector balance change wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMintFeeDistribution_PaidPost(
|
||||||
|
uint256 pubId,
|
||||||
|
address transactionExecutor,
|
||||||
|
uint160 amount,
|
||||||
|
uint96 collectLimit,
|
||||||
|
uint16 referralFee,
|
||||||
|
bool followerOnly,
|
||||||
|
uint72 currentTimestamp,
|
||||||
|
uint72 endTimestamp,
|
||||||
|
address recipient
|
||||||
|
) public {
|
||||||
|
vm.assume(amount > 0);
|
||||||
|
address collectorProfileOwner = makeAddr('COLLECTOR_PROFILE_OWNER');
|
||||||
|
address executorFrontend = makeAddr('EXECUTOR_FRONTEND');
|
||||||
|
|
||||||
|
balancesBefore = Balances({
|
||||||
|
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||||
|
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||||
|
creatorFrontend: bonsai.balanceOf(creatorFrontendAddress),
|
||||||
|
executorFrontend: bonsai.balanceOf(executorFrontend),
|
||||||
|
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||||
|
});
|
||||||
|
|
||||||
|
exampleExecutorFrontend = executorFrontend;
|
||||||
|
super.testCanCollectIfAllConditionsAreMet(
|
||||||
|
pubId,
|
||||||
|
transactionExecutor,
|
||||||
|
amount,
|
||||||
|
collectLimit,
|
||||||
|
referralFee,
|
||||||
|
followerOnly,
|
||||||
|
currentTimestamp,
|
||||||
|
endTimestamp,
|
||||||
|
recipient,
|
||||||
|
collectorProfileOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
balancesAfter = Balances({
|
||||||
|
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||||
|
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||||
|
creatorFrontend: bonsai.balanceOf(creatorFrontendAddress),
|
||||||
|
executorFrontend: bonsai.balanceOf(executorFrontend),
|
||||||
|
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||||
|
});
|
||||||
|
|
||||||
|
balancesChange = Balances({
|
||||||
|
creator: balancesAfter.creator - balancesBefore.creator,
|
||||||
|
protocol: balancesAfter.protocol - balancesBefore.protocol,
|
||||||
|
creatorFrontend: balancesAfter.creatorFrontend - balancesBefore.creatorFrontend,
|
||||||
|
executorFrontend: balancesAfter.executorFrontend - balancesBefore.executorFrontend,
|
||||||
|
collector: balancesBefore.collector - balancesAfter.collector
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEq(balancesChange.creator, 0, 'Creator balance change wrong');
|
||||||
|
assertEq(balancesChange.protocol, 0, 'Protocol balance change wrong');
|
||||||
|
assertEq(balancesChange.creatorFrontend, 0, 'CreatorFrontend balance change wrong');
|
||||||
|
assertEq(balancesChange.executorFrontend, 0, 'ExecutorFrontend balance change wrong');
|
||||||
|
assertEq(balancesChange.collector, 0, 'Collector balance change wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Fee Distribution of ProtocolSharedRevenueMinFeeMintModule
|
||||||
|
//
|
||||||
|
contract ProtocolSharedRevenueMinFeeMintModule_FeeDistribution is ProtocolSharedRevenueMinFeeMintModuleBase {
|
||||||
|
function setUp() public override(ProtocolSharedRevenueMinFeeMintModuleBase) {
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleBase.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEncodedInitData() internal override(ProtocolSharedRevenueMinFeeMintModuleBase) returns (bytes memory) {
|
||||||
|
return ProtocolSharedRevenueMinFeeMintModuleBase.getEncodedInitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Fee Distribution of ProtocolSharedRevenueMinFeeMintModule
|
||||||
|
//
|
||||||
|
contract ProtocolSharedRevenueMinFeeMintModule_OwnerMethods is ProtocolSharedRevenueMinFeeMintModuleBase {
|
||||||
|
function setUp() public override(ProtocolSharedRevenueMinFeeMintModuleBase) {
|
||||||
|
ProtocolSharedRevenueMinFeeMintModuleBase.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negatives
|
||||||
|
|
||||||
|
function testCannotSetMintFeeParams_ifNotOwner(address currency, uint256 mintFee, address notOwner) public {
|
||||||
|
if (mintFee == 0) currency = address(0);
|
||||||
|
if (currency == address(0)) mintFee = 0;
|
||||||
|
|
||||||
|
vm.assume(notOwner != mintFeeModule.owner());
|
||||||
|
|
||||||
|
vm.expectRevert('Ownable: caller is not the owner');
|
||||||
|
|
||||||
|
vm.prank(notOwner);
|
||||||
|
mintFeeModule.setMintFeeParams(currency, mintFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCannotSetProtocolSharedRevenueDistribution_ifNotOwner(
|
||||||
|
uint16 creatorSplit,
|
||||||
|
uint16 protocolSplit,
|
||||||
|
uint16 creatorFrontendSplit,
|
||||||
|
address notOwner
|
||||||
|
) public {
|
||||||
|
vm.assume(notOwner != mintFeeModule.owner());
|
||||||
|
creatorSplit = uint16(bound(uint256(creatorSplit), 0, BPS_MAX));
|
||||||
|
protocolSplit = uint16(bound(uint256(protocolSplit), 0, BPS_MAX - creatorSplit));
|
||||||
|
creatorFrontendSplit = uint16(bound(uint256(creatorFrontendSplit), 0, BPS_MAX - creatorSplit - protocolSplit));
|
||||||
|
uint16 executorFrontendSplit = BPS_MAX - creatorSplit - protocolSplit - creatorFrontendSplit;
|
||||||
|
|
||||||
|
vm.expectRevert('Ownable: caller is not the owner');
|
||||||
|
|
||||||
|
vm.prank(notOwner);
|
||||||
|
mintFeeModule.setProtocolSharedRevenueDistribution(
|
||||||
|
ProtocolSharedRevenueDistribution({
|
||||||
|
creatorSplit: creatorSplit,
|
||||||
|
protocolSplit: protocolSplit,
|
||||||
|
creatorFrontendSplit: creatorFrontendSplit,
|
||||||
|
executorFrontendSplit: executorFrontendSplit
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCannotSetMintFeeParams_ifCurrencyZero_and_amountNotZero(uint256 mintFee) public {
|
||||||
|
vm.assume(mintFee > 0);
|
||||||
|
|
||||||
|
vm.prank(mintFeeModule.owner());
|
||||||
|
vm.expectRevert(ModuleErrors.InvalidParams.selector);
|
||||||
|
mintFeeModule.setMintFeeParams(address(0), mintFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCannotSetProtocolSharedRevenueDistribution_ifSplitsDontAddUpToBPS_MAX(
|
||||||
|
uint16 creatorSplit,
|
||||||
|
uint16 protocolSplit,
|
||||||
|
uint16 creatorFrontendSplit,
|
||||||
|
uint16 executorFrontendSplit
|
||||||
|
) public {
|
||||||
|
vm.assume(
|
||||||
|
uint256(creatorSplit) +
|
||||||
|
uint256(protocolSplit) +
|
||||||
|
uint256(creatorFrontendSplit) +
|
||||||
|
uint256(executorFrontendSplit) !=
|
||||||
|
BPS_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
vm.startPrank(mintFeeModule.owner());
|
||||||
|
if (
|
||||||
|
uint256(creatorSplit) +
|
||||||
|
uint256(protocolSplit) +
|
||||||
|
uint256(creatorFrontendSplit) +
|
||||||
|
uint256(executorFrontendSplit) >
|
||||||
|
type(uint16).max
|
||||||
|
) {
|
||||||
|
vm.expectRevert(stdError.arithmeticError);
|
||||||
|
} else {
|
||||||
|
vm.expectRevert(ModuleErrors.InvalidParams.selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
mintFeeModule.setProtocolSharedRevenueDistribution(
|
||||||
|
ProtocolSharedRevenueDistribution({
|
||||||
|
creatorSplit: creatorSplit,
|
||||||
|
protocolSplit: protocolSplit,
|
||||||
|
creatorFrontendSplit: creatorFrontendSplit,
|
||||||
|
executorFrontendSplit: executorFrontendSplit
|
||||||
|
})
|
||||||
|
);
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios
|
||||||
|
|
||||||
|
function testSetMintFeeParams(uint256 mintFee, address currency) public {
|
||||||
|
if (mintFee > 0) {
|
||||||
|
vm.assume(currency != address(0));
|
||||||
|
} else {
|
||||||
|
currency = address(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.prank(mintFeeModule.owner());
|
||||||
|
mintFeeModule.setMintFeeParams(currency, mintFee);
|
||||||
|
|
||||||
|
(address actualCurrency, uint256 actualMintFee) = mintFeeModule.getMintFeeParams();
|
||||||
|
|
||||||
|
assertEq(actualCurrency, currency, 'Currency mismatch');
|
||||||
|
assertEq(actualMintFee, mintFee, 'Mint fee mismatch');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSetProtocolSharedRevenueDistribution(
|
||||||
|
uint16 creatorSplit,
|
||||||
|
uint16 protocolSplit,
|
||||||
|
uint16 creatorFrontendSplit,
|
||||||
|
uint16 executorFrontendSplit
|
||||||
|
) public {
|
||||||
|
creatorSplit = uint16(bound(uint256(creatorSplit), 0, BPS_MAX));
|
||||||
|
protocolSplit = uint16(bound(uint256(protocolSplit), 0, BPS_MAX - creatorSplit));
|
||||||
|
creatorFrontendSplit = uint16(bound(uint256(creatorFrontendSplit), 0, BPS_MAX - creatorSplit - protocolSplit));
|
||||||
|
uint16 executorFrontendSplit = BPS_MAX - creatorSplit - protocolSplit - creatorFrontendSplit;
|
||||||
|
|
||||||
|
ProtocolSharedRevenueDistribution memory expectedDistribution = ProtocolSharedRevenueDistribution({
|
||||||
|
creatorSplit: creatorSplit,
|
||||||
|
protocolSplit: protocolSplit,
|
||||||
|
creatorFrontendSplit: creatorFrontendSplit,
|
||||||
|
executorFrontendSplit: executorFrontendSplit
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.prank(mintFeeModule.owner());
|
||||||
|
mintFeeModule.setProtocolSharedRevenueDistribution(expectedDistribution);
|
||||||
|
|
||||||
|
ProtocolSharedRevenueDistribution memory actualDistribution = mintFeeModule
|
||||||
|
.getProtocolSharedRevenueDistribution();
|
||||||
|
|
||||||
|
assertEq(actualDistribution.creatorSplit, expectedDistribution.creatorSplit, 'Creator split mismatch');
|
||||||
|
assertEq(actualDistribution.protocolSplit, expectedDistribution.protocolSplit, 'Protocol split mismatch');
|
||||||
|
assertEq(
|
||||||
|
actualDistribution.creatorFrontendSplit,
|
||||||
|
expectedDistribution.creatorFrontendSplit,
|
||||||
|
'CreatorFrontend split mismatch'
|
||||||
|
);
|
||||||
|
assertEq(
|
||||||
|
actualDistribution.executorFrontendSplit,
|
||||||
|
expectedDistribution.executorFrontendSplit,
|
||||||
|
'ExecutorFrontend split mismatch'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user