Skip to content

Commit

Permalink
feat: Create Group with associated Feed - factory function added
Browse files Browse the repository at this point in the history
Co-authored-by: Victor Naumik <[email protected]>
  • Loading branch information
donosonaumczuk and vicnaum committed Dec 29, 2024
1 parent 47e6095 commit 4a80b96
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 49 deletions.
90 changes: 73 additions & 17 deletions contracts/dashboard/factories/LensFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
);
}
Expand All @@ -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
Expand All @@ -230,7 +286,7 @@ contract LensFactory {
namespace,
metadataURI,
_deployAccessControl(owner, admins),
ruleChanges,
rules,
extraData,
nftName,
nftSymbol,
Expand Down
14 changes: 9 additions & 5 deletions contracts/rules/feed/GroupGatedFeedRule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
18 changes: 12 additions & 6 deletions deploy/deployFactories.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
deployLensContract,
ContractType,
ContractInfo,
} from './lensUtils';
import { deployLensContract, ContractType, ContractInfo } from './lensUtils';

export default async function deployFactories(): Promise<void> {
const metadataURI = 'https://lens.dev/metadata'; // TODO: Change this to the actual metadata URI
Expand All @@ -15,7 +11,16 @@ export default async function deployFactories(): Promise<void> {
{ 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<string, ContractInfo> = {};
for (const contract of contracts) {
Expand All @@ -33,6 +38,7 @@ export default async function deployFactories(): Promise<void> {
deployedContracts['GraphFactory'].address,
deployedContracts['UsernameFactory'].address,
deployedContracts['UserBlockingRule'].address,
deployedContracts['GroupGatedFeedRule'].address,
];

await deployLensContract({
Expand Down
97 changes: 76 additions & 21 deletions deploy/deployRules.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,89 @@
import {
deployLensContract,
ContractType,
ContractInfo,
} from './lensUtils';
import { deployLensContract, ContractType, ContractInfo } from './lensUtils';

export async function deployRules(): Promise<void> {
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) {
Expand Down

0 comments on commit 4a80b96

Please sign in to comment.