lens-modules 1.0.13 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/contracts/FollowNFT.sol +507 -0
  2. package/contracts/LensHub.sol +590 -0
  3. package/contracts/base/LensGovernable.sol +114 -0
  4. package/contracts/base/LensHubEventHooks.sol +42 -0
  5. package/contracts/base/LensHubStorage.sol +69 -0
  6. package/contracts/base/LensImplGetters.sol +32 -0
  7. package/contracts/base/LensProfiles.sol +162 -0
  8. package/contracts/base/LensVersion.sol +36 -0
  9. package/contracts/base/upgradeability/FollowNFTProxy.sol +21 -0
  10. package/contracts/interfaces/IFollowTokenURI.sol +11 -0
  11. package/contracts/interfaces/ILegacyCollectModule.sol +45 -0
  12. package/contracts/interfaces/ILegacyCollectNFT.sol +46 -0
  13. package/contracts/interfaces/ILegacyFollowModule.sol +82 -0
  14. package/contracts/interfaces/IProfileTokenURI.sol +7 -0
  15. package/contracts/interfaces/ITokenHandleRegistry.sol +90 -0
  16. package/contracts/libraries/ActionLib.sol +69 -0
  17. package/contracts/libraries/FollowLib.sol +175 -0
  18. package/contracts/libraries/GovernanceLib.sol +111 -0
  19. package/contracts/libraries/LegacyCollectLib.sol +176 -0
  20. package/contracts/libraries/ProfileLib.sol +284 -0
  21. package/contracts/libraries/PublicationLib.sol +567 -0
  22. package/contracts/libraries/ValidationLib.sol +228 -0
  23. package/dist/index.d.ts +5 -0
  24. package/dist/{src/index.js → index.js} +4 -4
  25. package/dist/verified-modules/follow-modules.d.ts +12 -0
  26. package/dist/verified-modules/follow-modules.js +15 -0
  27. package/dist/verified-modules/open-actions.d.ts +42 -0
  28. package/dist/verified-modules/open-actions.js +45 -0
  29. package/dist/verified-modules/reference-modules.d.ts +17 -0
  30. package/dist/verified-modules/reference-modules.js +20 -0
  31. package/package.json +1 -1
  32. package/dist/src/index.d.ts +0 -5
  33. /package/dist/{deployments/lens-contracts.d.ts → lens-contracts.d.ts} +0 -0
  34. /package/dist/{deployments/lens-contracts.js → lens-contracts.js} +0 -0
  35. /package/dist/{src/lenshub-abi.d.ts → lenshub-abi.d.ts} +0 -0
  36. /package/dist/{src/lenshub-abi.js → lenshub-abi.js} +0 -0
