forked from nounsDAO/nouns-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NounsDescriptor.sol
351 lines (300 loc) · 11.8 KB
/
NounsDescriptor.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
// SPDX-License-Identifier: GPL-3.0
/// @title The Nouns NFT descriptor
/*********************************
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░█████████░░█████████░░░ *
* ░░░░░░██░░░████░░██░░░████░░░ *
* ░░██████░░░████████░░░████░░░ *
* ░░██░░██░░░████░░██░░░████░░░ *
* ░░██░░██░░░████░░██░░░████░░░ *
* ░░░░░░█████████░░█████████░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
*********************************/
pragma solidity ^0.8.6;
import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol';
import { Strings } from '@openzeppelin/contracts/utils/Strings.sol';
import { INounsDescriptor } from './interfaces/INounsDescriptor.sol';
import { INounsSeeder } from './interfaces/INounsSeeder.sol';
import { NFTDescriptor } from './libs/NFTDescriptor.sol';
import { MultiPartRLEToSVG } from './libs/MultiPartRLEToSVG.sol';
contract NounsDescriptor is INounsDescriptor, Ownable {
using Strings for uint256;
// prettier-ignore
// https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt
bytes32 constant COPYRIGHT_CC0_1_0_UNIVERSAL_LICENSE = 0xa2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499;
// Whether or not new Noun parts can be added
bool public override arePartsLocked;
// Whether or not `tokenURI` should be returned as a data URI (Default: true)
bool public override isDataURIEnabled = true;
// Base URI
string public override baseURI;
// Noun Color Palettes (Index => Hex Colors)
mapping(uint8 => string[]) public override palettes;
// Noun Backgrounds (Hex Colors)
string[] public override backgrounds;
// Noun Bodies (Custom RLE)
bytes[] public override bodies;
// Noun Accessories (Custom RLE)
bytes[] public override accessories;
// Noun Heads (Custom RLE)
bytes[] public override heads;
// Noun Glasses (Custom RLE)
bytes[] public override glasses;
/**
* @notice Require that the parts have not been locked.
*/
modifier whenPartsNotLocked() {
require(!arePartsLocked, 'Parts are locked');
_;
}
/**
* @notice Get the number of available Noun `backgrounds`.
*/
function backgroundCount() external view override returns (uint256) {
return backgrounds.length;
}
/**
* @notice Get the number of available Noun `bodies`.
*/
function bodyCount() external view override returns (uint256) {
return bodies.length;
}
/**
* @notice Get the number of available Noun `accessories`.
*/
function accessoryCount() external view override returns (uint256) {
return accessories.length;
}
/**
* @notice Get the number of available Noun `heads`.
*/
function headCount() external view override returns (uint256) {
return heads.length;
}
/**
* @notice Get the number of available Noun `glasses`.
*/
function glassesCount() external view override returns (uint256) {
return glasses.length;
}
/**
* @notice Add colors to a color palette.
* @dev This function can only be called by the owner.
*/
function addManyColorsToPalette(uint8 paletteIndex, string[] calldata newColors) external override onlyOwner {
require(palettes[paletteIndex].length + newColors.length <= 256, 'Palettes can only hold 256 colors');
for (uint256 i = 0; i < newColors.length; i++) {
_addColorToPalette(paletteIndex, newColors[i]);
}
}
/**
* @notice Batch add Noun backgrounds.
* @dev This function can only be called by the owner when not locked.
*/
function addManyBackgrounds(string[] calldata _backgrounds) external override onlyOwner whenPartsNotLocked {
for (uint256 i = 0; i < _backgrounds.length; i++) {
_addBackground(_backgrounds[i]);
}
}
/**
* @notice Batch add Noun bodies.
* @dev This function can only be called by the owner when not locked.
*/
function addManyBodies(bytes[] calldata _bodies) external override onlyOwner whenPartsNotLocked {
for (uint256 i = 0; i < _bodies.length; i++) {
_addBody(_bodies[i]);
}
}
/**
* @notice Batch add Noun accessories.
* @dev This function can only be called by the owner when not locked.
*/
function addManyAccessories(bytes[] calldata _accessories) external override onlyOwner whenPartsNotLocked {
for (uint256 i = 0; i < _accessories.length; i++) {
_addAccessory(_accessories[i]);
}
}
/**
* @notice Batch add Noun heads.
* @dev This function can only be called by the owner when not locked.
*/
function addManyHeads(bytes[] calldata _heads) external override onlyOwner whenPartsNotLocked {
for (uint256 i = 0; i < _heads.length; i++) {
_addHead(_heads[i]);
}
}
/**
* @notice Batch add Noun glasses.
* @dev This function can only be called by the owner when not locked.
*/
function addManyGlasses(bytes[] calldata _glasses) external override onlyOwner whenPartsNotLocked {
for (uint256 i = 0; i < _glasses.length; i++) {
_addGlasses(_glasses[i]);
}
}
/**
* @notice Add a single color to a color palette.
* @dev This function can only be called by the owner.
*/
function addColorToPalette(uint8 _paletteIndex, string calldata _color) external override onlyOwner {
require(palettes[_paletteIndex].length <= 255, 'Palettes can only hold 256 colors');
_addColorToPalette(_paletteIndex, _color);
}
/**
* @notice Add a Noun background.
* @dev This function can only be called by the owner when not locked.
*/
function addBackground(string calldata _background) external override onlyOwner whenPartsNotLocked {
_addBackground(_background);
}
/**
* @notice Add a Noun body.
* @dev This function can only be called by the owner when not locked.
*/
function addBody(bytes calldata _body) external override onlyOwner whenPartsNotLocked {
_addBody(_body);
}
/**
* @notice Add a Noun accessory.
* @dev This function can only be called by the owner when not locked.
*/
function addAccessory(bytes calldata _accessory) external override onlyOwner whenPartsNotLocked {
_addAccessory(_accessory);
}
/**
* @notice Add a Noun head.
* @dev This function can only be called by the owner when not locked.
*/
function addHead(bytes calldata _head) external override onlyOwner whenPartsNotLocked {
_addHead(_head);
}
/**
* @notice Add Noun glasses.
* @dev This function can only be called by the owner when not locked.
*/
function addGlasses(bytes calldata _glasses) external override onlyOwner whenPartsNotLocked {
_addGlasses(_glasses);
}
/**
* @notice Lock all Noun parts.
* @dev This cannot be reversed and can only be called by the owner when not locked.
*/
function lockParts() external override onlyOwner whenPartsNotLocked {
arePartsLocked = true;
emit PartsLocked();
}
/**
* @notice Toggle a boolean value which determines if `tokenURI` returns a data URI
* or an HTTP URL.
* @dev This can only be called by the owner.
*/
function toggleDataURIEnabled() external override onlyOwner {
bool enabled = !isDataURIEnabled;
isDataURIEnabled = enabled;
emit DataURIToggled(enabled);
}
/**
* @notice Set the base URI for all token IDs. It is automatically
* added as a prefix to the value returned in {tokenURI}, or to the
* token ID if {tokenURI} is empty.
* @dev This can only be called by the owner.
*/
function setBaseURI(string calldata _baseURI) external override onlyOwner {
baseURI = _baseURI;
emit BaseURIUpdated(_baseURI);
}
/**
* @notice Given a token ID and seed, construct a token URI for an official Nouns DAO noun.
* @dev The returned value may be a base64 encoded data URI or an API URL.
*/
function tokenURI(uint256 tokenId, INounsSeeder.Seed memory seed) external view override returns (string memory) {
if (isDataURIEnabled) {
return dataURI(tokenId, seed);
}
return string(abi.encodePacked(baseURI, tokenId.toString()));
}
/**
* @notice Given a token ID and seed, construct a base64 encoded data URI for an official Nouns DAO noun.
*/
function dataURI(uint256 tokenId, INounsSeeder.Seed memory seed) public view override returns (string memory) {
string memory nounId = tokenId.toString();
string memory name = string(abi.encodePacked('Noun ', nounId));
string memory description = string(abi.encodePacked('Noun ', nounId, ' is a member of the Nouns DAO'));
return genericDataURI(name, description, seed);
}
/**
* @notice Given a name, description, and seed, construct a base64 encoded data URI.
*/
function genericDataURI(
string memory name,
string memory description,
INounsSeeder.Seed memory seed
) public view override returns (string memory) {
NFTDescriptor.TokenURIParams memory params = NFTDescriptor.TokenURIParams({
name: name,
description: description,
parts: _getPartsForSeed(seed),
background: backgrounds[seed.background]
});
return NFTDescriptor.constructTokenURI(params, palettes);
}
/**
* @notice Given a seed, construct a base64 encoded SVG image.
*/
function generateSVGImage(INounsSeeder.Seed memory seed) external view override returns (string memory) {
MultiPartRLEToSVG.SVGParams memory params = MultiPartRLEToSVG.SVGParams({
parts: _getPartsForSeed(seed),
background: backgrounds[seed.background]
});
return NFTDescriptor.generateSVGImage(params, palettes);
}
/**
* @notice Add a single color to a color palette.
*/
function _addColorToPalette(uint8 _paletteIndex, string calldata _color) internal {
palettes[_paletteIndex].push(_color);
}
/**
* @notice Add a Noun background.
*/
function _addBackground(string calldata _background) internal {
backgrounds.push(_background);
}
/**
* @notice Add a Noun body.
*/
function _addBody(bytes calldata _body) internal {
bodies.push(_body);
}
/**
* @notice Add a Noun accessory.
*/
function _addAccessory(bytes calldata _accessory) internal {
accessories.push(_accessory);
}
/**
* @notice Add a Noun head.
*/
function _addHead(bytes calldata _head) internal {
heads.push(_head);
}
/**
* @notice Add Noun glasses.
*/
function _addGlasses(bytes calldata _glasses) internal {
glasses.push(_glasses);
}
/**
* @notice Get all Noun parts for the passed `seed`.
*/
function _getPartsForSeed(INounsSeeder.Seed memory seed) internal view returns (bytes[] memory) {
bytes[] memory _parts = new bytes[](4);
_parts[0] = bodies[seed.body];
_parts[1] = accessories[seed.accessory];
_parts[2] = heads[seed.head];
_parts[3] = glasses[seed.glasses];
return _parts;
}
}