-
Notifications
You must be signed in to change notification settings - Fork 21
/
DegreesOfSeparationReferenceModule.sol
402 lines (382 loc) · 16.9 KB
/
DegreesOfSeparationReferenceModule.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import {DataTypes} from '@aave/lens-protocol/contracts/libraries/DataTypes.sol';
import {EIP712} from '@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol';
import {Errors} from '@aave/lens-protocol/contracts/libraries/Errors.sol';
import {Events} from '@aave/lens-protocol/contracts/libraries/Events.sol';
import {FollowValidationModuleBase} from '@aave/lens-protocol/contracts/core/modules/FollowValidationModuleBase.sol';
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import {IFollowModule} from '@aave/lens-protocol/contracts/interfaces/IFollowModule.sol';
import {ILensHub} from '@aave/lens-protocol/contracts/interfaces/ILensHub.sol';
import {IReferenceModule} from '@aave/lens-protocol/contracts/interfaces/IReferenceModule.sol';
import {ModuleBase} from '@aave/lens-protocol/contracts/core/modules/ModuleBase.sol';
/**
* @notice Struct representing the module configuration for certain publication.
*
* @param setUp Indicates if the publication was set up to use this module, to then allow updating params.
* @param commentsRestricted Indicates if the comment operation is restricted or open to everyone.
* @param mirrorsRestricted Indicates if the mirror operation is restricted or open to everyone.
* @param degreesOfSeparation The max degrees of separation allowed for restricted operations.
*/
struct ModuleConfig {
bool setUp;
bool commentsRestricted;
bool mirrorsRestricted;
uint8 degreesOfSeparation;
}
/**
* @title DegreesOfSeparationReferenceModule
* @author Lens Protocol
*
* @notice This reference module allows to set a degree of separation `n`, and then allows to comment/mirror only to
* profiles that are at most at `n` degrees of separation from the author of the root publication.
*/
contract DegreesOfSeparationReferenceModule is
EIP712,
FollowValidationModuleBase,
IReferenceModule
{
event ModuleParametersUpdated(
uint256 indexed profileId,
uint256 indexed pubId,
bool commentsRestricted,
bool mirrorsRestricted,
uint8 degreesOfSeparation
);
error InvalidDegreesOfSeparation();
error OperationDisabled();
error ProfilePathExceedsDegreesOfSeparation();
error PublicationNotSetUp();
/**
* @dev Because of the "Six degrees of separation" theory, in the long term, setting up 5, 6 or more degrees of
* separation will be almost equivalent to turning off the restriction.
* If we also take into account the gas cost of performing the validations on-chain, makes sense to only support up
* to 4 degrees of separation.
*/
uint8 constant MAX_DEGREES_OF_SEPARATION = 4;
mapping(address => uint256) public nonces;
mapping(uint256 => mapping(uint256 => ModuleConfig)) internal _moduleConfigByPubByProfile;
constructor(address hub) EIP712('DegreesOfSeparationReferenceModule', '1') ModuleBase(hub) {}
/**
* @notice Initializes data for a given publication being published. This can only be called by the hub.
*
* @param profileId The token ID of the profile publishing the publication.
* @param pubId The associated publication's LensHub publication ID.
* @param data Arbitrary data passed from the user to be decoded.
*
* @return bytes An abi encoded byte array encapsulating the execution's state changes. This will be emitted by the
* hub alongside the collect module's address and should be consumed by front ends.
*/
function initializeReferenceModule(
uint256 profileId,
uint256 pubId,
bytes calldata data
) external override onlyHub returns (bytes memory) {
(bool commentsRestricted, bool mirrorsRestricted, uint8 degreesOfSeparation) = abi.decode(
data,
(bool, bool, uint8)
);
if (degreesOfSeparation > MAX_DEGREES_OF_SEPARATION) {
revert InvalidDegreesOfSeparation();
}
_moduleConfigByPubByProfile[profileId][pubId] = ModuleConfig(
true,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation
);
return data;
}
/**
* @notice Processes a comment action referencing a given publication. This can only be called by the hub.
*
* @dev It will apply the degrees of separation restriction if the publication has `commentsRestricted` enabled.
*
* @param profileId The token ID of the profile associated with the publication being published.
* @param profileIdPointed The profile ID of the profile associated the publication being referenced.
* @param pubIdPointed The publication ID of the publication being referenced.
* @param data Encoded data containing the array of profile IDs representing the follower path between the owner of
* the author of the root publication and the profile authoring the comment.
*/
function processComment(
uint256 profileId,
uint256 profileIdPointed,
uint256 pubIdPointed,
bytes calldata data
) external view override onlyHub {
if (_moduleConfigByPubByProfile[profileIdPointed][pubIdPointed].commentsRestricted) {
_validateDegreesOfSeparationRestriction(
profileId,
profileIdPointed,
_moduleConfigByPubByProfile[profileIdPointed][pubIdPointed].degreesOfSeparation,
abi.decode(data, (uint256[]))
);
}
}
/**
* @notice Processes a mirror action referencing a given publication. This can only be called by the hub.
*
* @dev It will apply the degrees of separation restriction if the publication has `mirrorsRestricted` enabled.
*
* @param profileId The token ID of the profile associated with the publication being published.
* @param profileIdPointed The profile ID of the profile associated the publication being referenced.
* @param pubIdPointed The publication ID of the publication being referenced.
* @param data Encoded data containing the array of profile IDs representing the follower path between the owner of
* the author of the root publication and the profile authoring the mirror.
*/
function processMirror(
uint256 profileId,
uint256 profileIdPointed,
uint256 pubIdPointed,
bytes calldata data
) external view override onlyHub {
if (_moduleConfigByPubByProfile[profileIdPointed][pubIdPointed].mirrorsRestricted) {
_validateDegreesOfSeparationRestriction(
profileId,
profileIdPointed,
_moduleConfigByPubByProfile[profileIdPointed][pubIdPointed].degreesOfSeparation,
abi.decode(data, (uint256[]))
);
}
}
/**
* @notice Updates the module parameters for the given publication.
*
* @param profileId The token ID of the profile publishing the publication.
* @param pubId The associated publication's LensHub publication ID.
* @param commentsRestricted Indicates if the comment operation is restricted or open to everyone.
* @param mirrorsRestricted Indicates if the mirror operation is restricted or open to everyone.
* @param degreesOfSeparation The max degrees of separation allowed for restricted operations.
*/
function updateModuleParameters(
uint256 profileId,
uint256 pubId,
bool commentsRestricted,
bool mirrorsRestricted,
uint8 degreesOfSeparation
) external {
_updateModuleParameters(
profileId,
pubId,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation,
msg.sender
);
}
/**
* @notice Updates the module parameters for the given publication through EIP-712 signatures.
*
* @param profileId The token ID of the profile publishing the publication.
* @param pubId The associated publication's LensHub publication ID.
* @param commentsRestricted Indicates if the comment operation is restricted or open to everyone.
* @param mirrorsRestricted Indicates if the mirror operation is restricted or open to everyone.
* @param degreesOfSeparation The max degrees of separation allowed for restricted operations.
* @param operator The address that is executing this parameter update. Should match the recovered signer.
* @param sig The EIP-712 signature for this operation.
*/
function updateModuleParametersWithSig(
uint256 profileId,
uint256 pubId,
bool commentsRestricted,
bool mirrorsRestricted,
uint8 degreesOfSeparation,
address operator,
DataTypes.EIP712Signature calldata sig
) external {
_validateUpdateModuleParametersSignature(
profileId,
pubId,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation,
operator,
sig
);
_updateModuleParameters(
profileId,
pubId,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation,
operator
);
}
/**
* @notice Gets the module configuration for the given publication.
*
* @param profileId The token ID of the profile publishing the publication.
* @param pubId The associated publication's LensHub publication ID.
*
* @return ModuleConfig The module configuration set for the given publication.
*/
function getModuleConfig(uint256 profileId, uint256 pubId)
external
view
returns (ModuleConfig memory)
{
return _moduleConfigByPubByProfile[profileId][pubId];
}
/**
* @dev The data has encoded an array of integers, each integer is a profile ID, the whole array represents a path
* of `n` profiles.
*
* Let's define `X --> Y` as `The owner of X is following Y`. Then, being `path[i]` the i-th profile in the path,
* the following condition must be met for a given path of `n` profiles:
*
* profileIdPointed --> path[0] --> path[1] --> path[2] --> ... --> path[n-2] --> path[n-1] --> profileId
*
* @param profileId The token ID of the profile associated with the publication being published.
* @param profileIdPointed The profile ID of the profile associated the publication being referenced.
* @param degreesOfSeparation The degrees of separations configured for the given publication.
* @param profilePath The array of profile IDs representing the follower path between the owner of the author of the
* root publication and the profile authoring the comment.
*/
function _validateDegreesOfSeparationRestriction(
uint256 profileId,
uint256 profileIdPointed,
uint8 degreesOfSeparation,
uint256[] memory profilePath
) internal view {
if (degreesOfSeparation == 0) {
revert OperationDisabled();
}
if (profilePath.length > degreesOfSeparation - 1) {
revert ProfilePathExceedsDegreesOfSeparation();
}
address follower = IERC721(HUB).ownerOf(profileIdPointed);
if (profilePath.length > 0) {
// Checks the owner of the profile authoring the root publication follows the first profile in the path.
// In the previous notation: profileIdPointed --> path[0]
_checkFollowValidity(profilePath[0], follower);
// Checks each profile owner in the path is following the profile coming next, according the order.
// In the previous notaiton: path[0] --> path[1] --> path[2] --> ... --> path[n-2] --> path[n-1]
uint256 i;
while (i < profilePath.length - 1) {
follower = IERC721(HUB).ownerOf(profilePath[i]);
unchecked {
++i;
}
_checkFollowValidity(profilePath[i], follower);
}
// Checks the last profile in the path follows the profile commenting/mirroring.
// In the previous notation: path[n-1] --> profileId
follower = IERC721(HUB).ownerOf(profilePath[i]);
_checkFollowValidity(profileId, follower);
} else {
// Checks the owner of the profile authoring the root publication follows the profile commenting/mirroring.
// In the previous notation: profileIdPointed --> profileId
_checkFollowValidity(profileId, follower);
}
}
/**
* @notice Internal function to abstract the logic regarding the parameter updating.
*
* @param profileId The token ID of the profile publishing the publication.
* @param pubId The associated publication's LensHub publication ID.
* @param commentsRestricted Indicates if the comment operation is restricted or open to everyone.
* @param mirrorsRestricted Indicates if the mirror operation is restricted or open to everyone.
* @param degreesOfSeparation The max degrees of separation allowed for restricted operations.
* @param operator The address that is executing this parameter update. Should match the recovered signer.
*/
function _updateModuleParameters(
uint256 profileId,
uint256 pubId,
bool commentsRestricted,
bool mirrorsRestricted,
uint8 degreesOfSeparation,
address operator
) internal {
if (IERC721(HUB).ownerOf(profileId) != operator) {
revert Errors.NotProfileOwner();
}
if (!_moduleConfigByPubByProfile[profileId][pubId].setUp) {
revert PublicationNotSetUp();
}
if (degreesOfSeparation > MAX_DEGREES_OF_SEPARATION) {
revert InvalidDegreesOfSeparation();
}
_moduleConfigByPubByProfile[profileId][pubId] = ModuleConfig(
true,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation
);
emit ModuleParametersUpdated(
profileId,
pubId,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation
);
}
/**
* @notice Checks if the signature for the `UpdateModuleParametersWithSig` function is valid according EIP-712.
*
* @param profileId The token ID of the profile associated with the publication.
* @param pubId The publication ID associated with the publication.
* @param commentsRestricted Indicates if the comment operation is restricted or open to everyone.
* @param mirrorsRestricted Indicates if the mirror operation is restricted or open to everyone.
* @param degreesOfSeparation The max degrees of separation allowed for restricted operations.
* @param operator The address that is executing this parameter update. Should match the recovered signer.
* @param sig The EIP-712 signature for this operation.
*/
function _validateUpdateModuleParametersSignature(
uint256 profileId,
uint256 pubId,
bool commentsRestricted,
bool mirrorsRestricted,
uint8 degreesOfSeparation,
address operator,
DataTypes.EIP712Signature calldata sig
) internal {
unchecked {
_validateRecoveredAddress(
_calculateDigest(
abi.encode(
keccak256(
'UpdateModuleParametersWithSig(uint256 profileId,uint256 pubId,bool commentsRestricted,bool mirrorsRestricted,uint8 degreesOfSeparation,uint256 nonce,uint256 deadline)'
),
profileId,
pubId,
commentsRestricted,
mirrorsRestricted,
degreesOfSeparation,
nonces[operator]++,
sig.deadline
)
),
operator,
sig
);
}
}
/**
* @notice Checks the recovered address is the expected signer for the given signature.
*
* @param digest The expected signed data.
* @param expectedAddress The address of the expected signer.
* @param sig The signature.
*/
function _validateRecoveredAddress(
bytes32 digest,
address expectedAddress,
DataTypes.EIP712Signature calldata sig
) internal view {
if (sig.deadline < block.timestamp) {
revert Errors.SignatureExpired();
}
address recoveredAddress = ecrecover(digest, sig.v, sig.r, sig.s);
if (recoveredAddress == address(0) || recoveredAddress != expectedAddress) {
revert Errors.SignatureInvalid();
}
}
/**
* @notice Calculates the digest for the given bytes according EIP-712 standard.
*
* @param message The message, as bytes, to calculate the digest from.
*/
function _calculateDigest(bytes memory message) internal view returns (bytes32) {
return keccak256(abi.encodePacked('\x19\x01', _domainSeparatorV4(), keccak256(message)));
}
}