forked from nounsDAO/nouns-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNounsToken.sol
322 lines (265 loc) · 9.49 KB
/
NounsToken.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
// SPDX-License-Identifier: GPL-3.0
/// @title The Nouns ERC-721 token
// TODO: Add NounsDAO tribute and upgrades made
pragma solidity ^0.8.6;
import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol';
import { ERC721Checkpointable } from './base/ERC721Checkpointable.sol';
import { ERC721URIStorage } from './base/ERC721URIStorage.sol';
import { ERC721Enumerable } from './base/ERC721Enumerable.sol';
import { INounsDescriptor } from './interfaces/INounsDescriptor.sol';
import { INounsSeeder } from './interfaces/INounsSeeder.sol';
import { INounsToken } from './interfaces/INounsToken.sol';
import { ERC721 } from './base/ERC721.sol';
import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import { IProxyRegistry } from './external/opensea/IProxyRegistry.sol';
import '@openzeppelin/contracts/utils/introspection/ERC165.sol';
contract NounsToken is INounsToken, Ownable, ERC721URIStorage, ERC721Checkpointable {
// The nounders DAO address (creators org)
address public noundersDAO;
// An address who has permissions to mint Noun
address public minter;
// An address who has permissions to update token URIs
address public uriUpdater;
// The Nouns token URI descriptor
INounsDescriptor public descriptor;
// The Nouns token seeder
INounsSeeder public seeder;
// Whether the minter can be updated
bool public isMinterLocked;
// Whether the uri updater can be updated
bool public isUriUpdaterLocked;
// Whether the descriptor can be updated
bool public isDescriptorLocked;
// Whether the seeder can be updated
bool public isSeederLocked;
// The nouns seeds
mapping(uint256 => INounsSeeder.Seed) public seeds;
// The internal nouns ID tracker
uint256 private _currentNounsId;
// IPFS content hash of contract-level metadata
string private _contractURIHash = 'QmZi1n79FqWt2tTLwCqiy6nLM6xLGRsEPQ5JmReJQKNNzX';
// OpenSea's Proxy Registry
IProxyRegistry public immutable proxyRegistry;
/**
* @notice Require that the minter has not been locked.
*/
modifier whenMinterNotLocked() {
require(!isMinterLocked, 'Minter is locked');
_;
}
/**
* @notice Require that the descriptor has not been locked.
*/
modifier whenDescriptorNotLocked() {
require(!isDescriptorLocked, 'Descriptor is locked');
_;
}
/**
* @notice Require that the seeder has not been locked.
*/
modifier whenSeederNotLocked() {
require(!isSeederLocked, 'Seeder is locked');
_;
}
/**
* @notice Require that the seeder has not been locked.
*/
modifier whenUriUpdaterNotLocked() {
require(!isUriUpdaterLocked, 'URI Updater is locked');
_;
}
/**
* @notice Require that the sender is the nounders DAO.
*/
modifier onlyNoundersDAO() {
require(msg.sender == noundersDAO, 'Sender is not the nounders DAO');
_;
}
/**
* @notice Require that the sender is the minter.
*/
modifier onlyMinter() {
require(msg.sender == minter, 'Sender is not the minter');
_;
}
/**
* @notice Require that the sender is the uri updater.
*/
modifier onlyUriUpdater() {
require(msg.sender == uriUpdater, 'Sender is not the URI updater');
_;
}
constructor(
address _noundersDAO,
address _minter,
address _uriUpdater,
INounsDescriptor _descriptor,
INounsSeeder _seeder,
IProxyRegistry _proxyRegistry
) ERC721('Nouns', 'NOUN') {
noundersDAO = _noundersDAO;
minter = _minter;
uriUpdater = _uriUpdater;
descriptor = _descriptor;
seeder = _seeder;
proxyRegistry = _proxyRegistry;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(IERC165, ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override(ERC721, ERC721Checkpointable) {
super._beforeTokenTransfer(from, to, tokenId);
}
function _burn(uint256 tokenId) internal virtual override(ERC721, ERC721URIStorage) {
ERC721URIStorage._burn(tokenId);
}
/**
* @notice The IPFS URI of contract-level metadata.
*/
function contractURI() public view returns (string memory) {
return string(abi.encodePacked('ipfs://', _contractURIHash));
}
/**
* @notice Set the _contractURIHash.
* @dev Only callable by the owner.
*/
function setContractURIHash(string memory newContractURIHash) external onlyOwner {
_contractURIHash = newContractURIHash;
}
/**
* @notice Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
*/
function isApprovedForAll(address owner, address operator) public view override(IERC721, ERC721) returns (bool) {
// Whitelist OpenSea proxy contract for easy trading.
if (proxyRegistry.proxies(owner) == operator) {
return true;
}
return super.isApprovedForAll(owner, operator);
}
/**
* @notice Mint a Noun to the minter, along with a possible nounders reward
* Nouns. Nounders reward Nouns are minted every 10 Nouns, starting at 0,
* until 183 nounsder Nouns have been minted (5 years w/ 24 hour auctions).
* @dev Call _mintTo with the to address(es).
*/
function mint() public override onlyMinter returns (uint256) {
if (_currentNounsId <= 1820 && _currentNounsId % 10 == 0) {
_mintTo(noundersDAO, _currentNounsId++);
}
return _mintTo(minter, _currentNounsId++);
}
/**
* @notice Burn a Noun.
*/
function burn(uint256 nounId) public override onlyMinter {
_burn(nounId);
emit NounBurned(nounId);
}
/**
* @notice A distinct Uniform Resource Identifier (URI) for a given asset.
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
require(_exists(tokenId), 'NounsToken: URI query for nonexistent token');
return super.tokenURI(tokenId);
}
/**
* @notice Set the nounders DAO.
* @dev Only callable by the nounders DAO when not locked.
*/
function setNoundersDAO(address _noundersDAO) external override onlyNoundersDAO {
noundersDAO = _noundersDAO;
emit NoundersDAOUpdated(_noundersDAO);
}
/**
* @notice Set the token minter.
* @dev Only callable by the owner when not locked.
*/
function setMinter(address _minter) external override onlyOwner whenMinterNotLocked {
minter = _minter;
emit MinterUpdated(_minter);
}
/**
* @notice Lock the minter.
* @dev This cannot be reversed and is only callable by the owner when not locked.
*/
function lockMinter() external override onlyOwner whenMinterNotLocked {
isMinterLocked = true;
emit MinterLocked();
}
/**
* @notice Set the token minter.
* @dev Only callable by the owner when not locked.
*/
function setUriUpdater(address _updater) external override onlyOwner whenUriUpdaterNotLocked {
uriUpdater = _updater;
emit UriUpdaterUpdated(_updater);
}
function lockUriUpdater() external override onlyOwner whenUriUpdaterNotLocked {
isUriUpdaterLocked = true;
emit UriUpdaterLocked();
}
/**
* @notice Set the token URI descriptor.
* @dev Only callable by the owner when not locked.
*/
function setDescriptor(INounsDescriptor _descriptor) external override onlyOwner whenDescriptorNotLocked {
descriptor = _descriptor;
emit DescriptorUpdated(_descriptor);
}
/**
* @notice Lock the descriptor.
* @dev This cannot be reversed and is only callable by the owner when not locked.
*/
function lockDescriptor() external override onlyOwner whenDescriptorNotLocked {
isDescriptorLocked = true;
emit DescriptorLocked();
}
/**
* @notice Set the token seeder.
* @dev Only callable by the owner when not locked.
*/
function setSeeder(INounsSeeder _seeder) external override onlyOwner whenSeederNotLocked {
seeder = _seeder;
emit SeederUpdated(_seeder);
}
/**
* @notice Lock the seeder.
* @dev This cannot be reversed and is only callable by the owner when not locked.
*/
function lockSeeder() external override onlyOwner whenSeederNotLocked {
isSeederLocked = true;
emit SeederLocked();
}
/**
* @notice Set the token URI of provided tokenId
* @dev See {ERC721URIStorage-_setTokenURI}
*/
function setTokenURI(uint256 tokenId, string memory uri) external onlyUriUpdater {
require(_exists(tokenId), 'NounsToken: URI set for nonexistent token');
super._setTokenURI(tokenId, uri);
emit TokenUriSet(tokenId, uri);
}
/**
* @notice Mint a Noun with `nounId` to the provided `to` address.
*/
function _mintTo(address to, uint256 nounId) internal returns (uint256) {
INounsSeeder.Seed memory seed = seeds[nounId] = seeder.generateSeed(nounId, descriptor);
_mint(owner(), to, nounId);
emit NounCreated(nounId, seed);
return nounId;
}
}