mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-10 06:38:05 -05:00
Merge pull request #167 from lens-protocol/feat/protocolSharedRevenueMinFeeMintModule
Feat/protocol shared revenue min fee mint module
This commit is contained in:
@@ -171,6 +171,10 @@
|
||||
{
|
||||
"symbol": "USDC",
|
||||
"addy": "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"
|
||||
},
|
||||
{
|
||||
"symbol": "BONSAI",
|
||||
"addy": "0x9f98f838e3db42830ef488a92c8ed9671268000e"
|
||||
}
|
||||
],
|
||||
"Modules": {
|
||||
@@ -204,6 +208,10 @@
|
||||
{
|
||||
"name": "MultirecipientFeeCollectModule",
|
||||
"addy": "0xC13ACcCe5cDb32bED1Af0B11cdb637E3966BCB45"
|
||||
},
|
||||
{
|
||||
"name": "ProtocolSharedRevenueMinFeeMintModule",
|
||||
"addy": "0x56174f7ff96C82004D3cEC42e9A99b5A44e76C77"
|
||||
}
|
||||
],
|
||||
"reference": [
|
||||
@@ -232,7 +240,7 @@
|
||||
"CollectNFT": "0xC7B6faDeCE0345E60ffa46BD3100094815aeB428",
|
||||
"LitAccessControlImpl": "0xae17Edd1CbCE05394575192006893Af9cB1AFc05",
|
||||
"LitAccessControl": "0x9Ddad77aD520d02D2566563b446935C6edD970fC",
|
||||
"PublicActProxy": "0x88c8fa7C470d9d94aDfA40187157917B26A548d3",
|
||||
"PublicActProxy": "0x01B282B6e55F8B20c17680aAE87FBDf9f0364F72",
|
||||
"GovernanceContract": "0x17e37DC312934108DaC5905897559C4c4A81b994",
|
||||
"GovernanceContractAdmin": "0x532BbA5445e306cB83cF26Ef89842d4701330A45",
|
||||
"ProxyAdminContract": "0xF71926E6487D465A307Bc0AcB5da50Ab7A15DA27",
|
||||
|
||||
409
broadcast/DeployMintFeeModule.s.sol/80002/9352fad2-latest.json
Normal file
409
broadcast/DeployMintFeeModule.s.sol/80002/9352fad2-latest.json
Normal file
File diff suppressed because one or more lines are too long
420
broadcast/DeployMintFeeModule.s.sol/80002/run-1714390429.json
Normal file
420
broadcast/DeployMintFeeModule.s.sol/80002/run-1714390429.json
Normal file
File diff suppressed because one or more lines are too long
409
broadcast/DeployMintFeeModule.s.sol/80002/run-1714414131.json
Normal file
409
broadcast/DeployMintFeeModule.s.sol/80002/run-1714414131.json
Normal file
File diff suppressed because one or more lines are too long
409
broadcast/DeployMintFeeModule.s.sol/80002/run-1714735736.json
Normal file
409
broadcast/DeployMintFeeModule.s.sol/80002/run-1714735736.json
Normal file
File diff suppressed because one or more lines are too long
448
broadcast/DeployModule.s.sol/80002/9352fad2-latest.json
Normal file
448
broadcast/DeployModule.s.sol/80002/9352fad2-latest.json
Normal file
File diff suppressed because one or more lines are too long
448
broadcast/DeployModule.s.sol/80002/run-1713430613.json
Normal file
448
broadcast/DeployModule.s.sol/80002/run-1713430613.json
Normal file
File diff suppressed because one or more lines are too long
189
broadcast/DeployPublicActProxy.s.sol/80002/9352fad2-latest.json
Normal file
189
broadcast/DeployPublicActProxy.s.sol/80002/9352fad2-latest.json
Normal file
File diff suppressed because one or more lines are too long
189
broadcast/DeployPublicActProxy.s.sol/80002/run-1715027001.json
Normal file
189
broadcast/DeployPublicActProxy.s.sol/80002/run-1715027001.json
Normal file
File diff suppressed because one or more lines are too long
@@ -10,6 +10,10 @@ import {CollectPublicationAction} from 'contracts/modules/act/collect/CollectPub
|
||||
import {BaseProfilePublicationData, IBaseFeeCollectModule} from 'contracts/modules/interfaces/IBaseFeeCollectModule.sol';
|
||||
import {MetaTxLib} from 'contracts/libraries/MetaTxLib.sol';
|
||||
|
||||
interface IProtocolSharedRevenueMinFeeMintModule {
|
||||
function getMintFeeParams() external view returns (address, uint256);
|
||||
}
|
||||
|
||||
/// @title PublicActProxy
|
||||
/// @author LensProtocol
|
||||
/// @notice This contract allows anyone to Act on a publication without holding a profile
|
||||
@@ -71,6 +75,18 @@ contract PublicActProxy {
|
||||
_publicCollect(publicationActionParams, signature.signer);
|
||||
}
|
||||
|
||||
function publicSharedRevenueCollect(Types.PublicationActionParams calldata publicationActionParams) external {
|
||||
_publicSharedRevenueCollect(publicationActionParams, msg.sender);
|
||||
}
|
||||
|
||||
function publicSharedRevenueCollectWithSig(
|
||||
Types.PublicationActionParams calldata publicationActionParams,
|
||||
Types.EIP712Signature calldata signature
|
||||
) external {
|
||||
MetaTxLib.validateActSignature(signature, publicationActionParams);
|
||||
_publicSharedRevenueCollect(publicationActionParams, signature.signer);
|
||||
}
|
||||
|
||||
function nonces(address signer) public view returns (uint256) {
|
||||
return _nonces[signer];
|
||||
}
|
||||
@@ -103,6 +119,35 @@ contract PublicActProxy {
|
||||
HUB.act(publicationActionParams);
|
||||
}
|
||||
|
||||
function _publicSharedRevenueCollect(
|
||||
Types.PublicationActionParams calldata publicationActionParams,
|
||||
address transactionExecutor
|
||||
) internal {
|
||||
address collectModule = COLLECT_PUBLICATION_ACTION
|
||||
.getCollectData(
|
||||
publicationActionParams.publicationActedProfileId,
|
||||
publicationActionParams.publicationActedId
|
||||
)
|
||||
.collectModule;
|
||||
|
||||
BaseProfilePublicationData memory collectData = IBaseFeeCollectModule(collectModule).getBasePublicationData(
|
||||
publicationActionParams.publicationActedProfileId,
|
||||
publicationActionParams.publicationActedId
|
||||
);
|
||||
|
||||
if (collectData.amount > 0) {
|
||||
IERC20(collectData.currency).safeTransferFrom(transactionExecutor, address(this), collectData.amount);
|
||||
IERC20(collectData.currency).safeIncreaseAllowance(collectModule, collectData.amount);
|
||||
} else {
|
||||
(address currency, uint256 amount) = IProtocolSharedRevenueMinFeeMintModule(collectModule)
|
||||
.getMintFeeParams();
|
||||
IERC20(currency).safeTransferFrom(transactionExecutor, address(this), amount);
|
||||
IERC20(currency).safeIncreaseAllowance(collectModule, amount);
|
||||
}
|
||||
|
||||
HUB.act(publicationActionParams);
|
||||
}
|
||||
|
||||
function name() external pure returns (string memory) {
|
||||
return 'PublicActProxy';
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ abstract contract FeeModuleBase {
|
||||
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));
|
||||
if (decodedAmount != amount || decodedCurrency != currency) {
|
||||
revert Errors.ModuleDataMismatch();
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
// 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 {LensModuleMetadataInitializable} from 'contracts/modules/LensModuleMetadataInitializable.sol';
|
||||
import {LensModule} from 'contracts/modules/LensModule.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;
|
||||
address recipient;
|
||||
uint16 referralFee;
|
||||
bool followerOnly;
|
||||
uint72 endTimestamp;
|
||||
address creatorClient;
|
||||
}
|
||||
|
||||
struct ProtocolSharedRevenueMinFeeMintModulePublicationData {
|
||||
uint160 amount;
|
||||
uint96 collectLimit;
|
||||
address currency;
|
||||
uint96 currentCollects;
|
||||
address recipient;
|
||||
uint16 referralFee;
|
||||
bool followerOnly;
|
||||
uint72 endTimestamp;
|
||||
address creatorClient;
|
||||
}
|
||||
|
||||
// Splits (in BPS)
|
||||
struct ProtocolSharedRevenueDistribution {
|
||||
uint16 creatorSplit;
|
||||
uint16 protocolSplit;
|
||||
uint16 creatorClientSplit;
|
||||
uint16 executorClientSplit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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, LensModuleMetadataInitializable {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address mintFeeToken;
|
||||
uint256 mintFeeAmount;
|
||||
ProtocolSharedRevenueDistribution protocolSharedRevenueDistribution;
|
||||
|
||||
event MintFeeParamsSet(address token, uint256 amount, uint256 timestamp);
|
||||
event ProtocolSharedRevenueDistributionSet(ProtocolSharedRevenueDistribution distribution, uint256 timestamp);
|
||||
|
||||
mapping(uint256 profileId => mapping(uint256 pubId => address creatorClient))
|
||||
internal _creatorClientByPublicationByProfile;
|
||||
|
||||
constructor(
|
||||
address hub,
|
||||
address actionModule,
|
||||
address moduleRegistry,
|
||||
address moduleOwner
|
||||
) BaseFeeCollectModule(hub, actionModule, moduleRegistry) LensModuleMetadataInitializable(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.creatorClient != address(0)) {
|
||||
_creatorClientByPublicationByProfile[profileId][pubId] = initData.creatorClient;
|
||||
}
|
||||
|
||||
_validateBaseInitData(baseInitData);
|
||||
_storeBasePublicationCollectParameters(profileId, pubId, baseInitData);
|
||||
return data;
|
||||
}
|
||||
|
||||
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 creatorClient = _creatorClientByPublicationByProfile[
|
||||
processCollectParams.publicationCollectedProfileId
|
||||
][processCollectParams.publicationCollectedId];
|
||||
uint256 creatorClientAmount = (mintFeeAmount * protocolSharedRevenueDistribution.creatorClientSplit) / 10000;
|
||||
|
||||
if (creatorClient != address(0)) {
|
||||
IERC20(mintFeeToken).safeTransferFrom(
|
||||
processCollectParams.transactionExecutor,
|
||||
creatorClient,
|
||||
creatorClientAmount
|
||||
);
|
||||
} else {
|
||||
// If there's no creatorClient specified - we give that amount to the publication creator
|
||||
creatorAmount += creatorClientAmount;
|
||||
}
|
||||
|
||||
(, , address executorClient) = abi.decode(processCollectParams.data, (address, uint256, address));
|
||||
uint256 executorClientAmount = (mintFeeAmount * protocolSharedRevenueDistribution.executorClientSplit) / 10000;
|
||||
|
||||
if (executorClient != address(0)) {
|
||||
IERC20(mintFeeToken).safeTransferFrom(
|
||||
processCollectParams.transactionExecutor,
|
||||
executorClient,
|
||||
executorClientAmount
|
||||
);
|
||||
} else {
|
||||
// If there's no executorClient specified - we give that amount to the publication creator
|
||||
creatorAmount += executorClientAmount;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
emit MintFeeParamsSet(token, amount, block.timestamp);
|
||||
}
|
||||
|
||||
function setProtocolSharedRevenueDistribution(
|
||||
ProtocolSharedRevenueDistribution memory distribution
|
||||
) external onlyOwner {
|
||||
if (
|
||||
distribution.creatorSplit +
|
||||
distribution.protocolSplit +
|
||||
distribution.creatorClientSplit +
|
||||
distribution.executorClientSplit !=
|
||||
BPS_MAX
|
||||
) {
|
||||
revert Errors.InvalidParams();
|
||||
}
|
||||
protocolSharedRevenueDistribution = distribution;
|
||||
|
||||
emit ProtocolSharedRevenueDistributionSet(distribution, block.timestamp);
|
||||
}
|
||||
|
||||
// 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 creatorClient = _creatorClientByPublicationByProfile[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,
|
||||
creatorClient: creatorClient
|
||||
});
|
||||
}
|
||||
|
||||
function supportsInterface(
|
||||
bytes4 interfaceID
|
||||
) public pure override(BaseFeeCollectModule, LensModule) returns (bool) {
|
||||
return BaseFeeCollectModule.supportsInterface(interfaceID) || LensModule.supportsInterface(interfaceID);
|
||||
}
|
||||
}
|
||||
@@ -533,6 +533,8 @@ contract DeployFreshLensV2 is Script, ForkManagement, ArrayHelpers {
|
||||
vm.writeLine(addressesFile, string.concat('AnonymousProfileId :', vm.toString(anonymousProfileId)));
|
||||
console.log('\n* * * Anonymous profile created with id: ', anonymousProfileId);
|
||||
saveValue('AnonymousProfileId', vm.toString(anonymousProfileId));
|
||||
|
||||
// TODO: Add PublicActProxy as a delegatedExecutor of anonymousProfileId
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
|
||||
|
||||
273
script/DeployMintFeeModule.s.sol
Normal file
273
script/DeployMintFeeModule.s.sol
Normal file
@@ -0,0 +1,273 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {ForkManagement} from 'script/helpers/ForkManagement.sol';
|
||||
import 'forge-std/Script.sol';
|
||||
import {ProtocolSharedRevenueDistribution, ProtocolSharedRevenueMinFeeMintModule} from 'contracts/modules/act/collect/ProtocolSharedRevenueMinFeeMintModule.sol';
|
||||
import {TransparentUpgradeableProxy} from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
|
||||
import {CollectPublicationAction} from 'contracts/modules/act/collect/CollectPublicationAction.sol';
|
||||
import {IModuleRegistry} from 'contracts/interfaces/IModuleRegistry.sol';
|
||||
import {LibString} from 'solady/utils/LibString.sol';
|
||||
|
||||
contract DeployMintFeeModule is Script, ForkManagement {
|
||||
using stdJson for string;
|
||||
|
||||
struct LensAccount {
|
||||
uint256 ownerPk;
|
||||
address owner;
|
||||
uint256 profileId;
|
||||
}
|
||||
|
||||
LensAccount _deployer;
|
||||
LensAccount governance;
|
||||
LensAccount proxyAdmin;
|
||||
|
||||
string mnemonic;
|
||||
|
||||
address lensHub;
|
||||
address collectPublicationAction;
|
||||
address moduleRegistry;
|
||||
address governanceOwner;
|
||||
address proxyAdminContractAdmin;
|
||||
address bonsai;
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModule mintFeeModule;
|
||||
address mintFeeModuleImpl;
|
||||
address payable mintFeeModuleProxyAddr;
|
||||
|
||||
function loadPrivateKeys() internal {
|
||||
if (isEnvSet('MNEMONIC')) {
|
||||
mnemonic = vm.envString('MNEMONIC');
|
||||
}
|
||||
|
||||
if (bytes(mnemonic).length == 0) {
|
||||
revert('Missing mnemonic');
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
|
||||
(_deployer.owner, _deployer.ownerPk) = deriveRememberKey(mnemonic, 0);
|
||||
console.log('Deployer address: %s', address(_deployer.owner));
|
||||
|
||||
(governance.owner, governance.ownerPk) = deriveRememberKey(mnemonic, 1);
|
||||
console.log('\n- - - GOVERNANCE: %s', governance.owner);
|
||||
|
||||
(proxyAdmin.owner, proxyAdmin.ownerPk) = deriveRememberKey(mnemonic, 2);
|
||||
console.log('\n- - - PROXY ADMIN: %s', proxyAdmin.owner);
|
||||
|
||||
console.log('\n');
|
||||
|
||||
console.log('Current block:', block.number);
|
||||
}
|
||||
|
||||
struct Currency {
|
||||
address addy;
|
||||
string symbol;
|
||||
}
|
||||
|
||||
// TODO: Use from test/ContractAddresses
|
||||
struct Module {
|
||||
address addy;
|
||||
string name;
|
||||
}
|
||||
|
||||
// TODO: Move this somewhere common (also in UpgradeForkTest)
|
||||
function findModuleHelper(
|
||||
Module[] memory modules,
|
||||
string memory moduleNameToFind
|
||||
) internal pure returns (Module memory) {
|
||||
for (uint256 i = 0; i < modules.length; i++) {
|
||||
if (LibString.eq(modules[i].name, moduleNameToFind)) {
|
||||
return modules[i];
|
||||
}
|
||||
}
|
||||
revert('Module not found');
|
||||
}
|
||||
|
||||
function findModuleHelper_noFail(
|
||||
Module[] memory modules,
|
||||
string memory moduleNameToFind
|
||||
) internal pure returns (Module memory) {
|
||||
for (uint256 i = 0; i < modules.length; i++) {
|
||||
if (LibString.eq(modules[i].name, moduleNameToFind)) {
|
||||
return modules[i];
|
||||
}
|
||||
}
|
||||
return Module(address(0), '');
|
||||
}
|
||||
|
||||
function saveModule(
|
||||
string memory moduleName,
|
||||
address moduleAddress,
|
||||
string memory lensVersion,
|
||||
string memory moduleType
|
||||
) internal {
|
||||
// console.log('Saving %s (%s) into addresses under %s environment', moduleName, moduleAddress, targetEnv);
|
||||
string[] memory inputs = new string[](7);
|
||||
inputs[0] = 'node';
|
||||
inputs[1] = 'script/helpers/saveAddress.js';
|
||||
inputs[2] = targetEnv;
|
||||
inputs[3] = moduleName;
|
||||
inputs[4] = vm.toString(moduleAddress);
|
||||
inputs[5] = lensVersion;
|
||||
inputs[6] = moduleType;
|
||||
// bytes memory res =
|
||||
vm.ffi(inputs);
|
||||
// string memory output = abi.decode(res, (string));
|
||||
// console.log(output);
|
||||
}
|
||||
|
||||
function _logDeployedModule(address deployedAddress, string memory moduleName, string memory moduleType) internal {
|
||||
string memory lensVersion = 'v2';
|
||||
console.log('\n+ + + ', moduleName, ': ', deployedAddress);
|
||||
saveModule(moduleName, deployedAddress, lensVersion, moduleType);
|
||||
}
|
||||
|
||||
function loadBaseAddresses() internal override {
|
||||
lensHub = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHub')));
|
||||
vm.label(lensHub, 'LensHub');
|
||||
console.log('Lens Hub Proxy: %s', lensHub);
|
||||
|
||||
Module[] memory actModules = abi.decode(
|
||||
vm.parseJson(json, string(abi.encodePacked('.', targetEnv, '.Modules.v2.act'))),
|
||||
(Module[])
|
||||
);
|
||||
collectPublicationAction = findModuleHelper(actModules, 'CollectPublicationAction').addy;
|
||||
vm.label(collectPublicationAction, 'CollectPublicationAction');
|
||||
console.log('CollectPublicationAction: %s', collectPublicationAction);
|
||||
|
||||
Module[] memory collectModules = abi.decode(
|
||||
vm.parseJson(json, string(abi.encodePacked('.', targetEnv, '.Modules.v2.collect'))),
|
||||
(Module[])
|
||||
);
|
||||
mintFeeModuleProxyAddr = payable(
|
||||
findModuleHelper_noFail(collectModules, 'ProtocolSharedRevenueMinFeeMintModule').addy
|
||||
);
|
||||
|
||||
moduleRegistry = json.readAddress(string(abi.encodePacked('.', targetEnv, '.ModuleRegistry')));
|
||||
vm.label(moduleRegistry, 'ModuleRegistry');
|
||||
console.log('ModuleRegistry: %s', moduleRegistry);
|
||||
|
||||
governanceOwner = json.readAddress(string(abi.encodePacked('.', targetEnv, '.GovernanceContractAdmin')));
|
||||
vm.label(governanceOwner, 'GovernanceOwner');
|
||||
console.log('Governance Owner: %s', governanceOwner);
|
||||
|
||||
proxyAdminContractAdmin = json.readAddress(
|
||||
string(abi.encodePacked('.', targetEnv, '.ProxyAdminContractAdmin'))
|
||||
);
|
||||
vm.label(proxyAdminContractAdmin, 'ProxyAdminContractAdmin');
|
||||
console.log('Proxy Admin Contract Admin: %s', proxyAdminContractAdmin);
|
||||
|
||||
Currency[] memory currencies = abi.decode(
|
||||
vm.parseJson(json, string(abi.encodePacked('.', targetEnv, '.Currencies'))),
|
||||
(Currency[])
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < currencies.length; i++) {
|
||||
if (LibString.eq(currencies[i].symbol, 'BONSAI')) {
|
||||
bonsai = currencies[i].addy;
|
||||
}
|
||||
}
|
||||
vm.label(bonsai, 'BONSAI');
|
||||
console.log('BONSAI: %s', bonsai);
|
||||
}
|
||||
|
||||
function run(string memory targetEnv_) external {
|
||||
targetEnv = targetEnv_;
|
||||
loadJson();
|
||||
checkNetworkParams();
|
||||
loadBaseAddresses();
|
||||
loadPrivateKeys();
|
||||
deploy();
|
||||
moduleRegistryActions();
|
||||
governanceActions();
|
||||
}
|
||||
|
||||
function deploy() internal {
|
||||
vm.startBroadcast(_deployer.ownerPk);
|
||||
{
|
||||
mintFeeModuleImpl = address(
|
||||
new ProtocolSharedRevenueMinFeeMintModule({
|
||||
hub: lensHub,
|
||||
actionModule: collectPublicationAction,
|
||||
moduleRegistry: moduleRegistry,
|
||||
moduleOwner: governanceOwner
|
||||
})
|
||||
);
|
||||
console.log(
|
||||
'\n* * * Deployed ProtocolSharedRevenueMinFeeMintModule implementation at: ',
|
||||
mintFeeModuleImpl
|
||||
);
|
||||
console.log('With parameters:');
|
||||
console.log('\tHub: ', lensHub);
|
||||
console.log('\tAction Module: ', collectPublicationAction);
|
||||
console.log('\tModule Registry: ', moduleRegistry);
|
||||
console.log('\tModule Owner: ', governanceOwner);
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
|
||||
if (mintFeeModuleProxyAddr == address(0)) {
|
||||
console.log('\n* * * MintFeeModule proxy not found - deploying from scratch...');
|
||||
vm.startBroadcast(_deployer.ownerPk);
|
||||
{
|
||||
mintFeeModule = ProtocolSharedRevenueMinFeeMintModule(
|
||||
address(
|
||||
new TransparentUpgradeableProxy(
|
||||
mintFeeModuleImpl,
|
||||
proxyAdminContractAdmin,
|
||||
abi.encodeCall(mintFeeModule.initialize, (governanceOwner))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
|
||||
_logDeployedModule(address(mintFeeModule), 'ProtocolSharedRevenueMinFeeMintModule', 'collect');
|
||||
} else {
|
||||
console.log('\n* * * MintFeeModule proxy found - upgrading...');
|
||||
console.log('\tProxyAdminContractAdmin: ', proxyAdminContractAdmin);
|
||||
console.log('\tproxyAdmin.owner: ', proxyAdmin.owner);
|
||||
vm.startBroadcast(proxyAdmin.ownerPk);
|
||||
{
|
||||
TransparentUpgradeableProxy(mintFeeModuleProxyAddr).upgradeTo(mintFeeModuleImpl);
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
mintFeeModule = ProtocolSharedRevenueMinFeeMintModule(mintFeeModuleProxyAddr);
|
||||
console.log('\n* * * MintFeeModule upgraded to new implementation: ', mintFeeModuleImpl);
|
||||
}
|
||||
}
|
||||
|
||||
function moduleRegistryActions() internal {
|
||||
vm.startBroadcast(_deployer.ownerPk);
|
||||
IModuleRegistry(moduleRegistry).registerErc20Currency(bonsai);
|
||||
console.log('\n* * * BONSAI registered as currency: ', bonsai);
|
||||
|
||||
CollectPublicationAction(collectPublicationAction).registerCollectModule(address(mintFeeModule));
|
||||
console.log('\n* * * MintFeeModule registered as collect module in CollectPublicationAction');
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function governanceActions() internal {
|
||||
console.log('\n* * * Owner of mintFeeModule:', mintFeeModule.owner());
|
||||
vm.startBroadcast(governance.ownerPk);
|
||||
{
|
||||
mintFeeModule.setMintFeeParams(bonsai, 10 ether);
|
||||
console.log('\n* * * MintFeeModule mint fee set to 10 BONSAI');
|
||||
|
||||
mintFeeModule.setProtocolSharedRevenueDistribution(
|
||||
ProtocolSharedRevenueDistribution({
|
||||
creatorSplit: 5000,
|
||||
protocolSplit: 2000,
|
||||
creatorClientSplit: 1500,
|
||||
executorClientSplit: 1500
|
||||
})
|
||||
);
|
||||
console.log('\n* * * MintFeeModule revenue distribution set to:');
|
||||
console.log('\tCreator: 50%');
|
||||
console.log('\tProtocol: 20%');
|
||||
console.log('\tCreator Client: 15%');
|
||||
console.log('\tExecutor Client: 15%');
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
||||
143
script/DeployPublicActProxy.s.sol
Normal file
143
script/DeployPublicActProxy.s.sol
Normal file
@@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {ForkManagement} from 'script/helpers/ForkManagement.sol';
|
||||
import 'forge-std/Script.sol';
|
||||
import {ArrayHelpers} from 'script/helpers/ArrayHelpers.sol';
|
||||
import {LibString} from 'solady/utils/LibString.sol';
|
||||
import {PublicActProxy} from 'contracts/misc/PublicActProxy.sol';
|
||||
import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
|
||||
|
||||
contract DeployPublicActProxy is Script, ForkManagement, ArrayHelpers {
|
||||
using stdJson for string;
|
||||
|
||||
string addressesFile = 'addressesV2.txt';
|
||||
|
||||
struct LensAccount {
|
||||
uint256 ownerPk;
|
||||
address owner;
|
||||
uint256 profileId;
|
||||
}
|
||||
|
||||
// TODO: Use from test/ContractAddresses
|
||||
struct Module {
|
||||
address addy;
|
||||
string name;
|
||||
}
|
||||
|
||||
LensAccount deployer;
|
||||
LensAccount governance;
|
||||
LensAccount proxyAdmin;
|
||||
|
||||
string mnemonic;
|
||||
|
||||
address lensHub;
|
||||
address collectPublicationAction;
|
||||
|
||||
address publicActProxy;
|
||||
|
||||
function findModuleHelper(
|
||||
Module[] memory modules,
|
||||
string memory moduleNameToFind
|
||||
) internal pure returns (Module memory) {
|
||||
for (uint256 i = 0; i < modules.length; i++) {
|
||||
if (LibString.eq(modules[i].name, moduleNameToFind)) {
|
||||
return modules[i];
|
||||
}
|
||||
}
|
||||
revert('Module not found');
|
||||
}
|
||||
|
||||
function saveContractAddress(string memory contractName, address deployedAddress) internal {
|
||||
// console.log('Saving %s (%s) into addresses under %s environment', contractName, deployedAddress, targetEnv);
|
||||
string[] memory inputs = new string[](5);
|
||||
inputs[0] = 'node';
|
||||
inputs[1] = 'script/helpers/saveAddress.js';
|
||||
inputs[2] = targetEnv;
|
||||
inputs[3] = contractName;
|
||||
inputs[4] = vm.toString(deployedAddress);
|
||||
// bytes memory res =
|
||||
vm.ffi(inputs);
|
||||
// string memory output = abi.decode(res, (string));
|
||||
// console.log(output);
|
||||
}
|
||||
|
||||
function _logDeployedAddress(address deployedAddress, string memory addressLabel) internal {
|
||||
console.log('\n+ + + ', addressLabel, ': ', deployedAddress);
|
||||
vm.writeLine(addressesFile, string.concat(addressLabel, string.concat(': ', vm.toString(deployedAddress))));
|
||||
saveContractAddress(addressLabel, deployedAddress);
|
||||
}
|
||||
|
||||
function loadPrivateKeys() internal {
|
||||
if (isEnvSet('MNEMONIC')) {
|
||||
mnemonic = vm.envString('MNEMONIC');
|
||||
}
|
||||
|
||||
if (bytes(mnemonic).length == 0) {
|
||||
revert('Missing mnemonic');
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
|
||||
(deployer.owner, deployer.ownerPk) = deriveRememberKey(mnemonic, 0);
|
||||
console.log('\n- - - DEPLOYER: %s', deployer.owner);
|
||||
(governance.owner, governance.ownerPk) = deriveRememberKey(mnemonic, 1);
|
||||
console.log('\n- - - GOVERNANCE: %s', governance.owner);
|
||||
(proxyAdmin.owner, proxyAdmin.ownerPk) = deriveRememberKey(mnemonic, 2);
|
||||
console.log('\n- - - PROXYADMIN: %s', proxyAdmin.owner);
|
||||
|
||||
console.log('\n');
|
||||
|
||||
console.log('Current block:', block.number);
|
||||
}
|
||||
|
||||
function loadBaseAddresses() internal override {
|
||||
lensHub = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHub')));
|
||||
vm.label(lensHub, 'LensHub');
|
||||
console.log('Lens Hub Proxy: %s', lensHub);
|
||||
|
||||
Module[] memory actModules = abi.decode(
|
||||
vm.parseJson(json, string(abi.encodePacked('.', targetEnv, '.Modules.v2.act'))),
|
||||
(Module[])
|
||||
);
|
||||
collectPublicationAction = findModuleHelper(actModules, 'CollectPublicationAction').addy;
|
||||
vm.label(collectPublicationAction, 'CollectPublicationAction');
|
||||
console.log('CollectPublicationAction: %s', collectPublicationAction);
|
||||
}
|
||||
|
||||
function run(string memory targetEnv_) external {
|
||||
targetEnv = targetEnv_;
|
||||
loadJson();
|
||||
checkNetworkParams();
|
||||
loadBaseAddresses();
|
||||
loadPrivateKeys();
|
||||
deploy();
|
||||
governanceActions();
|
||||
}
|
||||
|
||||
function deploy() internal {
|
||||
vm.startBroadcast(deployer.ownerPk);
|
||||
{
|
||||
publicActProxy = address(
|
||||
new PublicActProxy({lensHub: lensHub, collectPublicationAction: collectPublicationAction})
|
||||
);
|
||||
_logDeployedAddress(publicActProxy, 'PublicActProxy');
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function governanceActions() internal {
|
||||
uint256 anonymousProfileId = json.readUint(string(abi.encodePacked('.', targetEnv, '.AnonymousProfileId')));
|
||||
console.log('Anonymous Profile Id: %s', anonymousProfileId);
|
||||
vm.startBroadcast(deployer.ownerPk);
|
||||
{
|
||||
ILensHub(lensHub).changeDelegatedExecutorsConfig(
|
||||
anonymousProfileId,
|
||||
_toAddressArray(publicActProxy),
|
||||
_toBoolArray(true)
|
||||
);
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
console.log('PublicActProxy added as DelegatedExecutor of AnonymousProfileId: %s', publicActProxy);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {CollectNFT} from 'contracts/modules/act/collect/CollectNFT.sol';
|
||||
import {SimpleFeeCollectModule} from 'contracts/modules/act/collect/SimpleFeeCollectModule.sol';
|
||||
import {BaseFeeCollectModuleInitData} from 'contracts/modules/interfaces/IBaseFeeCollectModule.sol';
|
||||
import {MockCurrency} from 'test/mocks/MockCurrency.sol';
|
||||
import {ProtocolSharedRevenueDistribution, ProtocolSharedRevenueMinFeeMintModuleInitData, ProtocolSharedRevenueMinFeeMintModule} from 'contracts/modules/act/collect/ProtocolSharedRevenueMinFeeMintModule.sol';
|
||||
|
||||
contract PublicActProxyTest is BaseTest {
|
||||
using stdJson for string;
|
||||
@@ -216,4 +217,321 @@ contract PublicActProxyTest is BaseTest {
|
||||
|
||||
assertTrue(collectNFT.balanceOf(nftRecipient) > 0, 'NFT recipient balance is 0');
|
||||
}
|
||||
|
||||
function testCanPublicSharedRevenueCollectPaid() public {
|
||||
vm.prank(deployer);
|
||||
address revenueShareModule = address(
|
||||
new ProtocolSharedRevenueMinFeeMintModule(
|
||||
address(hub),
|
||||
address(collectPublicationAction),
|
||||
address(moduleRegistry),
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
|
||||
collectPublicationAction.registerCollectModule(revenueShareModule);
|
||||
|
||||
MockCurrency currency = new MockCurrency();
|
||||
currency.mint(payer, 10 ether);
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModuleInitData memory exampleInitData;
|
||||
exampleInitData.amount = 1 ether;
|
||||
exampleInitData.collectLimit = 0;
|
||||
exampleInitData.currency = address(currency);
|
||||
exampleInitData.referralFee = 0;
|
||||
exampleInitData.followerOnly = false;
|
||||
exampleInitData.endTimestamp = 0;
|
||||
exampleInitData.recipient = defaultAccount.owner;
|
||||
exampleInitData.creatorClient = address(0);
|
||||
|
||||
Types.PostParams memory postParams = _getDefaultPostParams();
|
||||
postParams.actionModules[0] = address(collectPublicationAction);
|
||||
postParams.actionModulesInitDatas[0] = abi.encode(revenueShareModule, abi.encode(exampleInitData));
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
defaultPubId = hub.post(postParams);
|
||||
|
||||
collectActionParams = Types.PublicationActionParams({
|
||||
publicationActedProfileId: defaultAccount.profileId,
|
||||
publicationActedId: defaultPubId,
|
||||
actorProfileId: publicProfile.profileId,
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
actionModuleAddress: address(collectPublicationAction),
|
||||
actionModuleData: abi.encode(nftRecipient, abi.encode(currency, exampleInitData.amount, address(0)))
|
||||
});
|
||||
|
||||
vm.startPrank(payer);
|
||||
currency.approve(address(publicActProxy), exampleInitData.amount);
|
||||
publicActProxy.publicSharedRevenueCollect(collectActionParams);
|
||||
vm.stopPrank();
|
||||
|
||||
CollectNFT collectNFT = CollectNFT(
|
||||
CollectPublicationAction(collectPublicationAction)
|
||||
.getCollectData(defaultAccount.profileId, defaultPubId)
|
||||
.collectNFT
|
||||
);
|
||||
|
||||
assertTrue(collectNFT.balanceOf(nftRecipient) > 0, 'NFT recipient balance is 0');
|
||||
}
|
||||
|
||||
function testCanPublicSharedRevenueCollectPaidWithSig() public {
|
||||
vm.prank(deployer);
|
||||
address revenueShareModule = address(
|
||||
new ProtocolSharedRevenueMinFeeMintModule(
|
||||
address(hub),
|
||||
address(collectPublicationAction),
|
||||
address(moduleRegistry),
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
|
||||
collectPublicationAction.registerCollectModule(revenueShareModule);
|
||||
|
||||
MockCurrency currency = new MockCurrency();
|
||||
currency.mint(payer, 10 ether);
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModuleInitData memory exampleInitData;
|
||||
exampleInitData.amount = 1 ether;
|
||||
exampleInitData.collectLimit = 0;
|
||||
exampleInitData.currency = address(currency);
|
||||
exampleInitData.referralFee = 0;
|
||||
exampleInitData.followerOnly = false;
|
||||
exampleInitData.endTimestamp = 0;
|
||||
exampleInitData.recipient = defaultAccount.owner;
|
||||
exampleInitData.creatorClient = address(0);
|
||||
|
||||
Types.PostParams memory postParams = _getDefaultPostParams();
|
||||
postParams.actionModules[0] = address(collectPublicationAction);
|
||||
postParams.actionModulesInitDatas[0] = abi.encode(revenueShareModule, abi.encode(exampleInitData));
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
defaultPubId = hub.post(postParams);
|
||||
|
||||
collectActionParams = Types.PublicationActionParams({
|
||||
publicationActedProfileId: defaultAccount.profileId,
|
||||
publicationActedId: defaultPubId,
|
||||
actorProfileId: publicProfile.profileId,
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
actionModuleAddress: address(collectPublicationAction),
|
||||
actionModuleData: abi.encode(nftRecipient, abi.encode(currency, exampleInitData.amount, address(0)))
|
||||
});
|
||||
|
||||
vm.prank(payer);
|
||||
currency.approve(address(publicActProxy), exampleInitData.amount);
|
||||
|
||||
domainSeparator = keccak256(
|
||||
abi.encode(
|
||||
Typehash.EIP712_DOMAIN,
|
||||
keccak256('PublicActProxy'),
|
||||
MetaTxLib.EIP712_DOMAIN_VERSION_HASH,
|
||||
block.chainid,
|
||||
address(publicActProxy)
|
||||
)
|
||||
);
|
||||
|
||||
publicActProxy.publicSharedRevenueCollectWithSig({
|
||||
publicationActionParams: collectActionParams,
|
||||
signature: _getSigStruct({
|
||||
pKey: payerPk,
|
||||
digest: _getActTypedDataHash(collectActionParams, publicActProxy.nonces(payer), type(uint256).max),
|
||||
deadline: type(uint256).max
|
||||
})
|
||||
});
|
||||
|
||||
CollectNFT collectNFT = CollectNFT(
|
||||
CollectPublicationAction(collectPublicationAction)
|
||||
.getCollectData(defaultAccount.profileId, defaultPubId)
|
||||
.collectNFT
|
||||
);
|
||||
|
||||
assertTrue(collectNFT.balanceOf(nftRecipient) > 0, 'NFT recipient balance is 0');
|
||||
}
|
||||
|
||||
function testCanPublicSharedRevenueCollectFree() public {
|
||||
address creatorClient = makeAddr('CREATOR_CLIENT');
|
||||
address executorClient = makeAddr('EXECUTOR_CLIENT');
|
||||
|
||||
uint256 mintFee = 10 ether;
|
||||
|
||||
vm.prank(deployer);
|
||||
address revenueShareModule = address(
|
||||
new ProtocolSharedRevenueMinFeeMintModule(
|
||||
address(hub),
|
||||
address(collectPublicationAction),
|
||||
address(moduleRegistry),
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
|
||||
collectPublicationAction.registerCollectModule(revenueShareModule);
|
||||
|
||||
MockCurrency currency = new MockCurrency();
|
||||
currency.mint(payer, mintFee);
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModule(revenueShareModule).setMintFeeParams(address(currency), mintFee);
|
||||
ProtocolSharedRevenueMinFeeMintModule(revenueShareModule).setProtocolSharedRevenueDistribution(
|
||||
ProtocolSharedRevenueDistribution({
|
||||
creatorSplit: 5000,
|
||||
protocolSplit: 2000,
|
||||
creatorClientSplit: 1500,
|
||||
executorClientSplit: 1500
|
||||
})
|
||||
);
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModuleInitData memory exampleInitData;
|
||||
exampleInitData.amount = 0 ether;
|
||||
exampleInitData.collectLimit = 0;
|
||||
exampleInitData.currency = address(0);
|
||||
exampleInitData.referralFee = 0;
|
||||
exampleInitData.followerOnly = false;
|
||||
exampleInitData.endTimestamp = 0;
|
||||
exampleInitData.recipient = defaultAccount.owner;
|
||||
exampleInitData.creatorClient = creatorClient;
|
||||
|
||||
Types.PostParams memory postParams = _getDefaultPostParams();
|
||||
postParams.actionModules[0] = address(collectPublicationAction);
|
||||
postParams.actionModulesInitDatas[0] = abi.encode(revenueShareModule, abi.encode(exampleInitData));
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
defaultPubId = hub.post(postParams);
|
||||
|
||||
collectActionParams = Types.PublicationActionParams({
|
||||
publicationActedProfileId: defaultAccount.profileId,
|
||||
publicationActedId: defaultPubId,
|
||||
actorProfileId: publicProfile.profileId,
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
actionModuleAddress: address(collectPublicationAction),
|
||||
actionModuleData: abi.encode(
|
||||
nftRecipient,
|
||||
abi.encode(exampleInitData.currency, exampleInitData.amount, executorClient)
|
||||
)
|
||||
});
|
||||
|
||||
vm.startPrank(payer);
|
||||
currency.approve(address(publicActProxy), mintFee);
|
||||
publicActProxy.publicSharedRevenueCollect(collectActionParams);
|
||||
vm.stopPrank();
|
||||
|
||||
CollectNFT collectNFT = CollectNFT(
|
||||
CollectPublicationAction(collectPublicationAction)
|
||||
.getCollectData(defaultAccount.profileId, defaultPubId)
|
||||
.collectNFT
|
||||
);
|
||||
|
||||
assertTrue(collectNFT.balanceOf(nftRecipient) > 0, 'NFT recipient balance is 0');
|
||||
|
||||
assertEq(currency.balanceOf(defaultAccount.owner), (mintFee * 5000) / 10000, 'Creator balance is incorrect');
|
||||
assertEq(
|
||||
currency.balanceOf(hub.getTreasury()),
|
||||
(mintFee * 2000) / 10000,
|
||||
'Protocol treasury client balance is incorrect'
|
||||
);
|
||||
assertEq(currency.balanceOf(creatorClient), (mintFee * 1500) / 10000, 'Creator client balance is incorrect');
|
||||
assertEq(currency.balanceOf(executorClient), (mintFee * 1500) / 10000, 'Executor client balance is incorrect');
|
||||
}
|
||||
|
||||
function testCanPublicSharedRevenueCollectFreeWithSig() public {
|
||||
address creatorClient = makeAddr('CREATOR_CLIENT');
|
||||
address executorClient = makeAddr('EXECUTOR_CLIENT');
|
||||
|
||||
uint256 mintFee = 10 ether;
|
||||
|
||||
vm.prank(deployer);
|
||||
address revenueShareModule = address(
|
||||
new ProtocolSharedRevenueMinFeeMintModule(
|
||||
address(hub),
|
||||
address(collectPublicationAction),
|
||||
address(moduleRegistry),
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
|
||||
collectPublicationAction.registerCollectModule(revenueShareModule);
|
||||
|
||||
MockCurrency currency = new MockCurrency();
|
||||
currency.mint(payer, mintFee);
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModule(revenueShareModule).setMintFeeParams(address(currency), mintFee);
|
||||
ProtocolSharedRevenueMinFeeMintModule(revenueShareModule).setProtocolSharedRevenueDistribution(
|
||||
ProtocolSharedRevenueDistribution({
|
||||
creatorSplit: 5000,
|
||||
protocolSplit: 2000,
|
||||
creatorClientSplit: 1500,
|
||||
executorClientSplit: 1500
|
||||
})
|
||||
);
|
||||
|
||||
ProtocolSharedRevenueMinFeeMintModuleInitData memory exampleInitData;
|
||||
exampleInitData.amount = 0 ether;
|
||||
exampleInitData.collectLimit = 0;
|
||||
exampleInitData.currency = address(0);
|
||||
exampleInitData.referralFee = 0;
|
||||
exampleInitData.followerOnly = false;
|
||||
exampleInitData.endTimestamp = 0;
|
||||
exampleInitData.recipient = defaultAccount.owner;
|
||||
exampleInitData.creatorClient = creatorClient;
|
||||
|
||||
Types.PostParams memory postParams = _getDefaultPostParams();
|
||||
postParams.actionModules[0] = address(collectPublicationAction);
|
||||
postParams.actionModulesInitDatas[0] = abi.encode(revenueShareModule, abi.encode(exampleInitData));
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
defaultPubId = hub.post(postParams);
|
||||
|
||||
collectActionParams = Types.PublicationActionParams({
|
||||
publicationActedProfileId: defaultAccount.profileId,
|
||||
publicationActedId: defaultPubId,
|
||||
actorProfileId: publicProfile.profileId,
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
actionModuleAddress: address(collectPublicationAction),
|
||||
actionModuleData: abi.encode(
|
||||
nftRecipient,
|
||||
abi.encode(exampleInitData.currency, exampleInitData.amount, executorClient)
|
||||
)
|
||||
});
|
||||
|
||||
vm.startPrank(payer);
|
||||
currency.approve(address(publicActProxy), mintFee);
|
||||
vm.stopPrank();
|
||||
|
||||
domainSeparator = keccak256(
|
||||
abi.encode(
|
||||
Typehash.EIP712_DOMAIN,
|
||||
keccak256('PublicActProxy'),
|
||||
MetaTxLib.EIP712_DOMAIN_VERSION_HASH,
|
||||
block.chainid,
|
||||
address(publicActProxy)
|
||||
)
|
||||
);
|
||||
|
||||
publicActProxy.publicSharedRevenueCollectWithSig({
|
||||
publicationActionParams: collectActionParams,
|
||||
signature: _getSigStruct({
|
||||
pKey: payerPk,
|
||||
digest: _getActTypedDataHash(collectActionParams, publicActProxy.nonces(payer), type(uint256).max),
|
||||
deadline: type(uint256).max
|
||||
})
|
||||
});
|
||||
|
||||
CollectNFT collectNFT = CollectNFT(
|
||||
CollectPublicationAction(collectPublicationAction)
|
||||
.getCollectData(defaultAccount.profileId, defaultPubId)
|
||||
.collectNFT
|
||||
);
|
||||
|
||||
assertTrue(collectNFT.balanceOf(nftRecipient) > 0, 'NFT recipient balance is 0');
|
||||
|
||||
assertEq(currency.balanceOf(defaultAccount.owner), (mintFee * 5000) / 10000, 'Creator balance is incorrect');
|
||||
assertEq(
|
||||
currency.balanceOf(hub.getTreasury()),
|
||||
(mintFee * 2000) / 10000,
|
||||
'Protocol treasury client balance is incorrect'
|
||||
);
|
||||
assertEq(currency.balanceOf(creatorClient), (mintFee * 1500) / 10000, 'Creator client balance is incorrect');
|
||||
assertEq(currency.balanceOf(executorClient), (mintFee * 1500) / 10000, 'Executor client balance is incorrect');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, passedAmount)
|
||||
data: _getCollectParamsData(address(currency), passedAmount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -292,7 +292,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(passedCurrency, amount)
|
||||
data: _getCollectParamsData(passedCurrency, exampleInitData.amount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -335,7 +335,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, exampleInitData.amount)
|
||||
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -379,7 +379,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, exampleInitData.amount)
|
||||
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -423,7 +423,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, exampleInitData.amount)
|
||||
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -440,7 +440,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, exampleInitData.amount)
|
||||
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -518,7 +518,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, amount)
|
||||
data: _getCollectParamsData(address(currency), amount)
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -564,7 +564,7 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
referrerProfileIds: _emptyUint256Array(),
|
||||
referrerPubIds: _emptyUint256Array(),
|
||||
referrerPubTypes: _emptyPubTypesArray(),
|
||||
data: abi.encode(currency, exampleInitData.amount)
|
||||
data: _getCollectParamsData(address(currency), exampleInitData.amount)
|
||||
})
|
||||
);
|
||||
|
||||
@@ -572,6 +572,10 @@ contract BaseFeeCollectModule_ProcessCollect is BaseFeeCollectModuleBase {
|
||||
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 {
|
||||
|
||||
@@ -29,6 +29,7 @@ contract MultirecipientCollectModule_Initialization is
|
||||
return MultirecipientCollectModuleBase.getEncodedInitData();
|
||||
}
|
||||
|
||||
// TODO: WTF?
|
||||
function testCannotInitializeWithNonWhitelistedCurrency(
|
||||
uint256 profileId,
|
||||
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 creatorClientAddress = makeAddr('CREATOR_CLIENT');
|
||||
address executorClientAddress = makeAddr('EXECUTOR_CLIENT');
|
||||
|
||||
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,
|
||||
creatorClientSplit: 1500,
|
||||
executorClientSplit: 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.creatorClient = creatorClientAddress;
|
||||
|
||||
return abi.encode(mintFeeModuleExampleInitData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,621 @@
|
||||
// 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 creatorClient
|
||||
) 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.creatorClient = creatorClient;
|
||||
|
||||
vm.prank(collectPublicationAction);
|
||||
bytes memory returnedData = 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.creatorClient,
|
||||
mintFeeModuleExampleInitData.creatorClient,
|
||||
'CreatorClient initialization mismatch'
|
||||
);
|
||||
assertEq(keccak256(returnedData), keccak256(getEncodedInitData()), 'Returned data mismatch');
|
||||
}
|
||||
}
|
||||
|
||||
//////////////
|
||||
// Collect with ProtocolSharedRevenueMinFeeMintModule
|
||||
//
|
||||
contract ProtocolSharedRevenueMinFeeMintModule_ProcessCollect is
|
||||
ProtocolSharedRevenueMinFeeMintModuleBase,
|
||||
BaseFeeCollectModule_ProcessCollect
|
||||
{
|
||||
function testProtocolSharedRevenueMinFeeMintModule_Collect() public {
|
||||
// Prevents being counted in Foundry Coverage
|
||||
}
|
||||
|
||||
address exampleExecutorClient = executorClientAddress;
|
||||
|
||||
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 view override returns (bytes memory) {
|
||||
return abi.encode(currency, amount, exampleExecutorClient);
|
||||
}
|
||||
|
||||
// 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 executorClient = makeAddr('EXECUTOR_CLIENT');
|
||||
exampleExecutorClient = executorClient;
|
||||
|
||||
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 creatorClient;
|
||||
uint256 executorClient;
|
||||
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 executorClient = makeAddr('EXECUTOR_CLIENT');
|
||||
|
||||
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()),
|
||||
creatorClient: bonsai.balanceOf(creatorClientAddress),
|
||||
executorClient: bonsai.balanceOf(executorClient),
|
||||
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||
});
|
||||
|
||||
exampleExecutorClient = executorClient;
|
||||
super.testCanCollectIfAllConditionsAreMet(
|
||||
pubId,
|
||||
transactionExecutor,
|
||||
0,
|
||||
collectLimit,
|
||||
referralFee,
|
||||
followerOnly,
|
||||
currentTimestamp,
|
||||
endTimestamp,
|
||||
recipient,
|
||||
collectorProfileOwner
|
||||
);
|
||||
|
||||
balancesAfter = Balances({
|
||||
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||
creatorClient: bonsai.balanceOf(creatorClientAddress),
|
||||
executorClient: bonsai.balanceOf(executorClient),
|
||||
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||
});
|
||||
|
||||
balancesChange = Balances({
|
||||
creator: balancesAfter.creator - balancesBefore.creator,
|
||||
protocol: balancesAfter.protocol - balancesBefore.protocol,
|
||||
creatorClient: balancesAfter.creatorClient - balancesBefore.creatorClient,
|
||||
executorClient: balancesAfter.executorClient - balancesBefore.executorClient,
|
||||
collector: balancesBefore.collector - balancesAfter.collector
|
||||
});
|
||||
|
||||
uint256 expectedCreatorFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().creatorSplit) /
|
||||
BPS_MAX;
|
||||
uint256 expectedProtocolFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().protocolSplit) /
|
||||
BPS_MAX;
|
||||
uint256 expectedCreatorClientFee = (mintFee *
|
||||
mintFeeModule.getProtocolSharedRevenueDistribution().creatorClientSplit) / BPS_MAX;
|
||||
uint256 expectedExecutorClientFee = (mintFee *
|
||||
mintFeeModule.getProtocolSharedRevenueDistribution().executorClientSplit) / BPS_MAX;
|
||||
|
||||
assertEq(balancesChange.creator, expectedCreatorFee, 'Creator balance change wrong');
|
||||
assertEq(balancesChange.protocol, expectedProtocolFee, 'Protocol balance change wrong');
|
||||
assertEq(balancesChange.creatorClient, expectedCreatorClientFee, 'CreatorClient balance change wrong');
|
||||
assertEq(balancesChange.executorClient, expectedExecutorClientFee, 'ExecutorClient balance change wrong');
|
||||
assertEq(balancesChange.collector, mintFee, 'Collector balance change wrong');
|
||||
}
|
||||
|
||||
function testMintFeeDistribution_FreePost_WithoutClients(
|
||||
uint256 pubId,
|
||||
address transactionExecutor,
|
||||
uint96 collectLimit,
|
||||
uint16 referralFee,
|
||||
bool followerOnly,
|
||||
uint72 currentTimestamp,
|
||||
uint72 endTimestamp,
|
||||
address recipient
|
||||
) public {
|
||||
address collectorProfileOwner = makeAddr('COLLECTOR_PROFILE_OWNER');
|
||||
|
||||
address executorClient = address(0);
|
||||
creatorClientAddress = 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()),
|
||||
creatorClient: bonsai.balanceOf(creatorClientAddress),
|
||||
executorClient: bonsai.balanceOf(executorClient),
|
||||
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||
});
|
||||
|
||||
console.log('creatorClient balance before: %s', balancesBefore.creatorClient);
|
||||
|
||||
exampleExecutorClient = executorClient;
|
||||
super.testCanCollectIfAllConditionsAreMet(
|
||||
pubId,
|
||||
transactionExecutor,
|
||||
0,
|
||||
collectLimit,
|
||||
referralFee,
|
||||
followerOnly,
|
||||
currentTimestamp,
|
||||
endTimestamp,
|
||||
recipient,
|
||||
collectorProfileOwner
|
||||
);
|
||||
|
||||
balancesAfter = Balances({
|
||||
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||
creatorClient: bonsai.balanceOf(creatorClientAddress),
|
||||
executorClient: bonsai.balanceOf(executorClient),
|
||||
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||
});
|
||||
|
||||
balancesChange = Balances({
|
||||
creator: balancesAfter.creator - balancesBefore.creator,
|
||||
protocol: balancesAfter.protocol - balancesBefore.protocol,
|
||||
creatorClient: balancesAfter.creatorClient - balancesBefore.creatorClient,
|
||||
executorClient: balancesAfter.executorClient - balancesBefore.executorClient,
|
||||
collector: balancesBefore.collector - balancesAfter.collector
|
||||
});
|
||||
|
||||
uint256 expectedCreatorFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().creatorSplit) /
|
||||
BPS_MAX;
|
||||
uint256 expectedProtocolFee = (mintFee * mintFeeModule.getProtocolSharedRevenueDistribution().protocolSplit) /
|
||||
BPS_MAX;
|
||||
uint256 expectedCreatorClientFee = (mintFee *
|
||||
mintFeeModule.getProtocolSharedRevenueDistribution().creatorClientSplit) / BPS_MAX;
|
||||
uint256 expectedExecutorClientFee = (mintFee *
|
||||
mintFeeModule.getProtocolSharedRevenueDistribution().executorClientSplit) / BPS_MAX;
|
||||
|
||||
assertEq(
|
||||
balancesChange.creator,
|
||||
expectedCreatorFee + expectedCreatorClientFee + expectedExecutorClientFee,
|
||||
'Creator balance change wrong'
|
||||
);
|
||||
assertEq(balancesChange.protocol, expectedProtocolFee, 'Protocol balance change wrong');
|
||||
assertEq(balancesChange.creatorClient, 0, 'CreatorClient balance change wrong');
|
||||
assertEq(balancesChange.executorClient, 0, 'ExecutorClient 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 executorClient = makeAddr('EXECUTOR_CLIENT');
|
||||
|
||||
balancesBefore = Balances({
|
||||
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||
creatorClient: bonsai.balanceOf(creatorClientAddress),
|
||||
executorClient: bonsai.balanceOf(executorClient),
|
||||
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||
});
|
||||
|
||||
exampleExecutorClient = executorClient;
|
||||
super.testCanCollectIfAllConditionsAreMet(
|
||||
pubId,
|
||||
transactionExecutor,
|
||||
amount,
|
||||
collectLimit,
|
||||
referralFee,
|
||||
followerOnly,
|
||||
currentTimestamp,
|
||||
endTimestamp,
|
||||
recipient,
|
||||
collectorProfileOwner
|
||||
);
|
||||
|
||||
balancesAfter = Balances({
|
||||
creator: bonsai.balanceOf(defaultAccount.owner),
|
||||
protocol: bonsai.balanceOf(hub.getTreasury()),
|
||||
creatorClient: bonsai.balanceOf(creatorClientAddress),
|
||||
executorClient: bonsai.balanceOf(executorClient),
|
||||
collector: bonsai.balanceOf(collectorProfileOwner)
|
||||
});
|
||||
|
||||
balancesChange = Balances({
|
||||
creator: balancesAfter.creator - balancesBefore.creator,
|
||||
protocol: balancesAfter.protocol - balancesBefore.protocol,
|
||||
creatorClient: balancesAfter.creatorClient - balancesBefore.creatorClient,
|
||||
executorClient: balancesAfter.executorClient - balancesBefore.executorClient,
|
||||
collector: balancesBefore.collector - balancesAfter.collector
|
||||
});
|
||||
|
||||
assertEq(balancesChange.creator, 0, 'Creator balance change wrong');
|
||||
assertEq(balancesChange.protocol, 0, 'Protocol balance change wrong');
|
||||
assertEq(balancesChange.creatorClient, 0, 'CreatorClient balance change wrong');
|
||||
assertEq(balancesChange.executorClient, 0, 'ExecutorClient 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();
|
||||
}
|
||||
|
||||
event MintFeeParamsSet(address token, uint256 amount, uint256 timestamp);
|
||||
event ProtocolSharedRevenueDistributionSet(ProtocolSharedRevenueDistribution distribution, uint256 timestamp);
|
||||
|
||||
// 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 creatorClientSplit,
|
||||
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));
|
||||
creatorClientSplit = uint16(bound(uint256(creatorClientSplit), 0, BPS_MAX - creatorSplit - protocolSplit));
|
||||
uint16 executorClientSplit = BPS_MAX - creatorSplit - protocolSplit - creatorClientSplit;
|
||||
|
||||
vm.expectRevert('Ownable: caller is not the owner');
|
||||
|
||||
vm.prank(notOwner);
|
||||
mintFeeModule.setProtocolSharedRevenueDistribution(
|
||||
ProtocolSharedRevenueDistribution({
|
||||
creatorSplit: creatorSplit,
|
||||
protocolSplit: protocolSplit,
|
||||
creatorClientSplit: creatorClientSplit,
|
||||
executorClientSplit: executorClientSplit
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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 creatorClientSplit,
|
||||
uint16 executorClientSplit
|
||||
) public {
|
||||
vm.assume(
|
||||
uint256(creatorSplit) +
|
||||
uint256(protocolSplit) +
|
||||
uint256(creatorClientSplit) +
|
||||
uint256(executorClientSplit) !=
|
||||
BPS_MAX
|
||||
);
|
||||
|
||||
vm.startPrank(mintFeeModule.owner());
|
||||
if (
|
||||
uint256(creatorSplit) +
|
||||
uint256(protocolSplit) +
|
||||
uint256(creatorClientSplit) +
|
||||
uint256(executorClientSplit) >
|
||||
type(uint16).max
|
||||
) {
|
||||
vm.expectRevert(stdError.arithmeticError);
|
||||
} else {
|
||||
vm.expectRevert(ModuleErrors.InvalidParams.selector);
|
||||
}
|
||||
|
||||
mintFeeModule.setProtocolSharedRevenueDistribution(
|
||||
ProtocolSharedRevenueDistribution({
|
||||
creatorSplit: creatorSplit,
|
||||
protocolSplit: protocolSplit,
|
||||
creatorClientSplit: creatorClientSplit,
|
||||
executorClientSplit: executorClientSplit
|
||||
})
|
||||
);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
// Scenarios
|
||||
|
||||
function testSetMintFeeParams(uint256 mintFee, address currency) public {
|
||||
if (mintFee > 0) {
|
||||
vm.assume(currency != address(0));
|
||||
} else {
|
||||
currency = address(0);
|
||||
}
|
||||
|
||||
vm.expectEmit(true, true, true, true, address(mintFeeModule));
|
||||
emit MintFeeParamsSet(currency, mintFee, block.timestamp);
|
||||
|
||||
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 creatorClientSplit
|
||||
) public {
|
||||
creatorSplit = uint16(bound(uint256(creatorSplit), 0, BPS_MAX));
|
||||
protocolSplit = uint16(bound(uint256(protocolSplit), 0, BPS_MAX - creatorSplit));
|
||||
creatorClientSplit = uint16(bound(uint256(creatorClientSplit), 0, BPS_MAX - creatorSplit - protocolSplit));
|
||||
uint16 executorClientSplit = BPS_MAX - creatorSplit - protocolSplit - creatorClientSplit;
|
||||
|
||||
ProtocolSharedRevenueDistribution memory expectedDistribution = ProtocolSharedRevenueDistribution({
|
||||
creatorSplit: creatorSplit,
|
||||
protocolSplit: protocolSplit,
|
||||
creatorClientSplit: creatorClientSplit,
|
||||
executorClientSplit: executorClientSplit
|
||||
});
|
||||
|
||||
vm.expectEmit(true, true, true, true, address(mintFeeModule));
|
||||
emit ProtocolSharedRevenueDistributionSet(expectedDistribution, block.timestamp);
|
||||
|
||||
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.creatorClientSplit,
|
||||
expectedDistribution.creatorClientSplit,
|
||||
'CreatorClient split mismatch'
|
||||
);
|
||||
assertEq(
|
||||
actualDistribution.executorClientSplit,
|
||||
expectedDistribution.executorClientSplit,
|
||||
'ExecutorClient split mismatch'
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user