Object
Supervillain Labs
0 APT
Balance
sidekick
Code
The source code is plain text uploaded by the deployer, which can be different from the actual bytecode.
1module supervlabs::sidekick {
2 use apto_orm::orm_class;
3 use apto_orm::orm_creator;
4 use apto_orm::orm_module;
5 use apto_orm::orm_object;
6 use aptos_framework::object::{Self, Object, ConstructorRef};
7 use aptos_framework::timestamp;
8 use aptos_token_objects::property_map;
9 use aptos_token_objects::token;
10 use std::error;
11 use std::option::{Self, Option};
12 use std::signer;
13 use std::string::{Self, String};
14 use std::vector;
15
16 use aptos_framework::randomness;
17
18 use supervlabs::gacha_rounds;
19 use supervlabs::gacha_item;
20 use supervlabs::sidekick_capsule;
21 use supervlabs::random;
22 use supervlabs::gacha_supply;
23
24 const CLASS_NAME: vector<u8> = b"Sidekick";
25 const ENOT_SIDEKICK_OBJECT: u64 = 4;
26 const EINVALID_AMOUNT: u64 = 5;
27 const EUNABLE_TO_MINT_TARGET_SIDEKICK_DUE_TO_LIMITED_SUPPLY: u64 = 6;
28 const EINVALID_RANDBOX_NUMBER: u64 = 7;
29 const EREACH_TO_MAX_SUPPLY: u64 = 8;
30 const EINVALID_SIDEKICK_INDEX: u64 = 9;
31 const EDEPRECATED_FUNCTION: u64 = 10;
32
33 const SIDEKICK_DRAW_SUPPLY_INDEX: u64 = 0;
34
35 #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
36 struct Sidekick has key, drop {
37 updated_at: u64,
38 salt: u64,
39 }
40
41 #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
42 struct SidekickMintRequest has key, drop {
43 request_id: String,
44 }
45
46 fun init_module(package: &signer) {
47 let class_address = orm_class::update_class_as_collection<Sidekick>(
48 package,
49 string::utf8(b"SuperV Sidekicks"),
50 true, true, true, true, false, true, false,
51 string::utf8(b"https://public.vir.supervlabs.io/virweb/nft/sidekicks/collection.png"),
52 string::utf8(b"Sidekicks, as faithful allies of the Villains, stand by their side and help maximize the Villains' potential. They typically inhabit the wild before being captured by Villains. By establishing a deep connection, they are reborn as true Sidekicks."),
53 0,
54 true,
55 true,
56 @0x0,
57 100,
58 5,
59 );
60 orm_module::set<Sidekick>(
61 package,
62 signer::address_of(package),
63 class_address,
64 );
65 let pa = signer::address_of(package);
66 let orm_class_obj = object::address_to_object<orm_class::OrmClass>(class_address);
67 if (!gacha_rounds::exists_at(class_address)) {
68 let class_signer = orm_class::load_class_signer(package, orm_class_obj);
69 let grades = vector[
70 string::utf8(b"legendary"),
71 string::utf8(b"epic"),
72 string::utf8(b"rare"),
73 string::utf8(b"uncommon"),
74 ];
75 gacha_rounds::add(&class_signer, pa, grades, vector[
76 100000, 800000, 5000000, 30000000], 100000000); // 0.1%, 0.8%, 5%, 30%, etc.
77 };
78
79 if (!gacha_supply::exists_at(class_address) && orm_class::has_class_signer(orm_class_obj)) {
80 let class_signer = orm_class::load_class_signer(package, orm_class_obj);
81 let (current_round, _, _, _) = gacha_rounds::get_probabilities(class_address);
82 gacha_supply::init(&class_signer, vector[
83 // SIDEKICK_DRAW_SUPPLY_INDEX = 0
84 gacha_supply::new_supply(
85 string::utf8(b"legendary_sidekick_draw"),
86 string::utf8(b"legendary"), 0, 1000, current_round),
87 ]);
88 };
89 }
90
91 entry fun update_module(package_owner: &signer) {
92 let (orm_creator, orm_class) = orm_module::get<Sidekick>(@supervlabs);
93 let package = orm_creator::load_creator(package_owner, orm_creator);
94 init_module(&package);
95 let class_address = object::object_address(&orm_class);
96 gacha_rounds::update_probabilities(&package, class_address, 0,
97 vector[
98 string::utf8(b"legendary"),
99 string::utf8(b"epic"),
100 string::utf8(b"rare"),
101 string::utf8(b"uncommon"),
102 ],
103 vector[100000, 800000, 5000000, 30000000],
104 100000000
105 );
106 }
107
108 package fun create_object_by_name(
109 creator_signer: &signer,
110 group_prefix: String,
111 salt: u64,
112 to: Option<address>,
113 grade: String,
114 index: u64,
115 supply_index: u64,
116 ): (ConstructorRef, bool) {
117 let (_, orm_class) = orm_module::get<Sidekick>(@supervlabs);
118 let class_address = object::object_address(&orm_class);
119 let supply = gacha_supply::get(class_address, supply_index);
120 if (gacha_supply::check_run_out(&supply)) {
121 let g = gacha_supply::get_grade(&supply);
122 assert!(g != grade, error::invalid_state(EREACH_TO_MAX_SUPPLY));
123 };
124 let round_up = false;
125 let key = group_prefix;
126 string::append(&mut key, string::utf8(b"/"));
127 string::append(&mut key, grade);
128 let (group, start_index, end_index, _) = gacha_item::get_item_group(key);
129 assert!(index >= start_index, error::invalid_argument(EINVALID_SIDEKICK_INDEX));
130 assert!(index < end_index, error::invalid_argument(EINVALID_SIDEKICK_INDEX));
131 if (grade == gacha_supply::get_grade(&supply)) round_up = true;
132 let (name, uri, description, _, property_keys, property_types, property_values)
133 = gacha_item::load_item_data(creator_signer, group, index);
134 let ref = token::create(
135 creator_signer,
136 string::utf8(b"SuperV Sidekicks"),
137 description,
138 name, // format: "{ItemName} #{count}"
139 option::none(),
140 uri,
141 );
142 let object_signer = orm_object::init<Sidekick>(creator_signer, &ref, orm_class);
143 orm_object::init_properties(&ref,
144 property_keys,
145 property_types,
146 property_values,
147 );
148 let updated_at = timestamp::now_seconds();
149 move_to<Sidekick>(&object_signer, Sidekick {
150 updated_at: updated_at, salt: salt
151 });
152 if (option::is_some(&to)) {
153 let destination = option::extract<address>(&mut to);
154 orm_object::transfer_initially(&ref, destination);
155 };
156 if (round_up) {
157 gacha_supply::increase(class_address, supply_index, 1);
158 };
159 (ref, round_up)
160 }
161
162 package fun create_object(
163 creator_signer: &signer,
164 group_prefix: String,
165 salt: u64,
166 to: Option<address>,
167 grades: vector<String>,
168 numerators: vector<u64>,
169 denominator: u64,
170 ): (ConstructorRef, bool) {
171 let (_, orm_class) = orm_module::get<Sidekick>(@supervlabs);
172 let class_address = object::object_address(&orm_class);
173 let supply = gacha_supply::get(class_address, SIDEKICK_DRAW_SUPPLY_INDEX);
174 if (gacha_supply::check_run_out(&supply)) {
175 let index = gacha_supply::get_add_info(&supply);
176 let numerator = vector::borrow_mut(&mut numerators, index);
177 *numerator = 0;
178 };
179
180 let roll1 = randomness::u64_range(0, denominator);
181 let round_up = false;
182 let (group, start_index, end_index) = (string::utf8(b""), 0, 0);
183 let i = 0;
184 let upto = 0;
185 let len = vector::length(&grades);
186 while (i < len) {
187 let grade = vector::borrow(&grades, i);
188 let numerator = vector::borrow(&numerators, i);
189 upto = upto + *numerator;
190 if (roll1 < upto) {
191 let key = group_prefix;
192 string::append(&mut key, string::utf8(b"/"));
193 string::append(&mut key, *grade);
194 (group, start_index, end_index, _) = gacha_item::get_item_group(key);
195 if (i == gacha_supply::get_add_info(&supply)) round_up = true;
196 break
197 };
198 i = i + 1;
199 };
200 if (i == len) {
201 let key = group_prefix;
202 string::append(&mut key, string::utf8(b"/common"));
203 (group, start_index, end_index, _) = gacha_item::get_item_group(key);
204 };
205
206 let roll2 = randomness::u64_range(start_index, end_index);
207 let (name, uri, description, _, property_keys, property_types, property_values)
208 = gacha_item::load_item_data(creator_signer, group, roll2);
209 let ref = token::create(
210 creator_signer,
211 string::utf8(b"SuperV Sidekicks"),
212 description,
213 name, // format: "{ItemName} #{count}"
214 option::none(),
215 uri,
216 );
217 let object_signer = orm_object::init<Sidekick>(creator_signer, &ref, orm_class);
218
219 orm_object::init_properties(&ref,
220 property_keys,
221 property_types,
222 property_values,
223 );
224
225 let updated_at = timestamp::now_seconds();
226 move_to<Sidekick>(&object_signer, Sidekick {
227 updated_at: updated_at, salt: salt
228 });
229
230 random::store_roll_u64(&object_signer, roll1, 0, denominator);
231 random::store_roll_u64(&object_signer, roll2, start_index, end_index);
232
233 if (option::is_some(&to)) {
234 let destination = option::extract<address>(&mut to);
235 orm_object::transfer_initially(&ref, destination);
236 };
237
238 if (round_up) {
239 gacha_supply::increase(class_address, SIDEKICK_DRAW_SUPPLY_INDEX, 1);
240 };
241 (ref, round_up)
242 }
243
244 fun update_object<T: key>(
245 package_owner: &signer,
246 object: Object<T>,
247 ) acquires Sidekick {
248 let object_address = object::object_address(&object);
249 assert!(
250 exists<Sidekick>(object_address),
251 error::invalid_argument(ENOT_SIDEKICK_OBJECT),
252 );
253 let _object_signer = orm_object::load_signer(package_owner, object);
254 let user_data = borrow_global_mut<Sidekick>(object_address);
255 user_data.updated_at = timestamp::now_seconds();
256 }
257
258 public fun delete_object<T: key>(
259 package_owner: &signer,
260 object: Object<T>,
261 ) acquires Sidekick {
262 let object_address = object::object_address(&object);
263 assert!(
264 exists<Sidekick>(object_address),
265 error::invalid_argument(ENOT_SIDEKICK_OBJECT),
266 );
267 move_from<Sidekick>(object_address);
268 let object_signer = orm_object::delete_and_load_signer(package_owner, object, string::utf8(b"release"));
269 random::clear_log(&object_signer);
270 gacha_rounds::clear_log(&object_signer);
271 }
272
273 public fun delete_for_fusion<T: key>(
274 package_owner: &signer,
275 object: Object<T>,
276 ) acquires Sidekick {
277 let object_address = object::object_address(&object);
278 assert!(
279 exists<Sidekick>(object_address),
280 error::invalid_argument(ENOT_SIDEKICK_OBJECT),
281 );
282 move_from<Sidekick>(object_address);
283 let object_signer = orm_object::load_signer(package_owner, object);
284 random::clear_log(&object_signer);
285 gacha_rounds::clear_log(&object_signer);
286 orm_object::delete(package_owner, object, string::utf8(b"fusion"));
287 }
288
289 #[randomness]
290 entry fun create(
291 package_owner: &signer,
292 sidekick_capsule: address,
293 salt: u64,
294 ) {
295 // burn sidekick_capsule
296 if (sidekick_capsule != @0x0) {
297 let sidekick_capsule_obj = object::address_to_object<sidekick_capsule::SidekickCapsule>(sidekick_capsule);
298 sidekick_capsule::delete_object(package_owner, sidekick_capsule_obj);
299 };
300 let (orm_creator, orm_class) = orm_module::get<Sidekick>(@supervlabs);
301 let class_address = object::object_address(&orm_class);
302 let creator_signer = orm_creator::load_creator(package_owner, orm_creator);
303 let (current_round, grades, numerators, denominator) = gacha_rounds::get_probabilities(class_address);
304
305 let (ref, round_up) = create_object(
306 &creator_signer, string::utf8(b"sidekick"), salt, option::none(),
307 grades, numerators, denominator,
308 );
309 gacha_rounds::set_round_log(class_address, &ref, current_round);
310 if (round_up) {
311 gacha_rounds::round_up(&creator_signer, class_address);
312 };
313 }
314
315 #[randomness]
316 entry fun create_to(
317 package_owner: &signer,
318 sidekick_capsule: address,
319 salt: u64,
320 to: address,
321 ) {
322 // burn sidekick_capsule
323 if (sidekick_capsule != @0x0) {
324 let sidekick_capsule_obj = object::address_to_object<sidekick_capsule::SidekickCapsule>(sidekick_capsule);
325 sidekick_capsule::delete_object(package_owner, sidekick_capsule_obj);
326 };
327 let (orm_creator, orm_class) = orm_module::get<Sidekick>(@supervlabs);
328 let class_address = object::object_address(&orm_class);
329 let creator_signer = orm_creator::load_creator(package_owner, orm_creator);
330 let (current_round, grades, numerators, denominator) = gacha_rounds::get_probabilities(class_address);
331
332 let (ref, round_up) = create_object(
333 &creator_signer, string::utf8(b"sidekick"), salt, option::some(to),
334 grades, numerators, denominator,
335 );
336 gacha_rounds::set_round_log(class_address, &ref, current_round);
337 if (round_up) {
338 gacha_rounds::round_up(&creator_signer, class_address);
339 };
340 }
341
342 fun draw_sidekicks_internal(
343 package_owner: &signer,
344 sidekick_capsules: vector<address>,
345 salt: u64,
346 amount: u64,
347 to: address,
348 ): vector<ConstructorRef> {
349 assert!(amount > 0, error::invalid_argument(EINVALID_AMOUNT));
350 let calsules_len = vector::length(&sidekick_capsules);
351 if (calsules_len > 0) {
352 assert!(amount == calsules_len, error::invalid_argument(EINVALID_AMOUNT));
353 };
354 // burn all sidekick_capsules
355 vector::for_each_ref(&sidekick_capsules, |sidekick_capsule| {
356 if (*sidekick_capsule != @0x0) {
357 let sidekick_capsule_obj =
358 object::address_to_object<sidekick_capsule::SidekickCapsule>(*sidekick_capsule);
359 sidekick_capsule::delete_object(package_owner, sidekick_capsule_obj);
360 };
361 });
362
363 let (orm_creator, orm_class) = orm_module::get<Sidekick>(@supervlabs);
364 let class_address = object::object_address(&orm_class);
365 let creator_signer = orm_creator::load_creator(package_owner, orm_creator);
366
367 let current_round: u64 = 0;
368 let grades: vector<String> = vector::empty();
369 let numerators: vector<u64> = vector::empty();
370 let denominator: u64 = 100000000;
371 let load_prob: bool = true;
372 let i = 0;
373 let r: vector<ConstructorRef> = vector::empty();
374 while (i < amount) {
375 if (load_prob) {
376 (current_round, grades, numerators, denominator)
377 = gacha_rounds::get_probabilities(class_address);
378 load_prob = false;
379 };
380 let (ref, round_up) = create_object(
381 &creator_signer, string::utf8(b"sidekick"), salt + i, option::some(to),
382 grades, numerators, denominator,
383 );
384 gacha_rounds::set_round_log(class_address, &ref, current_round);
385 if (round_up) {
386 gacha_rounds::round_up(&creator_signer, class_address);
387 load_prob = true;
388 };
389 i = i+1;
390 vector::push_back(&mut r, ref);
391 };
392 r
393 }
394
395 #[randomness]
396 entry fun draw_sidekicks(
397 package_owner: &signer,
398 sidekick_capsules: vector<address>,
399 salt: u64,
400 amount: u64,
401 to: address,
402 ) {
403 draw_sidekicks_internal(package_owner, sidekick_capsules, salt, amount, to);
404 }
405
406 entry fun update(
407 package_owner: &signer,
408 object: address,
409 ) acquires Sidekick {
410 let obj = object::address_to_object<Sidekick>(object);
411 update_object(package_owner, obj);
412 }
413
414 entry fun delete(
415 package_owner: &signer,
416 object: address,
417 ) acquires Sidekick {
418 let obj = object::address_to_object<Sidekick>(object);
419 delete_object(package_owner, obj);
420 }
421
422 entry fun batch_delete(
423 package_owner: &signer,
424 objects: vector<address>,
425 ) acquires Sidekick {
426 vector::enumerate_ref(&objects, |_i, obj_addr| {
427 let obj = object::address_to_object<Sidekick>(*obj_addr);
428 delete_object(package_owner, obj);
429 });
430 }
431
432 #[view]
433 public fun get(object: address): (
434 string::String,
435 string::String,
436 string::String,
437 address,
438 u64,
439 u64,
440 ) acquires Sidekick {
441 let o = object::address_to_object<Sidekick>(object);
442 let user_data = borrow_global<Sidekick>(object);
443 (
444 token::name(o),
445 token::uri(o),
446 token::description(o),
447 property_map::read_address(&o, &string::utf8(b"sidekick_capsule")),
448 user_data.updated_at,
449 user_data.salt,
450 )
451 }
452
453 #[view]
454 public fun exists_at(object: address): bool {
455 exists<Sidekick>(object)
456 }
457
458 #[view]
459 public fun replay_to_create_object(_object: address): (
460 string::String, u64, u64, u64, u64, u64, u64, u64, u64, string::String, u64, u64,
461 ) {
462 abort(error::invalid_argument(EDEPRECATED_FUNCTION))
463 }
464
465 #[test_only]
466 public fun update_limited_supply(package: &signer, max: u64, max_from_game: u64) {
467 init_module(package);
468 let (_, orm_class) = orm_module::get<Sidekick>(@supervlabs);
469 let class_address = object::object_address(&orm_class);
470 let class_signer = orm_class::load_class_signer(package, orm_class);
471 let (current_round, _, _, _) = gacha_rounds::get_probabilities(class_address);
472 gacha_supply::init(&class_signer, vector[
473 // SIDEKICK_DRAW_SUPPLY_INDEX = 0
474 gacha_supply::new_supply(string::utf8(b"legendary_sidekick_draw"), string::utf8(b"legendary"), 0, max, current_round),
475 // SIDEKICK_FROM_GAME_SUPPLY_INDEX = 1
476 gacha_supply::new_supply(string::utf8(b"legendary_sidekick_from_game"), string::utf8(b"legendary"), 0, max_from_game, 0),
477 ]);
478 }
479
480 #[lint::allow_unsafe_randomness]
481 #[test(aptos = @0x1, my_poa = @0x456, user1 = @0x789, user2 = @0xabc, apto_orm = @apto_orm, creator = @package_creator)]
482 // #[expected_failure(abort_code = 196614, location = Self)] // EUNABLE_TO_MINT_TARGET_SIDEKICK_DUE_TO_LIMITED_SUPPLY
483 public entry fun test_sidekick_limited_supply(
484 aptos: &signer, apto_orm: &signer, creator: &signer, my_poa: &signer, user1: &signer, user2: &signer
485 ) {
486 use apto_orm::test_utilities;
487 use apto_orm::power_of_attorney;
488 // use aptos_std::debug;
489 test_utilities::init_network(aptos, 1234);
490 randomness::initialize_for_testing(aptos);
491
492 let program_address = signer::address_of(apto_orm);
493 let creator_address = signer::address_of(creator);
494 let my_poa_address = signer::address_of(my_poa);
495 let user1_address = signer::address_of(user1);
496 let user2_address = signer::address_of(user2);
497
498 test_utilities::create_and_fund_account(program_address, 100);
499 test_utilities::create_and_fund_account(creator_address, 10);
500 test_utilities::create_and_fund_account(my_poa_address, 100);
501 test_utilities::create_and_fund_account(user1_address, 100);
502 test_utilities::create_and_fund_account(user2_address, 10);
503 let package = orm_creator::create_creator(creator, string::utf8(b"supervlabs"));
504
505 let target_supply = 2;
506 update_limited_supply(&package, target_supply, target_supply);
507
508 gacha_item::test_init_module(creator, &package);
509 power_of_attorney::register_poa(creator, my_poa, 1400, 0);
510 let refs = draw_sidekicks_internal(my_poa, vector::empty(), 1, 5000, user1_address);
511 let len = vector::length(&refs);
512 let i = 0;
513 while (i < len) {
514 let ref = vector::borrow(&refs, i);
515 let obj = object::object_from_constructor_ref<Sidekick>(ref);
516 assert!(object::owner(obj) == user1_address, 1);
517 i = i + 1;
518 };
519 let (_, orm_class) = orm_module::get<Sidekick>(@supervlabs);
520 let class_address = object::object_address(&orm_class);
521 let supply = gacha_supply::get(class_address, SIDEKICK_DRAW_SUPPLY_INDEX);
522 // debug::print<gacha_supply::SupplyInfo>(&supply);
523 assert!(gacha_supply::check_run_out(&supply), 1);
524 }
525}