-
Notifications
You must be signed in to change notification settings - Fork 115
/
main.leo
431 lines (373 loc) · 17.4 KB
/
main.leo
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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
// The 'leo_nft' program.
program leo_nft.aleo {
struct TokenId {
data1: u128, // Part 1 of the image data -- i.e., a way to link this nft to image or aws
data2: u128,
}
// base uri ascii bits. Include as many data pieces as necessary to encapsulate the uri. Padded with 0s at the end.
struct BaseURI {
data0: u128, // Part 1 of the base uri in bits. Bits should be the representation of the hexadecimal bytes for the ASCII text of the URL
data1: u128,
data2: u128,
data3: u128
}
struct SymbolBits {
data: u128 // The sybmol's ascii text represented in bits, and the u128 value of the bitstring.
}
record NFT {
private owner: address,
private data: TokenId,
private edition: scalar, // which edition of the nft this particular one is -- will be 0 for unique NFTs
}
record NFT_mint {
private owner: address,
private amount: u8,
}
record NFT_claim {
private owner: address,
private claim: field
}
// a way to prove ownership of an nft privately
record NFT_ownership {
private owner: address,
private nft_owner: address,
private data: TokenId,
private edition: scalar, // which edition of the nft this particular one is -- will be 0 for unique NFTs
}
mapping nft_owners: field => address;
// keys: setting index
// values: setting value
mapping general_settings: u8 => u128;
// keys: index of the nft to mint
// values: hash of the token id + edition
mapping nfts_to_mint: u128 => field;
// keys: fields that represent claims for having minted an nft
// values: fields that are the hash of the nft that was minted
mapping claims_to_nfts: field => field;
// keys: just two, 0u8 which corresponds to different on/off settings for the contract, and 1u8
// which corresponds to the mint block height.
// values: the bitstring that represents the settings that can be toggled
// in order of least significant bit index to most significant bit:
// 0: collection has been initialized
// 1: can minters mint
// 2: do minters have to have a mint record (i.e. is the private whitelist a requirement)
// 3: is the collection frozen
mapping toggle_settings: u8 => u32;
transition initialize_collection(
public total: u128,
public symbol: u128,
public base_uri: BaseURI,
) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(total, symbol, base_uri);
}
finalize initialize_collection(
public total: u128,
public symbol: u128,
public base_uri: BaseURI,
) {
// Ensure initialize cannot be called twice!
let toggle_settings_status: u32 = toggle_settings.get_or_use(0u8, 0u32);
let is_initialized: u32 = toggle_settings_status & 1u32;
assert_eq(is_initialized, 0u32);
general_settings.set(0u8, 0u128); // number of mintable NFTs (all editions)
general_settings.set(1u8, total); // Number of total NFTs (first-editions) that can be minted
general_settings.set(2u8, symbol); // Symbol for the NFT
general_settings.set(3u8, base_uri.data0); // Base URI for NFT
general_settings.set(4u8, base_uri.data1);
general_settings.set(5u8, base_uri.data2);
general_settings.set(6u8, base_uri.data3);
// initialized flag = 0b0000...0001 = 1u32
// minting flag = 0b0000...0010 = 2u32
// whitelist flag = 0b0000...0100 = 4u32
// frozen flag = 0b0000...1000 = 8u32
// defaults -- not frozen, whitelist required, not minting, initialized
// 0b0000...0101 = 5u32.
toggle_settings.set(0u8, 5u32);
toggle_settings.set(1u8, 0u32); // block height when mint is allowed
}
// Load the data into the mapping
// Enables someone to mint an NFT with provided image data
transition add_nft(public tokenId: TokenId, public edition: scalar) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
let tokenHash: field = BHP256::hash_to_field(tokenId);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
return then finalize(tokenEditionHash);
}
finalize add_nft(public tokenEditionHash: field) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
// Reduce the amount of total nfts that can be initialized
let remaining: u128 = general_settings.get(1u8);
general_settings.set(1u8, remaining - 1u128);
// Add this NFT to the mintable NFT collection
let mintable_num: u128 = general_settings.get(0u8);
nfts_to_mint.set(mintable_num, tokenEditionHash);
general_settings.set(0u8, mintable_num + 1u128);
}
transition add_minter(private minter: address, public amount: u8) -> NFT_mint {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return NFT_mint {
owner: minter,
amount,
} then finalize();
}
finalize add_minter()
{
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
}
// call this function to toggle minting, the whitelist requirement, or to permanently freeze the contract
transition update_toggle_settings(public toggle_settings_bitstring: u32) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(toggle_settings_bitstring);
}
finalize update_toggle_settings(public toggle_settings_bitstring: u32) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
// Ensure updated settings are not uninitializing the collection.
let still_initialized: u32 = toggle_settings_bitstring & 1u32;
assert_eq(still_initialized, 1u32);
toggle_settings.set(0u8, toggle_settings_bitstring);
}
transition set_mint_block(public mint_block: u32) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(mint_block);
}
finalize set_mint_block(public mint_block: u32) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
toggle_settings.set(1u8, mint_block);
}
transition update_symbol(public symbol: u128) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(symbol);
}
finalize update_symbol(public symbol: u128) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
general_settings.set(2u8, symbol);
}
transition update_base_uri(public base_uri: BaseURI) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(base_uri);
}
finalize update_base_uri(public base_uri: BaseURI) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
general_settings.set(3u8, base_uri.data0); // Base URI for NFT
general_settings.set(4u8, base_uri.data1);
general_settings.set(5u8, base_uri.data2);
general_settings.set(6u8, base_uri.data3);
}
transition open_mint(private hiding_nonce: scalar) -> NFT_claim {
// CAUTION: If the minter selects the same hiding nonce,
// that minter will not be able to mint all of their NFTs without claiming some first.
// Additionally, some privacy will be lost as the claim is a deterministic hash and is held in public state.
let address_hash: field = BHP256::hash_to_field(self.caller);
let claim: field = BHP256::commit_to_field(address_hash, hiding_nonce);
return NFT_claim {
owner: self.caller,
claim
} then finalize(claim);
}
// note, much of this code should be combined in a closure, but finalize closures are not yet stable on aleo.
finalize open_mint(public claim: field) {
// Ensure mint block height is less than current block height
let mint_block: u32 = toggle_settings.get(1u8);
let passed_height_check: bool = mint_block <= block.height;
assert_eq(passed_height_check, true);
// Ensure collection is not frozen, whitelist is not required, minting is allowed, and is initialized,
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// frozen & whitelist & minting & initialized flags = 0b0000...1111 = 15u32
// what the settings should be = 0b0000...0011 = 3u32
let collection_settings_met: u32 = toggle_settings_status & 15u32;
assert_eq(collection_settings_met, 3u32);
// Ensure this claim has not already been made
let existing_claim: field = claims_to_nfts.get_or_use(claim, 0field);
assert_eq(existing_claim, 0field);
// Randomly select an NFT to mint
let randomNum: u128 = ChaCha::rand_u128();
let old_mintable_sum: u128 = general_settings.get_or_use(0u8, 0u128);
let randomIndex: u128 = randomNum % old_mintable_sum;
let tokenEditionHash: field = nfts_to_mint.get(randomIndex);
claims_to_nfts.set(claim, tokenEditionHash);
// Decrease the number of mintable nfts
let new_mintable_num: u128 = old_mintable_sum - 1u128;
general_settings.set(0u8, new_mintable_num);
// Replace the minted nft from the mintable nfts with the last mintable nft.
// This is done to ensure that the minted nft is not minted again.
// If the minted nft was the last mintable nft, it still won't be minted again because the next random index must stay
// within the bounds of the mintable nfts, set by the new mintable num.
nfts_to_mint.set(randomIndex, nfts_to_mint.get(new_mintable_num));
}
transition mint(nft_mint: NFT_mint, private hiding_nonce: scalar) -> (NFT_mint, NFT_claim) {
// CAUTION: For security purposes, the hiding nonce should be unique for each mint.
let address_hash: field = BHP256::hash_to_field(self.caller);
let claim: field = BHP256::commit_to_field(address_hash, hiding_nonce);
// overflow protection for minting
return (
NFT_mint {
owner: nft_mint.owner,
amount: nft_mint.amount - 1u8
},
NFT_claim {
owner: nft_mint.owner,
claim
}) then finalize(claim);
}
finalize mint(public claim: field) {
// Ensure mint block height is less than current block height
let mint_block: u32 = toggle_settings.get(1u8);
let passed_height_check: bool = mint_block <= block.height;
assert_eq(passed_height_check, true);
// Ensure collection is not frozen, minting is allowed, and is initialized,
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// frozen & minting & initialized flags = 0b0000...1011 = 11u32
// what the settings should be = 0b0000...0011 = 3u32
let is_initialized_and_not_frozen_and_minting: u32 = toggle_settings_status & 11u32;
assert_eq(is_initialized_and_not_frozen_and_minting, 3u32);
// Ensure this claim has not already been made
let existing_claim: field = claims_to_nfts.get_or_use(claim, 0field);
assert_eq(existing_claim, 0field);
// Randomly select an NFT to mint
let randomNum: u128 = ChaCha::rand_u128();
let old_mintable_sum: u128 = general_settings.get_or_use(0u8, 0u128);
let randomIndex: u128 = randomNum % old_mintable_sum;
let tokenEditionHash: field = nfts_to_mint.get(randomIndex);
claims_to_nfts.set(claim, tokenEditionHash);
// Decrease the number of mintable nfts
let new_mintable_num: u128 = old_mintable_sum - 1u128;
general_settings.set(0u8, new_mintable_num);
// Replace the minted nft from the mintable nfts with the last mintable nft.
// This is done to ensure that the minted nft is not minted again.
// If the minted nft was the last mintable nft, it still won't be minted again because the next random index must stay
// within the bounds of the mintable nfts, set by the new mintable num.
nfts_to_mint.set(randomIndex, nfts_to_mint.get(new_mintable_num));
}
transition claim_nft(nft_claim: NFT_claim, private tokenId: TokenId, private edition: scalar) -> NFT {
let tokenHash: field = BHP256::hash_to_field(tokenId);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
return NFT {
owner: nft_claim.owner,
data: tokenId,
edition
} then finalize(nft_claim.claim, tokenEditionHash);
}
finalize claim_nft(public claim: field, tokenEditionHash: field) {
// ensure that the claimed nft matches the claim
let claimedNFT: field = claims_to_nfts.get(claim);
assert_eq(claimedNFT, tokenEditionHash);
claims_to_nfts.set(claim, 0field);
}
// Proof that you own an nft without revealing the nft.
// this burn never actually destroys the nft as the finalize
// block is guaranteed to fail.
transition authorize(
nft: NFT,
public nonce: u64
)
{
return then finalize();
}
finalize authorize(
)
{
// fails on purpose, so that the nft is not burned.
assert_eq(0u8, 1u8);
}
transition transfer_private(
nft: NFT,
private receiver: address
) -> NFT
{
return NFT {
owner: receiver,
data: nft.data,
edition: nft.edition
};
}
transition transfer_public(
private receiver: address,
private data: TokenId,
private edition: scalar
)
{
let tokenHash: field = BHP256::hash_to_field(data);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
let caller: address = self.caller;
return then finalize(receiver, tokenEditionHash, caller);
}
finalize transfer_public(
public receiver: address,
public tokenEditionHash: field,
public caller: address
)
{
assert_eq(caller, nft_owners.get(tokenEditionHash));
nft_owners.set(tokenEditionHash, receiver);
}
transition convert_private_to_public(
nft: NFT
)
{
let tokenHash: field = BHP256::hash_to_field(nft.data);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, nft.edition);
return then finalize(nft.owner, tokenEditionHash);
}
finalize convert_private_to_public(
public owner: address,
public tokenEditionHash: field
)
{
nft_owners.set(tokenEditionHash, owner);
}
transition convert_public_to_private(
private owner: address,
private data: TokenId,
private edition: scalar
) -> NFT
{
assert_eq(owner, self.caller);
let tokenHash: field = BHP256::hash_to_field(data);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
return NFT {
owner,
data,
edition
} then finalize(owner, tokenEditionHash);
}
finalize convert_public_to_private(
public owner: address,
public tokenEditionHash: field
)
{
assert_eq(owner, nft_owners.get(tokenEditionHash));
// mapping::remove is not implemented yet, so instead we set the owner to be a dummy address that cannot publicly transfer or convert to private
nft_owners.set(tokenEditionHash, aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc);
}
}