| 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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520 |
16×
16×
16×
16×
16×
16×
16×
16×
16×
16×
16×
16×
1×
5×
2×
52×
1×
3×
1×
2068×
120×
120×
120×
120×
122×
121×
120×
119×
118×
118×
1034×
1034×
1034×
1034×
1034×
1034×
1034×
1034×
1034×
1034×
1034×
1034×
118×
118×
118×
118×
118×
121×
120×
119×
118×
118×
116×
115×
114×
114×
113×
114×
3×
2×
113×
113×
113×
113×
113×
113×
113×
113×
113×
113×
15×
18×
17×
16×
15×
2×
15×
15×
15×
8×
8×
8×
8×
8×
8×
8×
8×
8×
12×
11×
10×
10×
10×
7×
6×
6×
8×
8×
1147×
1147×
1147×
1147×
3374×
51×
51×
51×
1147×
1096×
10×
10×
10×
9×
9×
9×
51×
51×
102×
102×
102×
10×
10×
102×
1×
1×
2×
1×
2×
3×
2×
2×
100×
100×
12×
8×
13×
| // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./ExternalMetadata.sol";
import "./ITokenMetadata.sol";
import "./IERC1155MintablePayable.sol";
import {StateProofVerifier as Verifier} from "./StateProofVerifier.sol";
import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol";
/*
KUDZU CHRISTMAS CUP: AIRDROP TOURNAMENT
MINT A TEAM
AIRDROP TOKENS
TOP 3 TEAMS WITH MOST TOKENS WINS
MINT: 1 TIA
AIRDROP: 0.1 TIA
PRIZE: 80% OF FEES + 100% OF GAS
SPLIT: 60/30/10
ENDS: DEC 31 2024 00:00 UTC
NO CONTRACT BASED ACCOUNTS
CAN ONLY AIRDROP TO ADDRESSES EXISTING BEFORE DEC 1, 2024
AFTER DEC 25, 2024 AIRDROP IS RATE LIMITED 1 AIRDROP PER 26 BLOCKS (~every minute)
INDIVIDUALS ARE RESPONSIBLE FOR CLAIMING THEIR PORTION OF THE PRIZE
PRIZE IS CLAIMABLE 3 DAYS AFTER CONTEST ENDS (after gas fees are added to prize)
PRIZE IS FORFEITED IF NOT COLLECTED WITHIN 90 DAYS
https://kudzu.christmas
https://trifle.life
https://folia.app
https://forma.art
*/
contract Kudzu is ERC1155, Ownable, ITokenMetadata, IERC1155MintablePayable {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
//
// Constants
string public constant name = "Kudzu";
string public constant symbol = "KUDZU";
uint256 public ETH = 1;
uint256 public FORMA = 984122;
uint256 public BASE = 8453;
uint256 public ARB = 42161;
uint256 public OP = 10;
uint256 public constant DENOMINATOR = 1000;
uint256 public constant FIRST_PLACE_PERCENT = 600; // 60%
uint256 public constant SECOND_PLACE_PERCENT = 300; // 30%
uint256 public constant THIRD_PLACE_PERCENT = 100; // 10%
//
// Variables
ExternalMetadata public metadata;
uint256 public startDate = 1733853600; // Tue Dec 10 2024 18:00:00 GMT+0000
uint256 public endDate = 1735603200; // Tue Dec 31 2024 00:00:00 GMT+0000
uint256 public christmas = 1735171200; // Fri Dec 26 2024 00:00:00 GMT+0000
uint256 public claimDelay = 3 days; // Allow 3 days for additional prize contributions
uint256 public forfeitClaim = 90 days; // Forfeit ability to claim prize after 90 days
address public recipient;
uint256 public createPrice = 1 ether; // TIA ~$6.80 on day of launch
uint256 public airdropPrice = 0.1 ether; // TIA ~$0.68 on day of launch
uint256 public percentOfCreate = 200; // 200 / 1000 = 20%
uint256 public percentOfAirdrop = 200; // 200 / 1000 = 20%
//
// State
uint256 public totalSquads;
uint256 public prizePoolFinal;
uint256 public totalClaimed;
mapping(uint256 => mapping(address => uint256)) public claimed; // tokenId => address => quantity
mapping(uint256 => bool) public exists;
mapping(uint256 => uint256) public squadSupply;
mapping(address => bool) public accountExists;
mapping(uint256 => bytes32) public stateRoots; // chainId => stateRoot
mapping(uint256 => uint256) public blockNumbers; // chainId => blockNumber
struct Record {
uint256 tokenId;
uint256 supply;
}
Record[3] public topSquads;
mapping(address => uint256) public balances;
// Events
event Created(uint256 tokenId, address buyer);
event Airdrop(uint256 tokenId, address from, address to);
event ClaimedPrize(uint256 tokenId, address claimer, uint256 prizeAmount);
event EthMoved(
address indexed to,
bool indexed success,
bytes returnData,
uint256 amount
);
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
//
// Constructor
constructor(ExternalMetadata _metadata) ERC1155("") {
metadata = _metadata;
recipient = msg.sender;
stateRoots[
ETH
] = 0x376ed3ba55cc553c2bf651460471f44ecb604216d3eec20eda1a099b5a5f2d0f; // Homestead
blockNumbers[ETH] = 21303934; // Dec-01-2024 12:00:11 AM +UTC
stateRoots[
FORMA
] = 0x780dc28ebe79f860695b488b6618c167a3f7d8bcbd0a88b3f8f22cd7e7c7f444; // Forma
blockNumbers[FORMA] = 7065245; // Dec-01-2024 12:00:00 AM +UTC
stateRoots[
BASE
] = 0xb35b67b8a3efce22a121107605afb766db098ccf2e0a89fdb537df6dd45d2505; // Base
blockNumbers[BASE] = 23110927; // Dec-01-2024 12:00:01 AM +UTC
stateRoots[
ARB
] = 0xf23873ef08d1ea3ca478ffbcb22949abcadc91e302c297039fd81b84995ea429;
blockNumbers[ARB] = 280041525;
stateRoots[
OP
] = 0x533c820b0daeb5e8534f17987405ce6c984b862fc48d08565f554c49d13a300e;
blockNumbers[OP] = 128706212;
}
receive() external payable {
emit EthMoved(address(this), true, "", msg.value);
}
//
// Read Functions
function balanceOf(address _owner) external view returns (uint256 balance) {
return balances[_owner];
}
function blocktimestamp() public view returns (uint256) {
return block.timestamp;
}
function getWinningToken(
uint256 place
) public view returns (uint256 tokenId) {
return topSquads[place].tokenId;
}
function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
return uri(tokenId);
}
function uri(
uint256 tokenId
)
public
view
virtual
override(ERC1155, ITokenMetadata)
returns (string memory)
{
return metadata.getMetadata(tokenId);
}
function getTokenMetadata(
uint256 tokenId
) public view override returns (string memory) {
return uri(tokenId);
}
function pseudoRNG(uint modulo, uint nonce) private view returns (uint256) {
return
uint256(
keccak256(
abi.encodePacked(
block.prevrandao,
block.timestamp,
totalSquads,
nonce
)
)
) % modulo;
}
function userExists(
address user,
bytes32 root,
bytes memory _proofRlpBytes
) public pure virtual returns (bool, uint256) {
RLPReader.RLPItem[] memory proofs = _proofRlpBytes.toRlpItem().toList();
bytes32 addressHash = keccak256(abi.encodePacked(user));
Verifier.Account memory accountPool = Verifier.extractAccountFromProof(
addressHash,
root,
proofs[0].toList()
);
return (accountPool.exists, accountPool.balance);
}
//
// Write Functions
function create(address _to, uint256 quantity) public payable {
require(quantity > 0, "CANT CREATE 0");
require(block.timestamp > startDate, "GAME HASN'T STARTED");
require(block.timestamp < endDate, "GAME ENDED");
require(msg.value == (createPrice * quantity), "INSUFFICIENT FUNDS");
uint256 creatorQuantity = 10;
for (uint256 i = 0; i < quantity; i++) {
uint256 tokenId = totalSquads + 1;
totalSquads++;
tokenId = tokenId << 8;
tokenId = tokenId | pseudoRNG(32, 1);
tokenId = tokenId << 8;
tokenId = tokenId | pseudoRNG(32, 2);
exists[tokenId] = true;
_mint(_to, tokenId, creatorQuantity, "");
balances[_to] += creatorQuantity;
squadSupply[tokenId] += creatorQuantity;
tallyLeaderboard(tokenId);
emit Created(tokenId, _to);
}
uint256 payoutToRecipient = (msg.value * percentOfCreate) / DENOMINATOR;
(bool success, bytes memory data) = recipient.call{
value: payoutToRecipient
}("");
emit EthMoved(recipient, success, data, payoutToRecipient);
Erequire(success, "TRANSFER FAILED");
emit EthMoved(address(this), true, "", msg.value - payoutToRecipient);
}
uint256 public constant ONE_PER_NUM_BLOCKS = 26; // ~1 per minute per family
mapping(uint256 => uint256) public rateLimit; // tokenId => timestamp
function airdrop(
address _to,
uint256 tokenId,
bytes memory _proofRlpBytes,
uint256 chainId
) public payable {
require(block.timestamp > startDate, "GAME HASN'T STARTED");
require(block.timestamp < endDate, "GAME ENDED");
require(msg.value == airdropPrice, "INSUFFICIENT FUNDS");
Erequire(msg.sender == tx.origin, "NO SMART CONTRACTS");
require(balanceOf(msg.sender, tokenId) > 0, "NOT A HOLDER");
require(balanceOf(_to, tokenId) == 0, "ALREADY INFECTED");
if (!accountExists[_to]) {
(bool doesExist, ) = userExists(
_to,
stateRoots[chainId],
_proofRlpBytes
);
require(doesExist, "USER DOES NOT EXIST ON SPECIFIED CHAIN");
accountExists[_to] = true;
}
if (block.timestamp > christmas) {
require(
rateLimit[tokenId] < block.timestamp - ONE_PER_NUM_BLOCKS,
"CHRISTMAS RATE LIMIT EXCEEDED"
);
rateLimit[tokenId] = block.timestamp;
}
squadSupply[tokenId] += 1;
tallyLeaderboard(tokenId);
_mint(_to, tokenId, 1, "");
balances[_to] += 1;
emit Airdrop(tokenId, msg.sender, _to);
uint256 payoutToRecipient = (msg.value * percentOfAirdrop) /
DENOMINATOR;
(bool success, bytes memory data) = recipient.call{
value: payoutToRecipient
}("");
emit EthMoved(recipient, success, data, payoutToRecipient);
Erequire(success, "TRANSFER FAILED");
emit EthMoved(address(this), true, "", msg.value - payoutToRecipient);
}
function isWinningtoken(uint256 tokenId) public view returns (bool) {
return
tokenId == getWinningToken(0) ||
tokenId == getWinningToken(1) ||
tokenId == getWinningToken(2);
}
function claimPrize(uint256 place) public {
require(block.timestamp > endDate, "GAME NOT ENDED");
require(
block.timestamp > (endDate + claimDelay),
"CLAIM DELAY NOT ENDED"
);
require(
block.timestamp < (endDate + forfeitClaim),
"CLAIM PERIOD ENDED"
);
// if contest is over lock in the prize pool
if (totalClaimed == 0) {
prizePoolFinal = address(this).balance;
}
uint256 tokenId = getWinningToken(place);
uint256 tokenBalance = balanceOf(msg.sender, tokenId);
require(claimed[tokenId][msg.sender] < tokenBalance, "ALREADY CLAIMED");
uint256 claimableAmount = tokenBalance - claimed[tokenId][msg.sender];
uint256 placePercentage = (prizePoolFinal *
(
place == 0 ? FIRST_PLACE_PERCENT : place == 1
? SECOND_PLACE_PERCENT
: THIRD_PLACE_PERCENT
)) / DENOMINATOR;
uint256 proportionalPrize = (placePercentage * claimableAmount) /
squadSupply[tokenId];
totalClaimed += proportionalPrize;
claimed[tokenId][msg.sender] += claimableAmount;
(bool success, bytes memory data) = msg.sender.call{
value: proportionalPrize
}("");
Erequire(success, "TRANSFER FAILED");
emit EthMoved(msg.sender, success, data, proportionalPrize);
emit ClaimedPrize(tokenId, msg.sender, proportionalPrize);
}
//
// Internal Functions
function transfersEnabled(
address _from,
address _to,
uint256 tokenId,
uint256 amount
) internal {
require(block.timestamp > endDate, "GAME NOT ENDED");
if (isWinningtoken(tokenId)) {
// can infect if game is over
// and either:
// 1. not a winning token
// 2. winning token and either:
// 2a. claimed prize
// 2b. forfeit period is over
bool alreadyClaimed = claimed[tokenId][msg.sender] >= amount;
bool claimIsOver = block.timestamp > (endDate + forfeitClaim);
require(
alreadyClaimed || claimIsOver,
"WINNERS CANT TRANSFER UNTIL THEY CLAIM OR CLAIM PERIOD IS OVER"
);
// if claim is live, prevent new owner from claiming prize
if (!claimIsOver) {
claimed[tokenId][_to] += amount;
claimed[tokenId][_from] -= amount;
}
}
balances[_to] += amount;
balances[_from] -= amount;
}
function tallyLeaderboard(uint256 tokenId) internal {
uint256 supply = squadSupply[tokenId];
bool leader;
uint256 leaderIndex;
for (uint256 i = 0; i < 3; i++) {
if (topSquads[i].tokenId == tokenId) {
leader = true;
leaderIndex = i;
break;
}
}
if (!leader) {
if (supply > topSquads[0].supply) {
topSquads[2] = topSquads[1];
topSquads[1] = topSquads[0];
topSquads[0] = Record({tokenId: tokenId, supply: supply});
} else if (
supply > topSquads[1].supply && topSquads[0].tokenId != tokenId
) {
topSquads[2] = topSquads[1];
topSquads[1] = Record({tokenId: tokenId, supply: supply});
} else if (
supply > topSquads[2].supply &&
topSquads[0].tokenId != tokenId &&
topSquads[1].tokenId != tokenId
) {
topSquads[2] = Record({tokenId: tokenId, supply: supply});
}
} else {
topSquads[leaderIndex].supply = supply;
// stable sort algorithm to preserve order of equal elements
for (uint256 i = 1; i < 3; i++) {
Record memory key = topSquads[i];
uint256 j = i;
// Keep moving elements forward while:
// 1. We haven't reached the start (j > 0)
// 2. The element before has a lower supply
while (j > 0 && topSquads[j - 1].supply < key.supply) {
topSquads[j] = topSquads[j - 1];
j--;
}
topSquads[j] = key;
}
}
}
//
// Admin Functions
function addChain(
uint256 chainId,
uint256 blocknumber,
bytes32 roothash
) public onlyOwner {
stateRoots[chainId] = roothash;
blockNumbers[chainId] = blocknumber;
}
function emitBatchMetadataUpdate() public onlyOwner {
emit BatchMetadataUpdate(1, totalSquads);
}
function updateMetadata(ExternalMetadata _metadata) public onlyOwner {
metadata = _metadata;
}
function updateRecipient(address _recipient) public onlyOwner {
recipient = _recipient;
}
function collectForfeitPrizeAfterDelay(
address payable _to,
uint256 amount
) public onlyOwner {
require(
block.timestamp > (endDate + forfeitClaim),
"REMAINING PRIZE IS FORFEIT ONLY AFTER DELAY PERIOD"
);
(bool sent, bytes memory data) = _to.call{value: amount}("");
emit EthMoved(_to, sent, data, amount);
}
// Overrides
function mint(
address _to,
uint256 _tokenId,
uint256 _amount
) external payable override {
Erequire(_tokenId == 0, "MINT ONLY FOR NEW TOKENS");
create(_to, _amount);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
transfersEnabled(from, to, id, amount);
super.safeTransferFrom(from, to, id, amount, data);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
transfersEnabled(from, to, id, amount);
}
super.safeBatchTransferFrom(from, to, ids, amounts, data);
}
function supportsInterface(
bytes4 interfaceId
) public view override returns (bool) {
return
interfaceId == type(ITokenMetadata).interfaceId ||
interfaceId == type(IERC1155MintablePayable).interfaceId ||
interfaceId == type(Ownable).interfaceId ||
interfaceId == bytes4(0x49064906) || // IERC4906 MetadataUpdate
super.supportsInterface(interfaceId);
}
}
|