From 4a80b9639dce91f35ff6cc389cda3a0f9e621122 Mon Sep 17 00:00:00 2001 From: Alan Donoso Naumczuk Date: Sun, 29 Dec 2024 15:12:31 -0300 Subject: [PATCH] feat: Create Group with associated Feed - factory function added Co-authored-by: Victor Naumik --- contracts/dashboard/factories/LensFactory.sol | 90 +++++++++++++---- contracts/rules/feed/GroupGatedFeedRule.sol | 14 ++- deploy/deployFactories.ts | 18 ++-- deploy/deployRules.ts | 97 +++++++++++++++---- 4 files changed, 170 insertions(+), 49 deletions(-) diff --git a/contracts/dashboard/factories/LensFactory.sol b/contracts/dashboard/factories/LensFactory.sol index 553b651a..f84cb53f 100644 --- a/contracts/dashboard/factories/LensFactory.sol +++ b/contracts/dashboard/factories/LensFactory.sol @@ -27,10 +27,13 @@ import {ITokenURIProvider} from "./../../core/interfaces/ITokenURIProvider.sol"; import {LensUsernameTokenURIProvider} from "./../../core/primitives/username/LensUsernameTokenURIProvider.sol"; import {IFeedRule} from "./../../core/interfaces/IFeedRule.sol"; import {IGraphRule} from "./../../core/interfaces/IGraphRule.sol"; +import {GROUP_PARAM_KEY} from "./../../rules/feed/GroupGatedFeedRule.sol"; // TODO: Move this some place else or remove interface IOwnable { - function transferOwnership(address newOwner) external; + function transferOwnership( + address newOwner + ) external; function owner() external view returns (address); } @@ -53,6 +56,7 @@ contract LensFactory { UsernameFactory internal immutable USERNAME_FACTORY; IAccessControl internal immutable _factoryOwnedAccessControl; address internal immutable _userBlockingRule; + address internal immutable _groupGatedFeedRule; constructor( AccessControlFactory accessControlFactory, @@ -62,7 +66,8 @@ contract LensFactory { FeedFactory feedFactory, GraphFactory graphFactory, UsernameFactory usernameFactory, - address userBlockingRule + address userBlockingRule, + address groupGatedFeedRule ) { ACCESS_CONTROL_FACTORY = accessControlFactory; ACCOUNT_FACTORY = accountFactory; @@ -73,6 +78,7 @@ contract LensFactory { USERNAME_FACTORY = usernameFactory; _factoryOwnedAccessControl = new RoleBasedAccessControl({owner: address(this)}); _userBlockingRule = userBlockingRule; + _groupGatedFeedRule = groupGatedFeedRule; } // TODO: This function belongs to an App probably. @@ -122,6 +128,56 @@ contract LensFactory { return account; } + function createGroupWithFeed( + address owner, + address[] calldata admins, + string calldata groupMetadataURI, + RuleChange[] calldata groupRules, + KeyValue[] calldata groupExtraData, + string calldata feedMetadataURI, + RuleChange[] calldata feedRules, + KeyValue[] calldata feedExtraData + ) external returns (address, address) { + address group = + GROUP_FACTORY.deployGroup(groupMetadataURI, _deployAccessControl(owner, admins), groupRules, groupExtraData); + + RuleChange[] memory modifiedFeedRules = new RuleChange[](feedRules.length + 2); + + RuleSelectorChange[] memory selectorChanges = new RuleSelectorChange[](1); + // Both rules only operate on IFeedRule.processCreatePost.selector (at least at the moment of writing this) + selectorChanges[0] = + RuleSelectorChange({ruleSelector: IFeedRule.processCreatePost.selector, isRequired: true, enabled: true}); + + modifiedFeedRules[0] = RuleChange({ + ruleAddress: _userBlockingRule, + configSalt: bytes32(0), + configurationChanges: RuleConfigurationChange({configure: true, ruleParams: new KeyValue[](0)}), + selectorChanges: selectorChanges + }); + + KeyValue[] memory groupGatedRuleParams = new KeyValue[](1); + groupGatedRuleParams[0] = KeyValue({key: GROUP_PARAM_KEY, value: abi.encode(group)}); + + modifiedFeedRules[1] = RuleChange({ + ruleAddress: _groupGatedFeedRule, + configSalt: bytes32(0), + configurationChanges: RuleConfigurationChange({configure: true, ruleParams: groupGatedRuleParams}), + selectorChanges: selectorChanges + }); + + for (uint256 i = 0; i < feedRules.length; i++) { + require(feedRules[i].ruleAddress != _userBlockingRule, "UserBlockingRule was already prepended"); + require(feedRules[i].ruleAddress != _groupGatedFeedRule, "GroupGatedRule was already prepended"); + modifiedFeedRules[i + 2] = feedRules[i]; + } + + address feed = FEED_FACTORY.deployFeed( + feedMetadataURI, _deployAccessControl(owner, admins), modifiedFeedRules, feedExtraData + ); + + return (group, feed); + } + function deployAccount( string calldata metadataURI, address owner, @@ -156,61 +212,61 @@ contract LensFactory { string calldata metadataURI, address owner, address[] calldata admins, - RuleChange[] calldata ruleChanges, + RuleChange[] calldata rules, KeyValue[] calldata extraData ) external returns (address) { - return GROUP_FACTORY.deployGroup(metadataURI, _deployAccessControl(owner, admins), ruleChanges, extraData); + return GROUP_FACTORY.deployGroup(metadataURI, _deployAccessControl(owner, admins), rules, extraData); } function deployFeed( string calldata metadataURI, address owner, address[] calldata admins, - RuleChange[] calldata ruleChanges, + RuleChange[] calldata rules, KeyValue[] calldata extraData ) external returns (address) { return FEED_FACTORY.deployFeed( metadataURI, _deployAccessControl(owner, admins), - _prependUserBlocking(ruleChanges, IFeedRule.processCreatePost.selector), + _prependUserBlocking(rules, IFeedRule.processCreatePost.selector), extraData ); } function _prependUserBlocking( - RuleChange[] calldata ruleChanges, + RuleChange[] calldata rules, bytes4 ruleSelector ) internal view returns (RuleChange[] memory) { - RuleChange[] memory modifiedRuleChanges = new RuleChange[](ruleChanges.length + 1); + RuleChange[] memory modifiedRules = new RuleChange[](rules.length + 1); RuleSelectorChange[] memory selectorChanges = new RuleSelectorChange[](1); selectorChanges[0] = RuleSelectorChange({ruleSelector: ruleSelector, isRequired: true, enabled: true}); - modifiedRuleChanges[0] = RuleChange({ + modifiedRules[0] = RuleChange({ ruleAddress: _userBlockingRule, configSalt: bytes32(0), configurationChanges: RuleConfigurationChange({configure: true, ruleParams: new KeyValue[](0)}), selectorChanges: selectorChanges }); - for (uint256 i = 0; i < ruleChanges.length; i++) { - require(ruleChanges[i].ruleAddress != _userBlockingRule, "UserBlockingRule was already prepended"); - modifiedRuleChanges[i + 1] = modifiedRuleChanges[i]; + for (uint256 i = 0; i < rules.length; i++) { + require(rules[i].ruleAddress != _userBlockingRule, "UserBlockingRule was already prepended"); + modifiedRules[i + 1] = rules[i]; } - return modifiedRuleChanges; + return modifiedRules; } function deployGraph( string calldata metadataURI, address owner, address[] calldata admins, - RuleChange[] calldata ruleChanges, + RuleChange[] calldata rules, KeyValue[] calldata extraData ) external returns (address) { return GRAPH_FACTORY.deployGraph( metadataURI, _deployAccessControl(owner, admins), - _prependUserBlocking(ruleChanges, IGraphRule.processFollow.selector), + _prependUserBlocking(rules, IGraphRule.processFollow.selector), extraData ); } @@ -220,7 +276,7 @@ contract LensFactory { string calldata metadataURI, address owner, address[] calldata admins, - RuleChange[] calldata ruleChanges, + RuleChange[] calldata rules, KeyValue[] calldata extraData, string calldata nftName, string calldata nftSymbol @@ -230,7 +286,7 @@ contract LensFactory { namespace, metadataURI, _deployAccessControl(owner, admins), - ruleChanges, + rules, extraData, nftName, nftSymbol, diff --git a/contracts/rules/feed/GroupGatedFeedRule.sol b/contracts/rules/feed/GroupGatedFeedRule.sol index a83b8462..eb89edaf 100644 --- a/contracts/rules/feed/GroupGatedFeedRule.sol +++ b/contracts/rules/feed/GroupGatedFeedRule.sol @@ -8,19 +8,23 @@ import {IGroup} from "./../../core/interfaces/IGroup.sol"; import {KeyValue, RuleChange} from "./../../core/types/Types.sol"; import {MetadataBased} from "./../../core/base/MetadataBased.sol"; +// keccak256("lens.param.key.group"); +bytes32 constant GROUP_PARAM_KEY = 0xe556a4384e8a110aab4ea745eff2c09de81f87f56e4ecba2205982230d3bd4f4; + contract GroupGatedFeedRule is IFeedRule, MetadataBased { event Lens_Rule_MetadataURISet(string metadataURI); - // keccak256("lens.param.key.group"); - bytes32 immutable GROUP_PARAM_KEY = 0xe556a4384e8a110aab4ea745eff2c09de81f87f56e4ecba2205982230d3bd4f4; - mapping(address => mapping(bytes32 => address)) internal _groupGate; - constructor(string memory metadataURI) { + constructor( + string memory metadataURI + ) { _setMetadataURI(metadataURI); } - function _emitMetadataURISet(string memory metadataURI) internal override { + function _emitMetadataURISet( + string memory metadataURI + ) internal override { emit Lens_Rule_MetadataURISet(metadataURI); } diff --git a/deploy/deployFactories.ts b/deploy/deployFactories.ts index d44b6b51..280104a8 100644 --- a/deploy/deployFactories.ts +++ b/deploy/deployFactories.ts @@ -1,8 +1,4 @@ -import { - deployLensContract, - ContractType, - ContractInfo, -} from './lensUtils'; +import { deployLensContract, ContractType, ContractInfo } from './lensUtils'; export default async function deployFactories(): Promise { const metadataURI = 'https://lens.dev/metadata'; // TODO: Change this to the actual metadata URI @@ -15,7 +11,16 @@ export default async function deployFactories(): Promise { { contractName: 'GraphFactory', contractType: ContractType.Factory }, { contractName: 'GroupFactory', contractType: ContractType.Factory }, { contractName: 'UsernameFactory', contractType: ContractType.Factory }, - { contractName: 'UserBlockingRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'UserBlockingRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'GroupGatedFeedRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, ]; const deployedContracts: Record = {}; for (const contract of contracts) { @@ -33,6 +38,7 @@ export default async function deployFactories(): Promise { deployedContracts['GraphFactory'].address, deployedContracts['UsernameFactory'].address, deployedContracts['UserBlockingRule'].address, + deployedContracts['GroupGatedFeedRule'].address, ]; await deployLensContract({ diff --git a/deploy/deployRules.ts b/deploy/deployRules.ts index e13e952e..ace9b935 100644 --- a/deploy/deployRules.ts +++ b/deploy/deployRules.ts @@ -1,34 +1,89 @@ -import { - deployLensContract, - ContractType, - ContractInfo, -} from './lensUtils'; +import { deployLensContract, ContractType, ContractInfo } from './lensUtils'; export async function deployRules(): Promise { const metadataURI = 'https://lens.dev/metadata'; // TODO: Change this to the actual metadata URI const contracts: ContractInfo[] = [ // Feed Rules - { contractName: 'GroupGatedFeedRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'RestrictedSignersFeedRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'SimplePaymentFeedRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'TokenGatedFeedRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'RestrictedSignersFeedRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'SimplePaymentFeedRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'TokenGatedFeedRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, // Post Rules - { contractName: 'FollowersOnlyPostRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'FollowersOnlyPostRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, // Graph Rules - { contractName: 'RestrictedSignersGraphRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'TokenGatedGraphRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'RestrictedSignersGraphRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'TokenGatedGraphRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, // Follow Rules - { contractName: 'SimplePaymentFollowRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'TokenGatedFollowRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'SimplePaymentFollowRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'TokenGatedFollowRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, // Group Rules - { contractName: 'MembershipApprovalGroupRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'SimplePaymentGroupRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'TokenGatedGroupRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'MembershipApprovalGroupRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'SimplePaymentGroupRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'TokenGatedGroupRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, // Username Rules - { contractName: 'CharsetUsernameRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'LengthUsernameRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'SimplePaymentUsernameRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, - { contractName: 'TokenGatedUsernameRule', contractType: ContractType.Rule, constructorArguments: [metadataURI] }, + { + contractName: 'CharsetUsernameRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'LengthUsernameRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'SimplePaymentUsernameRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, + { + contractName: 'TokenGatedUsernameRule', + contractType: ContractType.Rule, + constructorArguments: [metadataURI], + }, ]; for (const contract of contracts) {