-
Notifications
You must be signed in to change notification settings - Fork 0
/
FollowNFT.sol
316 lines (276 loc) · 11.7 KB
/
FollowNFT.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
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.10;
import {IFollowNFT} from '../interfaces/IFollowNFT.sol';
import {IFollowModule} from '../interfaces/IFollowModule.sol';
import {ILensHub} from '../interfaces/ILensHub.sol';
import {Errors} from '../libraries/Errors.sol';
import {Events} from '../libraries/Events.sol';
import {DataTypes} from '../libraries/DataTypes.sol';
import {LensNFTBase} from './base/LensNFTBase.sol';
import {IERC721Metadata} from '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
/**
* @title FollowNFT
* @author Lens Protocol
*
* @notice This contract is the NFT that is minted upon following a given profile. It is cloned upon first follow for a
* given profile, and includes built-in governance power and delegation mechanisms.
*
* NOTE: This contract assumes total NFT supply for this follow NFT will never exceed 2^128 - 1
*/
contract FollowNFT is LensNFTBase, IFollowNFT {
struct Snapshot {
uint128 blockNumber;
uint128 value;
}
address public immutable HUB;
bytes32 internal constant DELEGATE_BY_SIG_TYPEHASH =
0xb8f190a57772800093f4e2b186099eb4f1df0ed7f5e2791e89a4a07678e0aeff;
// keccak256(
// 'DelegateBySig(address delegator,address delegatee,uint256 nonce,uint256 deadline)'
// );
mapping(address => mapping(uint256 => Snapshot)) internal _snapshots;
mapping(address => address) internal _delegates;
mapping(address => uint256) internal _snapshotCount;
mapping(uint256 => Snapshot) internal _delSupplySnapshots;
uint256 internal _delSupplySnapshotCount;
uint256 internal _profileId;
uint256 internal _tokenIdCounter;
bool private _initialized;
// We create the FollowNFT with the pre-computed HUB address before deploying the hub.
constructor(address hub) {
HUB = hub;
}
/// @inheritdoc IFollowNFT
function initialize(
uint256 profileId,
string calldata name,
string calldata symbol
) external override {
if (_initialized) revert Errors.Initialized();
_initialized = true;
_profileId = profileId;
super._initialize(name, symbol);
emit Events.FollowNFTInitialized(profileId, block.timestamp);
}
/// @inheritdoc IFollowNFT
function mint(address to) external override {
if (msg.sender != HUB) revert Errors.NotHub();
uint256 tokenId = ++_tokenIdCounter;
_mint(to, tokenId);
}
/// @inheritdoc IFollowNFT
function delegate(address delegatee) external override {
_delegate(msg.sender, delegatee);
}
/// @inheritdoc IFollowNFT
function delegateBySig(
address delegator,
address delegatee,
DataTypes.EIP712Signature calldata sig
) external override {
bytes32 digest;
unchecked {
digest = keccak256(
abi.encodePacked(
'\x19\x01',
_calculateDomainSeparator(),
keccak256(
abi.encode(
DELEGATE_BY_SIG_TYPEHASH,
delegator,
delegatee,
sigNonces[delegator]++,
sig.deadline
)
)
)
);
}
_validateRecoveredAddress(digest, delegator, sig);
_delegate(delegator, delegatee);
}
/// @inheritdoc IFollowNFT
function getPowerByBlockNumber(address user, uint256 blockNumber)
external
view
override
returns (uint256)
{
if (blockNumber > block.number) revert Errors.BlockNumberInvalid();
uint256 snapshotCount = _snapshotCount[user];
if (snapshotCount == 0) {
return 0; // Returning zero since this means the user never delegated and has no power
}
uint256 lower = 0;
uint256 upper = snapshotCount - 1;
// First check most recent balance
if (_snapshots[user][upper].blockNumber <= blockNumber) {
return _snapshots[user][upper].value;
}
// Next check implicit zero balance
if (_snapshots[user][lower].blockNumber > blockNumber) {
return 0;
}
while (upper > lower) {
uint256 center = upper - (upper - lower) / 2;
Snapshot memory snapshot = _snapshots[user][center];
if (snapshot.blockNumber == blockNumber) {
return snapshot.value;
} else if (snapshot.blockNumber < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return _snapshots[user][lower].value;
}
/// @inheritdoc IFollowNFT
function getDelegatedSupplyByBlockNumber(uint256 blockNumber)
external
view
override
returns (uint256)
{
if (blockNumber > block.number) revert Errors.BlockNumberInvalid();
uint256 snapshotCount = _delSupplySnapshotCount;
if (snapshotCount == 0) {
return 0; // Returning zero since this means a delegation has never occurred
}
uint256 lower = 0;
uint256 upper = snapshotCount - 1;
// First check most recent delegated supply
if (_delSupplySnapshots[upper].blockNumber <= blockNumber) {
return _delSupplySnapshots[upper].value;
}
// Next check implicit zero balance
if (_delSupplySnapshots[lower].blockNumber > blockNumber) {
return 0;
}
while (upper > lower) {
uint256 center = upper - (upper - lower) / 2;
Snapshot memory snapshot = _delSupplySnapshots[center];
if (snapshot.blockNumber == blockNumber) {
return snapshot.value;
} else if (snapshot.blockNumber < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return _delSupplySnapshots[lower].value;
}
/**
* @dev This returns the follow NFT URI fetched from the hub.
*/
function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!_exists(tokenId)) revert Errors.TokenDoesNotExist();
return ILensHub(HUB).getFollowNFTURI(_profileId);
}
/**
* @dev Upon transfers, we move the appropriate delegations, and emit the transfer event in the hub.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override {
address fromDelegatee = _delegates[from];
address toDelegatee = _delegates[to];
address followModule = ILensHub(HUB).getFollowModule(_profileId);
_moveDelegate(fromDelegatee, toDelegatee, 1);
super._beforeTokenTransfer(from, to, tokenId);
ILensHub(HUB).emitFollowNFTTransferEvent(_profileId, tokenId, from, to);
if (followModule != address(0)) {
IFollowModule(followModule).followModuleTransferHook(_profileId, from, to, tokenId);
}
}
function _delegate(address delegator, address delegatee) internal {
uint256 delegatorBalance = balanceOf(delegator);
address previousDelegate = _delegates[delegator];
_delegates[delegator] = delegatee;
_moveDelegate(previousDelegate, delegatee, delegatorBalance);
}
function _moveDelegate(
address from,
address to,
uint256 amount
) internal {
unchecked {
if (from != address(0)) {
uint256 fromSnapshotCount = _snapshotCount[from];
// Underflow is impossible since, if from != address(0), then a delegation must have occurred (at least 1 snapshot)
uint256 previous = _snapshots[from][fromSnapshotCount - 1].value;
uint128 newValue = uint128(previous - amount);
_writeSnapshot(from, newValue, fromSnapshotCount);
emit Events.FollowNFTDelegatedPowerChanged(from, newValue, block.timestamp);
}
if (to != address(0)) {
// if from == address(0) then this is an initial delegation (add amount to supply)
if (from == address(0)) {
// It is expected behavior that the `previousDelSupply` underflows upon the first delegation,
// returning the expected value of zero
uint256 delSupplySnapshotCount = _delSupplySnapshotCount;
uint128 previousDelSupply = _delSupplySnapshots[delSupplySnapshotCount - 1]
.value;
uint128 newDelSupply = uint128(previousDelSupply + amount);
_writeSupplySnapshot(newDelSupply, delSupplySnapshotCount);
}
// It is expected behavior that `previous` underflows upon the first delegation to an address,
// returning the expected value of zero
uint256 toSnapshotCount = _snapshotCount[to];
uint128 previous = _snapshots[to][toSnapshotCount - 1].value;
uint128 newValue = uint128(previous + amount);
_writeSnapshot(to, newValue, toSnapshotCount);
emit Events.FollowNFTDelegatedPowerChanged(to, newValue, block.timestamp);
} else {
// If from != address(0) then this is removing a delegation, otherwise we're dealing with a
// non-delegated burn of tokens and don't need to take any action
if (from != address(0)) {
// Upon removing delegation (from != address(0) && to == address(0)), supply calculations cannot
// underflow because if from != address(0), then a delegation must have previously occurred, so
// the snapshot count must be >= 1 and the previous delegated supply must be >= amount
uint256 delSupplySnapshotCount = _delSupplySnapshotCount;
uint128 previousDelSupply = _delSupplySnapshots[delSupplySnapshotCount - 1]
.value;
uint128 newDelSupply = uint128(previousDelSupply - amount);
_writeSupplySnapshot(newDelSupply, delSupplySnapshotCount);
}
}
}
}
function _writeSnapshot(
address owner,
uint128 newValue,
uint256 ownerSnapshotCount
) internal {
unchecked {
uint128 currentBlock = uint128(block.number);
mapping(uint256 => Snapshot) storage ownerSnapshots = _snapshots[owner];
// Doing multiple operations in the same block
if (
ownerSnapshotCount != 0 &&
ownerSnapshots[ownerSnapshotCount - 1].blockNumber == currentBlock
) {
ownerSnapshots[ownerSnapshotCount - 1].value = newValue;
} else {
ownerSnapshots[ownerSnapshotCount] = Snapshot(currentBlock, newValue);
_snapshotCount[owner] = ownerSnapshotCount + 1;
}
}
}
function _writeSupplySnapshot(uint128 newValue, uint256 supplySnapshotCount) internal {
unchecked {
uint128 currentBlock = uint128(block.number);
// Doing multiple operations in the same block
if (
supplySnapshotCount != 0 &&
_delSupplySnapshots[supplySnapshotCount - 1].blockNumber == currentBlock
) {
_delSupplySnapshots[supplySnapshotCount - 1].value = newValue;
} else {
_delSupplySnapshots[supplySnapshotCount] = Snapshot(currentBlock, newValue);
_delSupplySnapshotCount = supplySnapshotCount + 1;
}
}
}
}