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.
- package/contracts/FollowNFT.sol +507 -0
- package/contracts/LensHub.sol +590 -0
- package/contracts/base/LensGovernable.sol +114 -0
- package/contracts/base/LensHubEventHooks.sol +42 -0
- package/contracts/base/LensHubStorage.sol +69 -0
- package/contracts/base/LensImplGetters.sol +32 -0
- package/contracts/base/LensProfiles.sol +162 -0
- package/contracts/base/LensVersion.sol +36 -0
- package/contracts/base/upgradeability/FollowNFTProxy.sol +21 -0
- package/contracts/interfaces/IFollowTokenURI.sol +11 -0
- package/contracts/interfaces/ILegacyCollectModule.sol +45 -0
- package/contracts/interfaces/ILegacyCollectNFT.sol +46 -0
- package/contracts/interfaces/ILegacyFollowModule.sol +82 -0
- package/contracts/interfaces/IProfileTokenURI.sol +7 -0
- package/contracts/interfaces/ITokenHandleRegistry.sol +90 -0
- package/contracts/libraries/ActionLib.sol +69 -0
- package/contracts/libraries/FollowLib.sol +175 -0
- package/contracts/libraries/GovernanceLib.sol +111 -0
- package/contracts/libraries/LegacyCollectLib.sol +176 -0
- package/contracts/libraries/ProfileLib.sol +284 -0
- package/contracts/libraries/PublicationLib.sol +567 -0
- package/contracts/libraries/ValidationLib.sol +228 -0
- package/dist/index.d.ts +5 -0
- package/dist/{src/index.js → index.js} +4 -4
- package/dist/verified-modules/follow-modules.d.ts +12 -0
- package/dist/verified-modules/follow-modules.js +15 -0
- package/dist/verified-modules/open-actions.d.ts +42 -0
- package/dist/verified-modules/open-actions.js +45 -0
- package/dist/verified-modules/reference-modules.d.ts +17 -0
- package/dist/verified-modules/reference-modules.js +20 -0
- package/package.json +1 -1
- package/dist/src/index.d.ts +0 -5
- /package/dist/{deployments/lens-contracts.d.ts → lens-contracts.d.ts} +0 -0
- /package/dist/{deployments/lens-contracts.js → lens-contracts.js} +0 -0
- /package/dist/{src/lenshub-abi.d.ts → lenshub-abi.d.ts} +0 -0
- /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
|
+
}
|