@@ -0,0 +1,90 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity >=0.6.0;
4
+
5
+ /**
6
+ * @title ITokenHandleRegistry
7
+ * @author Lens Protocol
8
+ *
9
+ * @notice The interface for TokenHandleRegistry contract that is responsible for linking a handle NFT to a token NFT.
10
+ * Linking means a connection between the two NFTs is created, and the handle NFT can be used to resolve the token NFT
11
+ * or vice versa.
12
+ * The registry is responsible for keeping track of the links between the NFTs, and for resolving them.
13
+ * The first version of the registry is hard-coded to support only the .lens namespace and the Lens Protocol Profiles.
14
+ */
15
+ interface ITokenHandleRegistry {
16
+ /**
17
+ * @notice Lens V1 -> V2 migration function. Links a handle NFT to a profile NFT without additional checks to save
18
+ * gas.
19
+ * Will be called by the migration function (in MigrationLib) in LensHub, only for new handles being migrated.
20
+ *
21
+ * @custom:permissions LensHub
22
+ *
23
+ * @param handleId ID of the .lens namespace handle NFT
24
+ * @param profileId ID of the Lens Protocol Profile NFT
25
+ */
26
+ function migrationLink(uint256 handleId, uint256 profileId) external;
27
+
28
+ /**
29
+ * @notice Links a handle NFT with a profile NFT.
30
+ * Linking means a connection between the two NFTs is created, and the handle NFT can be used to resolve the profile
31
+ * NFT or vice versa.
32
+ * @custom:permissions Both NFTs must be owned by the same address, and caller must be the owner of profile or its
33
+ * approved DelegatedExecutor.
34
+ *
35
+ * @dev In the first version of the registry, the NFT contracts are hard-coded:
36
+ * - Handle is hard-coded to be of the .lens namespace
37
+ * - Token is hard-coded to be of the Lens Protocol Profile
38
+ * In future versions, the registry will be more flexible and allow for different namespaces and tokens, so this
39
+ * function might be deprecated and replaced with a new one accepting addresses of the handle and token contracts.
40
+ *
41
+ * @param handleId ID of the .lens namespace handle NFT
42
+ * @param profileId ID of the Lens Protocol Profile NFT
43
+ */
44
+ function link(uint256 handleId, uint256 profileId) external;
45
+
46
+ /**
47
+ * @notice Unlinks a handle NFT from a profile NFT.
48
+ * @custom:permissions Caller can be the owner of either of the NFTs.
49
+ *
50
+ * @dev In the first version of the registry, the contracts are hard-coded:
51
+ * - Handle is hard-coded to be of the .lens namespace
52
+ * - Token is hard-coded to be of the Lens Protocol Profile
53
+ * In future versions, the registry will be more flexible and allow for different namespaces and tokens, so this
54
+ * function might be deprecated and replaced with a new one accepting addresses of the handle and token contracts.
55
+ *
56
+ * @param handleId ID of the .lens namespace handle NFT
57
+ * @param profileId ID of the Lens Protocol Profile NFT
58
+ */
59
+ function unlink(uint256 handleId, uint256 profileId) external;
60
+
61
+ /**
62
+ * @notice Resolves a handle NFT to a profile NFT.
63
+ *
64
+ * @dev In the first version of the registry, the contracts are hard-coded:
65
+ * - Handle is hard-coded to be of the .lens namespace
66
+ * - Token is hard-coded to be of the Lens Protocol Profile
67
+ * In future versions, the registry will be more flexible and allow for different namespaces and tokens, so this
68
+ * function might be deprecated and replaced with a new one.
69
+ *
70
+ * @param handleId ID of the .lens namespace handle NFT
71
+ *
72
+ * @return tokenId ID of the Lens Protocol Profile NFT
73
+ */
74
+ function resolve(uint256 handleId) external view returns (uint256);
75
+
76
+ /**
77
+ * @notice Gets a default handle for a profile NFT (aka reverse resolution).
78
+ *
79
+ * @dev In the first version of the registry, the contracts are hard-coded:
80
+ * - Handle is hard-coded to be of the .lens namespace
81
+ * - Token is hard-coded to be of the Lens Protocol Profile
82
+ * In future versions, the registry will be more flexible and allow for different namespaces and tokens, so this
83
+ * function might be deprecated and replaced with a new one.
84
+ *
85
+ * @param tokenId ID of the Lens Protocol Profile NFT
86
+ *
87
+ * @return handleId ID of the .lens namespace handle NFT
88
+ */
89
+ function getDefaultHandle(uint256 tokenId) external view returns (uint256);
90
+ }
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.15;
4
+
5
+ import {Types} from './constants/Types.sol';
6
+ import {StorageLib} from './StorageLib.sol';
7
+ import {ValidationLib} from './ValidationLib.sol';
8
+ import {IPublicationActionModule} from '../interfaces/IPublicationActionModule.sol';
9
+ import {Errors} from './constants/Errors.sol';
10
+ import {Events} from './constants/Events.sol';
11
+
12
+ library ActionLib {
13
+ function act(
14
+ Types.PublicationActionParams calldata publicationActionParams,
15
+ address transactionExecutor,
16
+ address actorProfileOwner
17
+ ) external returns (bytes memory) {
18
+ ValidationLib.validateNotBlocked({
19
+ profile: publicationActionParams.actorProfileId,
20
+ byProfile: publicationActionParams.publicationActedProfileId
21
+ });
22
+
23
+ Types.Publication storage _actedOnPublication = StorageLib.getPublication(
24
+ publicationActionParams.publicationActedProfileId,
25
+ publicationActionParams.publicationActedId
26
+ );
27
+
28
+ if (!_isActionEnabled(_actedOnPublication, publicationActionParams.actionModuleAddress)) {
29
+ // This will also revert for:
30
+ // - Non-existent action modules
31
+ // - Non-existent publications
32
+ // - Legacy V1 publications
33
+ // Because the storage will be empty.
34
+ revert Errors.ActionNotAllowed();
35
+ }
36
+
37
+ Types.PublicationType[] memory referrerPubTypes = ValidationLib.validateReferrersAndGetReferrersPubTypes(
38
+ publicationActionParams.referrerProfileIds,
39
+ publicationActionParams.referrerPubIds,
40
+ publicationActionParams.publicationActedProfileId,
41
+ publicationActionParams.publicationActedId
42
+ );
43
+
44
+ bytes memory actionModuleReturnData = IPublicationActionModule(publicationActionParams.actionModuleAddress)
45
+ .processPublicationAction(
46
+ Types.ProcessActionParams({
47
+ publicationActedProfileId: publicationActionParams.publicationActedProfileId,
48
+ publicationActedId: publicationActionParams.publicationActedId,
49
+ actorProfileId: publicationActionParams.actorProfileId,
50
+ actorProfileOwner: actorProfileOwner,
51
+ transactionExecutor: transactionExecutor,
52
+ referrerProfileIds: publicationActionParams.referrerProfileIds,
53
+ referrerPubIds: publicationActionParams.referrerPubIds,
54
+ referrerPubTypes: referrerPubTypes,
55
+ actionModuleData: publicationActionParams.actionModuleData
56
+ })
57
+ );
58
+ emit Events.Acted(publicationActionParams, actionModuleReturnData, transactionExecutor, block.timestamp);
59
+
60
+ return actionModuleReturnData;
61
+ }
62
+
63
+ function _isActionEnabled(
64
+ Types.Publication storage _publication,
65
+ address actionModuleAddress
66
+ ) private view returns (bool) {
67
+ return _publication.actionModuleEnabled[actionModuleAddress];
68
+ }
69
+ }
@@ -0,0 +1,175 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.15;
4
+
5
+ import {IFollowModule} from '../interfaces/IFollowModule.sol';
6
+ import {ValidationLib} from './ValidationLib.sol';
7
+ import {Types} from './constants/Types.sol';
8
+ import {Errors} from './constants/Errors.sol';
9
+ import {Events} from './constants/Events.sol';
10
+ import {StorageLib} from './StorageLib.sol';
11
+ import {IFollowNFT} from '../interfaces/IFollowNFT.sol';
12
+ import {FollowNFTProxy} from '../base/upgradeability/FollowNFTProxy.sol';
13
+
14
+ library FollowLib {
15
+ function follow(
16
+ uint256 followerProfileId,
17
+ address transactionExecutor,
18
+ uint256[] calldata idsOfProfilesToFollow,
19
+ uint256[] calldata followTokenIds,
20
+ bytes[] calldata followModuleDatas
21
+ ) external returns (uint256[] memory) {
22
+ if (
23
+ idsOfProfilesToFollow.length != followTokenIds.length ||
24
+ idsOfProfilesToFollow.length != followModuleDatas.length
25
+ ) {
26
+ revert Errors.ArrayMismatch();
27
+ }
28
+ uint256[] memory followTokenIdsAssigned = new uint256[](idsOfProfilesToFollow.length);
29
+ uint256 i;
30
+ while (i < idsOfProfilesToFollow.length) {
31
+ ValidationLib.validateProfileExists({profileId: idsOfProfilesToFollow[i]});
32
+
33
+ ValidationLib.validateNotBlocked({
34
+ profile: followerProfileId,
35
+ byProfile: idsOfProfilesToFollow[i],
36
+ unidirectionalCheck: true // We allow to follow a blocked profile. Rest of interactions are restricted.
37
+ });
38
+
39
+ if (followerProfileId == idsOfProfilesToFollow[i]) {
40
+ revert Errors.SelfFollow();
41
+ }
42
+
43
+ followTokenIdsAssigned[i] = _follow({
44
+ followerProfileId: followerProfileId,
45
+ transactionExecutor: transactionExecutor,
46
+ idOfProfileToFollow: idsOfProfilesToFollow[i],
47
+ followTokenId: followTokenIds[i],
48
+ followModuleData: followModuleDatas[i]
49
+ });
50
+
51
+ unchecked {
52
+ ++i;
53
+ }
54
+ }
55
+ return followTokenIdsAssigned;
56
+ }
57
+
58
+ function unfollow(
59
+ uint256 unfollowerProfileId,
60
+ address transactionExecutor,
61
+ uint256[] calldata idsOfProfilesToUnfollow
62
+ ) external {
63
+ uint256 i;
64
+ while (i < idsOfProfilesToUnfollow.length) {
65
+ uint256 idOfProfileToUnfollow = idsOfProfilesToUnfollow[i];
66
+
67
+ address followNFT = StorageLib.getProfile(idOfProfileToUnfollow).followNFT;
68
+
69
+ // We don't validate the profile exists because we want to allow unfollowing a burnt profile.
70
+ // Because, if the profile never existed, followNFT will be address(0) and the call will revert.
71
+ if (followNFT == address(0)) {
72
+ revert Errors.NotFollowing();
73
+ }
74
+
75
+ IFollowNFT(followNFT).unfollow(unfollowerProfileId);
76
+
77
+ emit Events.Unfollowed(unfollowerProfileId, idOfProfileToUnfollow, transactionExecutor, block.timestamp);
78
+
79
+ unchecked {
80
+ ++i;
81
+ }
82
+ }
83
+ }
84
+
85
+ function isFollowing(uint256 followerProfileId, uint256 followedProfileId) internal view returns (bool) {
86
+ address followNFT = StorageLib.getProfile(followedProfileId).followNFT;
87
+ return followNFT != address(0) && IFollowNFT(followNFT).isFollowing(followerProfileId);
88
+ }
89
+
90
+ /**
91
+ * @notice Deploys the given profile's Follow NFT contract.
92
+ *
93
+ * @param profileId The token ID of the profile which Follow NFT should be deployed.
94
+ *
95
+ * @return address The address of the deployed Follow NFT contract.
96
+ */
97
+ function _deployFollowNFT(uint256 profileId) private returns (address) {
98
+ bytes memory functionData = abi.encodeCall(IFollowNFT.initialize, profileId);
99
+ address followNFT = address(new FollowNFTProxy(functionData));
100
+ emit Events.FollowNFTDeployed(profileId, followNFT, block.timestamp);
101
+
102
+ return followNFT;
103
+ }
104
+
105
+ function _follow(
106
+ uint256 followerProfileId,
107
+ address transactionExecutor,
108
+ uint256 idOfProfileToFollow,
109
+ uint256 followTokenId,
110
+ bytes calldata followModuleData
111
+ ) private returns (uint256) {
112
+ Types.Profile storage _profileToFollow = StorageLib.getProfile(idOfProfileToFollow);
113
+
114
+ address followNFT = _profileToFollow.followNFT;
115
+ if (followNFT == address(0)) {
116
+ followNFT = _deployFollowNFT(idOfProfileToFollow);
117
+ _profileToFollow.followNFT = followNFT;
118
+ }
119
+
120
+ return
121
+ _processFollow(
122
+ ProcessFollowParams({
123
+ followNFT: followNFT,
124
+ followerProfileId: followerProfileId,
125
+ transactionExecutor: transactionExecutor,
126
+ idOfProfileToFollow: idOfProfileToFollow,
127
+ followTokenId: followTokenId,
128
+ followModule: _profileToFollow.followModule,
129
+ followModuleData: followModuleData
130
+ })
131
+ );
132
+ }
133
+
134
+ // Struct defined for the sole purpose of avoiding 'stack too deep' error.
135
+ struct ProcessFollowParams {
136
+ address followNFT;
137
+ uint256 followerProfileId;
138
+ address transactionExecutor;
139
+ uint256 idOfProfileToFollow;
140
+ uint256 followTokenId;
141
+ address followModule;
142
+ bytes followModuleData;
143
+ }
144
+
145
+ function _processFollow(ProcessFollowParams memory processFollowParams) private returns (uint256) {
146
+ uint256 followTokenIdAssigned = IFollowNFT(processFollowParams.followNFT).follow({
147
+ followerProfileId: processFollowParams.followerProfileId,
148
+ transactionExecutor: processFollowParams.transactionExecutor,
149
+ followTokenId: processFollowParams.followTokenId
150
+ });
151
+
152
+ bytes memory processFollowModuleReturnData;
153
+ if (processFollowParams.followModule != address(0)) {
154
+ processFollowModuleReturnData = IFollowModule(processFollowParams.followModule).processFollow(
155
+ processFollowParams.followerProfileId,
156
+ processFollowParams.followTokenId,
157
+ processFollowParams.transactionExecutor,
158
+ processFollowParams.idOfProfileToFollow,
159
+ processFollowParams.followModuleData
160
+ );
161
+ }
162
+
163
+ emit Events.Followed(
164
+ processFollowParams.followerProfileId,
165
+ processFollowParams.idOfProfileToFollow,
166
+ followTokenIdAssigned,
167
+ processFollowParams.followModuleData,
168
+ processFollowModuleReturnData,
169
+ processFollowParams.transactionExecutor,
170
+ block.timestamp
171
+ );
172
+
173
+ return followTokenIdAssigned;
174
+ }
175
+ }
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.15;
4
+
5
+ import {Types} from './constants/Types.sol';
6
+ import {Errors} from './constants/Errors.sol';
7
+ import {StorageLib} from './StorageLib.sol';
8
+ import {Events} from './constants/Events.sol';
9
+
10
+ library GovernanceLib {
11
+ uint16 internal constant BPS_MAX = 10000;
12
+
13
+ /**
14
+ * @notice Sets the governance address.
15
+ *
16
+ * @param newGovernance The new governance address to set.
17
+ */
18
+ function setGovernance(address newGovernance) external {
19
+ address prevGovernance = StorageLib.getGovernance();
20
+ StorageLib.setGovernance(newGovernance);
21
+ emit Events.GovernanceSet(msg.sender, prevGovernance, newGovernance, block.timestamp);
22
+ }
23
+
24
+ /**
25
+ * @notice Sets the emergency admin address.
26
+ *
27
+ * @param newEmergencyAdmin The new governance address to set.
28
+ */
29
+ function setEmergencyAdmin(address newEmergencyAdmin) external {
30
+ address prevEmergencyAdmin = StorageLib.getEmergencyAdmin();
31
+ StorageLib.setEmergencyAdmin(newEmergencyAdmin);
32
+ emit Events.EmergencyAdminSet(msg.sender, prevEmergencyAdmin, newEmergencyAdmin, block.timestamp);
33
+ }
34
+
35
+ /**
36
+ * @notice Sets the protocol state, only meant to be called at initialization since
37
+ * this does not validate the caller.
38
+ *
39
+ * @param newState The new protocol state to set.
40
+ */
41
+ function initState(Types.ProtocolState newState) external {
42
+ _setState(newState);
43
+ }
44
+
45
+ /**
46
+ * @notice Sets the protocol state and validates the caller. The emergency admin can only
47
+ * pause further (Unpaused => PublishingPaused => Paused). Whereas governance can set any
48
+ * state.
49
+ *
50
+ * @param newState The new protocol state to set.
51
+ */
52
+ function setState(Types.ProtocolState newState) external {
53
+ // NOTE: This does not follow the CEI-pattern, but there is no interaction and this allows to abstract `_setState` logic.
54
+ Types.ProtocolState prevState = _setState(newState);
55
+
56
+ if (msg.sender != StorageLib.getGovernance()) {
57
+ // If the sender is the emergency admin, prevent them from reducing restrictions.
58
+ if (msg.sender == StorageLib.getEmergencyAdmin()) {
59
+ if (newState <= prevState) {
60
+ revert Errors.EmergencyAdminCanOnlyPauseFurther();
61
+ }
62
+ } else {
63
+ revert Errors.NotGovernanceOrEmergencyAdmin();
64
+ }
65
+ }
66
+ }
67
+
68
+ function _setState(Types.ProtocolState newState) private returns (Types.ProtocolState) {
69
+ Types.ProtocolState prevState = StorageLib.getState();
70
+ StorageLib.setState(newState);
71
+ emit Events.StateSet(msg.sender, prevState, newState, block.timestamp);
72
+ return prevState;
73
+ }
74
+
75
+ function whitelistProfileCreator(address profileCreator, bool whitelist) external {
76
+ StorageLib.profileCreatorWhitelisted()[profileCreator] = whitelist;
77
+ emit Events.ProfileCreatorWhitelisted(profileCreator, whitelist, block.timestamp);
78
+ }
79
+
80
+ function setTreasury(address newTreasury) internal {
81
+ if (newTreasury == address(0)) {
82
+ revert Errors.InitParamsInvalid();
83
+ }
84
+ Types.TreasuryData storage _treasuryData = StorageLib.getTreasuryData();
85
+
86
+ address prevTreasury = _treasuryData.treasury;
87
+ _treasuryData.treasury = newTreasury;
88
+
89
+ emit Events.TreasurySet(prevTreasury, newTreasury, block.timestamp);
90
+ }
91
+
92
+ function setTreasuryFee(uint16 newTreasuryFee) internal {
93
+ if (newTreasuryFee >= BPS_MAX / 2) {
94
+ revert Errors.InitParamsInvalid();
95
+ }
96
+ Types.TreasuryData storage _treasuryData = StorageLib.getTreasuryData();
97
+
98
+ uint16 prevTreasuryFee = _treasuryData.treasuryFeeBPS;
99
+ _treasuryData.treasuryFeeBPS = newTreasuryFee;
100
+
101
+ emit Events.TreasuryFeeSet(prevTreasuryFee, newTreasuryFee, block.timestamp);
102
+ }
103
+
104
+ function setProfileTokenURIContract(address profileTokenURIContract) external {
105
+ StorageLib.setProfileTokenURIContract(profileTokenURIContract);
106
+ }
107
+
108
+ function setFollowTokenURIContract(address followTokenURIContract) external {
109
+ StorageLib.setFollowTokenURIContract(followTokenURIContract);
110
+ }
111
+ }
@@ -0,0 +1,176 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.15;
4
+
5
+ import {ValidationLib} from './ValidationLib.sol';
6
+ import {Types} from './constants/Types.sol';
7
+ import {Errors} from './constants/Errors.sol';
8
+ import {Events} from './constants/Events.sol';
9
+ import {ICollectNFT} from '../interfaces/ICollectNFT.sol';
10
+ import {ILegacyCollectModule} from '../interfaces/ILegacyCollectModule.sol';
11
+ import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
12
+ import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
13
+ import {StorageLib} from './StorageLib.sol';
14
+ import {FollowLib} from './FollowLib.sol';
15
+
16
+ /**
17
+ * @title LegacyCollectLib
18
+ * @author Lens Protocol
19
+ * @notice Library containing the logic for legacy collect operation.
20
+ */
21
+ library LegacyCollectLib {
22
+ using Strings for uint256;
23
+
24
+ /**
25
+ * @dev Emitted upon a successful legacy collect action.
26
+ *
27
+ * @param publicationCollectedProfileId The profile ID of the publication being collected.
28
+ * @param publicationCollectedId The publication ID of the publication being collected.
29
+ * @param collectorProfileId The profile ID of the profile that collected the publication.
30
+ * @param transactionExecutor The address of the account that executed the collect transaction.
31
+ * @param referrerProfileId The profile ID of the referrer, if any. Zero if no referrer.
32
+ * @param referrerPubId The publication ID of the referrer, if any. Zero if no referrer.
33
+ * @param collectModule The address of the collect module that was used to collect the publication.
34
+ * @param collectModuleData The data passed to the collect module's collect action. This is ABI-encoded and depends
35
+ * on the collect module chosen.
36
+ * @param tokenId The token ID of the collect NFT that was minted as a collect of the publication.
37
+ * @param timestamp The current block timestamp.
38
+ */
39
+ event CollectedLegacy(
40
+ uint256 indexed publicationCollectedProfileId,
41
+ uint256 indexed publicationCollectedId,
42
+ uint256 indexed collectorProfileId,
43
+ address transactionExecutor,
44
+ uint256 referrerProfileId,
45
+ uint256 referrerPubId,
46
+ address collectModule,
47
+ bytes collectModuleData,
48
+ uint256 tokenId,
49
+ uint256 timestamp
50
+ );
51
+
52
+ function collect(
53
+ Types.LegacyCollectParams calldata collectParams,
54
+ address transactionExecutor,
55
+ address collectorProfileOwner,
56
+ address collectNFTImpl
57
+ ) external returns (uint256) {
58
+ ValidationLib.validateNotBlocked({
59
+ profile: collectParams.collectorProfileId,
60
+ byProfile: collectParams.publicationCollectedProfileId
61
+ });
62
+
63
+ address collectModule;
64
+ uint256 tokenId;
65
+ address collectNFT;
66
+ {
67
+ Types.Publication storage _collectedPublication = StorageLib.getPublication(
68
+ collectParams.publicationCollectedProfileId,
69
+ collectParams.publicationCollectedId
70
+ );
71
+ // This is a legacy collect operation, so we get the collect module from the deprecated storage field.
72
+ collectModule = _collectedPublication.__DEPRECATED__collectModule;
73
+ if (collectModule == address(0)) {
74
+ // It doesn't have collect module, thus it cannot be collected (a mirror or non-existent).
75
+ revert Errors.CollectNotAllowed();
76
+ }
77
+
78
+ if (collectParams.referrerProfileId != 0 || collectParams.referrerPubId != 0) {
79
+ ValidationLib.validateLegacyCollectReferrer(
80
+ collectParams.referrerProfileId,
81
+ collectParams.referrerPubId,
82
+ collectParams.publicationCollectedProfileId,
83
+ collectParams.publicationCollectedId
84
+ );
85
+ }
86
+
87
+ collectNFT = _getOrDeployCollectNFT(
88
+ _collectedPublication,
89
+ collectParams.publicationCollectedProfileId,
90
+ collectParams.publicationCollectedId,
91
+ collectNFTImpl
92
+ );
93
+ tokenId = ICollectNFT(collectNFT).mint(collectorProfileOwner);
94
+ }
95
+
96
+ _prefillLegacyCollectFollowValidationHelper({
97
+ profileId: collectParams.publicationCollectedProfileId,
98
+ collectorProfileId: collectParams.collectorProfileId,
99
+ collector: transactionExecutor
100
+ });
101
+
102
+ ILegacyCollectModule(collectModule).processCollect({
103
+ // Legacy collect modules expect referrer profile ID to match the collected pub's author if no referrer set.
104
+ referrerProfileId: collectParams.referrerProfileId == 0
105
+ ? collectParams.publicationCollectedProfileId
106
+ : collectParams.referrerProfileId,
107
+ // Collect NFT is minted to the `collectorProfileOwner`. Some follow-based constraints are expected to be
108
+ // broken in legacy collect modules if the `transactionExecutor` does not match the `collectorProfileOwner`.
109
+ collector: transactionExecutor,
110
+ profileId: collectParams.publicationCollectedProfileId,
111
+ pubId: collectParams.publicationCollectedId,
112
+ data: collectParams.collectModuleData
113
+ });
114
+
115
+ emit CollectedLegacy({
116
+ publicationCollectedProfileId: collectParams.publicationCollectedProfileId,
117
+ publicationCollectedId: collectParams.publicationCollectedId,
118
+ collectorProfileId: collectParams.collectorProfileId,
119
+ transactionExecutor: transactionExecutor,
120
+ referrerProfileId: collectParams.referrerProfileId,
121
+ referrerPubId: collectParams.referrerPubId,
122
+ collectModule: collectModule,
123
+ collectModuleData: collectParams.collectModuleData,
124
+ tokenId: tokenId,
125
+ timestamp: block.timestamp
126
+ });
127
+
128
+ return tokenId;
129
+ }
130
+
131
+ function _getOrDeployCollectNFT(
132
+ Types.Publication storage _collectedPublication,
133
+ uint256 publicationCollectedProfileId,
134
+ uint256 publicationCollectedId,
135
+ address collectNFTImpl
136
+ ) private returns (address) {
137
+ address collectNFT = _collectedPublication.__DEPRECATED__collectNFT;
138
+ if (collectNFT == address(0)) {
139
+ collectNFT = _deployCollectNFT(publicationCollectedProfileId, publicationCollectedId, collectNFTImpl);
140
+ _collectedPublication.__DEPRECATED__collectNFT = collectNFT;
141
+ }
142
+ return collectNFT;
143
+ }
144
+
145
+ /**
146
+ * @notice Deploys the given profile's Collect NFT contract.
147
+ *
148
+ * @param profileId The token ID of the profile which Collect NFT should be deployed.
149
+ * @param pubId The publication ID of the publication being collected, which Collect NFT should be deployed.
150
+ * @param collectNFTImpl The address of the Collect NFT implementation that should be used for the deployment.
151
+ *
152
+ * @return address The address of the deployed Collect NFT contract.
153
+ */
154
+ function _deployCollectNFT(uint256 profileId, uint256 pubId, address collectNFTImpl) private returns (address) {
155
+ address collectNFT = Clones.clone(collectNFTImpl);
156
+
157
+ ICollectNFT(collectNFT).initialize(profileId, pubId);
158
+ emit Events.LegacyCollectNFTDeployed(profileId, pubId, collectNFT, block.timestamp);
159
+
160
+ return collectNFT;
161
+ }
162
+
163
+ function _prefillLegacyCollectFollowValidationHelper(
164
+ uint256 profileId,
165
+ uint256 collectorProfileId,
166
+ address collector
167
+ ) private {
168
+ // It's important to set it as zero if not following, as the storage could be dirty from a previous transaction.
169
+ StorageLib.legacyCollectFollowValidationHelper()[collector] = FollowLib.isFollowing({
170
+ followerProfileId: collectorProfileId,
171
+ followedProfileId: profileId
172
+ })
173
+ ? profileId
174
+ : 0;
175
+ }
176
+ }