From 1225e738547bf5effde3c9eca7b52b24e1b7203b Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 22 Nov 2024 03:07:43 +0000 Subject: [PATCH 1/9] audit fixes unit tests --- launchpad-common/src/winner_selection.rs | 4 + .../tests/guaranteed_tickets_setup/mod.rs | 2 +- .../tests/guaranteed_tickets_test.rs | 452 ++++++++++++++++++ 3 files changed, 457 insertions(+), 1 deletion(-) diff --git a/launchpad-common/src/winner_selection.rs b/launchpad-common/src/winner_selection.rs index 29c7d20..cebfb4d 100644 --- a/launchpad-common/src/winner_selection.rs +++ b/launchpad-common/src/winner_selection.rs @@ -116,6 +116,10 @@ pub trait WinnerSelectionModule: let (mut rng, mut ticket_position) = self.load_select_winners_operation(); let run_result = self.run_while_it_has_gas(|| { + if nr_winning_tickets == 0 { + return STOP_OP; + } + self.shuffle_single_ticket(&mut rng, ticket_position, last_ticket_position); if ticket_position == nr_winning_tickets { diff --git a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_setup/mod.rs b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_setup/mod.rs index 5c8c44b..1302ea3 100644 --- a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_setup/mod.rs +++ b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_setup/mod.rs @@ -124,7 +124,7 @@ where ); sc.add_tickets_endpoint(args); - // 1 ticket for the max tier gets removed + // 1 ticket for the guaranteed entry gets removed assert_eq!(sc.nr_winning_tickets().get(), nr_winning_tickets - 1); assert_eq!(sc.users_with_guaranteed_ticket().len(), 1); assert!(sc diff --git a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs index 6591497..59519b4 100644 --- a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs +++ b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs @@ -1175,6 +1175,130 @@ fn blacklist_scenario_test() { ); } +#[test] +fn remove_from_blacklist_underflow_and_empty_storage_test() { + let nr_winning_tickets = 6; + let mut lp_setup = LaunchpadSetup::new( + nr_winning_tickets, + launchpad_guaranteed_tickets_v2::contract_obj, + ); + + let mut participants = lp_setup.participants.clone(); + + let new_participant = lp_setup + .b_mock + .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64 * 2)); + participants.push(new_participant.clone()); + + let new_participant2 = lp_setup + .b_mock + .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64 * 2)); + participants.push(new_participant.clone()); + + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK - 1); + + // Set guaranteed tickets for new_participant + lp_setup + .b_mock + .execute_tx( + &lp_setup.owner_address, + &lp_setup.lp_wrapper, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + // New participant with complex structure + let mut guaranteed_tickets_info = MultiValueEncodedCounted::new(); + + guaranteed_tickets_info.push((3, 3).into()); + args.push( + ( + managed_address!(&new_participant), + 6, + guaranteed_tickets_info, + ) + .into(), + ); + + sc.add_tickets_endpoint(args); + }, + ) + .assert_ok(); + + // Blacklist new_participant + lp_setup + .b_mock + .execute_tx( + &lp_setup.owner_address, + &lp_setup.lp_wrapper, + &rust_biguint!(0), + |sc| { + let mut blacklist = MultiValueEncoded::new(); + blacklist.push(managed_address!(&new_participant)); + blacklist.push(managed_address!(&participants[0])); + sc.add_users_to_blacklist_endpoint(blacklist); + }, + ) + .assert_ok(); + + // Set guaranteed tickets for new_participant2 + lp_setup + .b_mock + .execute_tx( + &lp_setup.owner_address, + &lp_setup.lp_wrapper, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + // New participant with complex structure + let mut guaranteed_tickets_info = MultiValueEncodedCounted::new(); + + guaranteed_tickets_info.push((3, 3).into()); + args.push( + ( + managed_address!(&new_participant2), + 6, + guaranteed_tickets_info, + ) + .into(), + ); + + sc.add_tickets_endpoint(args); + }, + ) + .assert_ok(); + + // Remove first participant from blacklist + lp_setup + .b_mock + .execute_tx( + &lp_setup.owner_address, + &lp_setup.lp_wrapper, + &rust_biguint!(0), + |sc| { + let mut blacklist = MultiValueEncoded::new(); + blacklist.push(managed_address!(&participants[0])); + sc.remove_guaranteed_users_from_blacklist_endpoint(blacklist); + }, + ) + .assert_ok(); + + // Try remove new_participant from blacklist + // Should throw an error + lp_setup + .b_mock + .execute_tx( + &lp_setup.owner_address, + &lp_setup.lp_wrapper, + &rust_biguint!(0), + |sc| { + let mut blacklist = MultiValueEncoded::new(); + blacklist.push(managed_address!(&new_participant)); + sc.remove_guaranteed_users_from_blacklist_endpoint(blacklist); + }, + ) + .assert_error(4, "Number of winning tickets exceeded"); +} + #[test] fn confirm_less_tickets_than_total_available_with_vesting_scenario_test() { let nr_random_tickets = 1; @@ -1700,3 +1824,331 @@ fn contract_pause_test() { &rust_biguint!(LAUNCHPAD_TOKENS_PER_TICKET), ); } + +#[test] +fn fair_chance_allocation_and_ticket_redistribution_test() { + let nr_winning_tickets = 9; + let mut lp_setup = LaunchpadSetup::new( + nr_winning_tickets, + launchpad_guaranteed_tickets_v2::contract_obj, + ); + + // Set unlock schedule: 100% release immediately + let unlock_milestones = vec![(0, 10000)]; + lp_setup.set_unlock_schedule(unlock_milestones); + + let mut participants = lp_setup.participants.clone(); + + let new_participant = lp_setup + .b_mock + .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64 * 2)); + participants.push(new_participant.clone()); + + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK - 1); + + // Set guaranteed tickets for users + lp_setup + .b_mock + .execute_tx( + &lp_setup.owner_address, + &lp_setup.lp_wrapper, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + // New participant with complex structure + let mut guaranteed_tickets_info = MultiValueEncodedCounted::new(); + guaranteed_tickets_info.push((1, 6).into()); + guaranteed_tickets_info.push((2, 4).into()); + guaranteed_tickets_info.push((3, 3).into()); + args.push( + ( + managed_address!(&new_participant), + 6, + guaranteed_tickets_info, + ) + .into(), + ); + + sc.add_tickets_endpoint(args); + }, + ) + .assert_ok(); + + // Confirm tickets + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK); + lp_setup.confirm(&participants[0], 1).assert_ok(); + lp_setup.confirm(&participants[1], 2).assert_ok(); + lp_setup.confirm(&participants[2], 3).assert_ok(); + lp_setup.confirm(&participants[3], 4).assert_ok(); + + // Set block to winner selection + lp_setup + .b_mock + .set_block_nonce(WINNER_SELECTION_START_BLOCK); + + // Filter tickets and select winners + lp_setup.filter_tickets().assert_ok(); + lp_setup.select_winners().assert_ok(); + lp_setup.distribute_tickets().assert_ok(); + + lp_setup + .b_mock + .execute_query(&lp_setup.lp_wrapper, |sc| { + assert_eq!(sc.nr_winning_tickets().get(), nr_winning_tickets); + }) + .assert_ok(); + + // Set block to claim period + lp_setup.b_mock.set_block_nonce(CLAIM_START_BLOCK); + + // Claim for all users + for participant in participants.iter().take(4) { + lp_setup.claim_user(participant).assert_ok(); + } + + // Check balances + // First user: 1 ticket, no guarantee, no winning tickets + let winning_tickets_user1 = 0u64; + lp_setup + .b_mock + .check_egld_balance(&participants[0], &rust_biguint!(3 * TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[0], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user1 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Second user: 2 tickets, no guarantee, 2 winning tickets + let winning_tickets_user2 = 2; + lp_setup + .b_mock + .check_egld_balance(&participants[1], &rust_biguint!(TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[1], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user2 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Third user: 3 tickets, 1 guaranteed, 3 winning ticket + let winning_tickets_user3 = 3; + lp_setup + .b_mock + .check_egld_balance(&participants[2], &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + &participants[2], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user3 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // New participant: 4 tickets, 5 guaranteed, 4 winning ticket + let winning_tickets_user4 = 4; + lp_setup + .b_mock + .check_egld_balance(&participants[3], &rust_biguint!(2 * TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[3], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user4 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Check correct nr_winning_tickets + // Does not work without ticket permutation + assert_eq!( + nr_winning_tickets as u64, + winning_tickets_user1 + + winning_tickets_user2 + + winning_tickets_user3 + + winning_tickets_user4 + ); + + // Owner claims + lp_setup.claim_owner().assert_ok(); + + // Check owner's balances + let expected_owner_egld = TICKET_COST * nr_winning_tickets as u64; // 9 winning tickets + + lp_setup + .b_mock + .check_egld_balance(&lp_setup.owner_address, &rust_biguint!(expected_owner_egld)); + lp_setup.b_mock.check_esdt_balance( + &lp_setup.owner_address, + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), // All tokens were distributed + ); + + // Check contract balances + lp_setup + .b_mock + .check_egld_balance(lp_setup.lp_wrapper.address_ref(), &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + lp_setup.lp_wrapper.address_ref(), + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), + ); +} + +#[test] +fn no_unlock_schedule_test() { + let nr_winning_tickets = 6; + let mut lp_setup = LaunchpadSetup::new( + nr_winning_tickets, + launchpad_guaranteed_tickets_v2::contract_obj, + ); + + // Do not set unlock schedule: 100% release immediately + + let participants = lp_setup.participants.clone(); + + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK - 1); + + // Confirm tickets + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK); + lp_setup.confirm(&participants[0], 1).assert_ok(); + lp_setup.confirm(&participants[1], 2).assert_ok(); + lp_setup.confirm(&participants[2], 3).assert_ok(); + + // Set block to winner selection + lp_setup + .b_mock + .set_block_nonce(WINNER_SELECTION_START_BLOCK); + + // Filter tickets and select winners + lp_setup.filter_tickets().assert_ok(); + lp_setup.select_winners().assert_ok(); + lp_setup.distribute_tickets().assert_ok(); + + lp_setup + .b_mock + .execute_query(&lp_setup.lp_wrapper, |sc| { + assert_eq!(sc.nr_winning_tickets().get(), nr_winning_tickets); + }) + .assert_ok(); + + // Set block to claim period + lp_setup.b_mock.set_block_nonce(CLAIM_START_BLOCK); + + // Claim for all users + for participant in participants.iter().take(3) { + lp_setup.claim_user(participant).assert_ok(); + } + + // Check balances + // First user: 1 ticket, no guarantee, 1 winning ticket + let winning_tickets_user1 = 1; + lp_setup + .b_mock + .check_egld_balance(&participants[0], &rust_biguint!(2 * TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[0], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user1 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Second user: 2 tickets, no guarantee, 2 winning tickets + let winning_tickets_user2 = 2; + lp_setup + .b_mock + .check_egld_balance(&participants[1], &rust_biguint!(TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[1], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user2 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Third user: 3 tickets, 1 guaranteed, 3 winning ticket + let winning_tickets_user3 = 3; + lp_setup + .b_mock + .check_egld_balance(&participants[2], &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + &participants[2], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user3 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Check correct nr_winning_tickets + // Does not work without ticket permutation + assert_eq!( + nr_winning_tickets as u64, + winning_tickets_user1 + winning_tickets_user2 + winning_tickets_user3 + ); + + // Owner claims + lp_setup.claim_owner().assert_ok(); + + // Check owner's balances + let expected_owner_egld = TICKET_COST * nr_winning_tickets as u64; // 9 winning tickets + + lp_setup + .b_mock + .check_egld_balance(&lp_setup.owner_address, &rust_biguint!(expected_owner_egld)); + lp_setup.b_mock.check_esdt_balance( + &lp_setup.owner_address, + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), // All tokens were distributed + ); + + // Check contract balances + lp_setup + .b_mock + .check_egld_balance(lp_setup.lp_wrapper.address_ref(), &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + lp_setup.lp_wrapper.address_ref(), + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), + ); +} + +#[test] +fn no_participants_test() { + let nr_winning_tickets = 6; + let mut lp_setup = LaunchpadSetup::new( + nr_winning_tickets, + launchpad_guaranteed_tickets_v2::contract_obj, + ); + + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK - 1); + + // Confirm tickets + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK); + // No participant confirms + + // Set block to winner selection + lp_setup + .b_mock + .set_block_nonce(WINNER_SELECTION_START_BLOCK); + + // Filter tickets and select winners + lp_setup.filter_tickets().assert_ok(); + lp_setup.select_winners().assert_ok(); + lp_setup.distribute_tickets().assert_ok(); + + // Set block to claim period + lp_setup.b_mock.set_block_nonce(CLAIM_START_BLOCK); + + // Owner claims + lp_setup.claim_owner().assert_ok(); + + // Check owner's balances + let nr_confirmed_tickets = 0u64; + let expected_owner_egld = TICKET_COST * nr_confirmed_tickets; // 0 + + lp_setup + .b_mock + .check_egld_balance(&lp_setup.owner_address, &rust_biguint!(expected_owner_egld)); + lp_setup.b_mock.check_esdt_balance( + &lp_setup.owner_address, + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(nr_winning_tickets as u64 * LAUNCHPAD_TOKENS_PER_TICKET), // All tokens were refunded to the owner + ); + + // Check contract balances + lp_setup + .b_mock + .check_egld_balance(lp_setup.lp_wrapper.address_ref(), &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + lp_setup.lp_wrapper.address_ref(), + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), + ); +} From d587001e28563f0ed42e68331d6acda75d17676d Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 22 Nov 2024 03:15:01 +0000 Subject: [PATCH 2/9] added MAX_TICKETS_ALLOWANCE check --- .../src/guaranteed_tickets_init.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs index 7594332..4e478a3 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs @@ -1,6 +1,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +pub const MAX_TICKETS_ALLOWANCE: usize = 128; pub const MAX_GUARANTEED_TICKETS_ENTRIES: usize = 10; #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, ManagedVecItem)] @@ -55,6 +56,10 @@ pub trait GuaranteedTicketsInitModule: for multi_arg in address_number_pairs.into_iter() { let (buyer, total_tickets_allowance, guaranteed_ticket_raw) = multi_arg.into_tuple(); + require!( + total_tickets_allowance <= MAX_TICKETS_ALLOWANCE, + "Total number of tickets exceeds maximum allowed" + ); require!( guaranteed_ticket_raw.len() <= MAX_GUARANTEED_TICKETS_ENTRIES, "Number of guaranteed tickets entries exceeds maximum allowed" From 08af8758821ef0e9e0292292e0eb652a7ba7f18e Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 22 Nov 2024 09:16:05 +0000 Subject: [PATCH 3/9] Added user accounts only check --- .../src/guaranteed_tickets_init.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs index 4e478a3..cef5f97 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs @@ -56,6 +56,10 @@ pub trait GuaranteedTicketsInitModule: for multi_arg in address_number_pairs.into_iter() { let (buyer, total_tickets_allowance, guaranteed_ticket_raw) = multi_arg.into_tuple(); + require!( + !self.blockchain().is_smart_contract(&buyer), + "Only user accounts can participate" + ); require!( total_tickets_allowance <= MAX_TICKETS_ALLOWANCE, "Total number of tickets exceeds maximum allowed" From d7a992f115b49795a1bf4969a000c625dbc95f2a Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 22 Nov 2024 11:54:10 +0000 Subject: [PATCH 4/9] updated MAX_TICKETS_ALLOWANCE value --- .../src/guaranteed_tickets_init.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs index cef5f97..86385e9 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs @@ -1,7 +1,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -pub const MAX_TICKETS_ALLOWANCE: usize = 128; +pub const MAX_TICKETS_ALLOWANCE: usize = 255; pub const MAX_GUARANTEED_TICKETS_ENTRIES: usize = 10; #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, ManagedVecItem)] @@ -153,9 +153,11 @@ pub trait GuaranteedTicketsInitModule: let mut whitelist = self.users_with_guaranteed_ticket(); for user in users { let user_ticket_status_mapper = self.user_ticket_status(&user); - if !user_ticket_status_mapper.is_empty() - || self.ticket_range_for_address(&user).is_empty() - { + if !user_ticket_status_mapper.is_empty() { + continue; + } + + if self.ticket_range_for_address(&user).is_empty() { continue; } From 492b05325938a6ac1f3a4718501dc6e2490e547d Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 22 Nov 2024 12:24:17 +0000 Subject: [PATCH 5/9] Blacklist mechanism extensive unit test --- .../tests/guaranteed_tickets_test.rs | 143 ++++++++++++++++-- 1 file changed, 129 insertions(+), 14 deletions(-) diff --git a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs index 59519b4..7b5cb19 100644 --- a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs +++ b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs @@ -1187,17 +1187,17 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { let new_participant = lp_setup .b_mock - .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64 * 2)); + .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64)); participants.push(new_participant.clone()); let new_participant2 = lp_setup .b_mock - .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64 * 2)); - participants.push(new_participant.clone()); + .create_user_account(&rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64)); + participants.push(new_participant2.clone()); lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK - 1); - // Set guaranteed tickets for new_participant + // Set guaranteed tickets for new_participant2 lp_setup .b_mock .execute_tx( @@ -1206,13 +1206,12 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { &rust_biguint!(0), |sc| { let mut args = MultiValueEncoded::new(); - // New participant with complex structure let mut guaranteed_tickets_info = MultiValueEncodedCounted::new(); guaranteed_tickets_info.push((3, 3).into()); args.push( ( - managed_address!(&new_participant), + managed_address!(&new_participant2), 6, guaranteed_tickets_info, ) @@ -1224,7 +1223,7 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { ) .assert_ok(); - // Blacklist new_participant + // Blacklist new_participant2 lp_setup .b_mock .execute_tx( @@ -1233,14 +1232,14 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { &rust_biguint!(0), |sc| { let mut blacklist = MultiValueEncoded::new(); - blacklist.push(managed_address!(&new_participant)); + blacklist.push(managed_address!(&new_participant2)); blacklist.push(managed_address!(&participants[0])); sc.add_users_to_blacklist_endpoint(blacklist); }, ) .assert_ok(); - // Set guaranteed tickets for new_participant2 + // Set guaranteed tickets for new_participant lp_setup .b_mock .execute_tx( @@ -1249,13 +1248,12 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { &rust_biguint!(0), |sc| { let mut args = MultiValueEncoded::new(); - // New participant with complex structure let mut guaranteed_tickets_info = MultiValueEncodedCounted::new(); guaranteed_tickets_info.push((3, 3).into()); args.push( ( - managed_address!(&new_participant2), + managed_address!(&new_participant), 6, guaranteed_tickets_info, ) @@ -1282,7 +1280,7 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { ) .assert_ok(); - // Try remove new_participant from blacklist + // Try remove new_participant2 from blacklist // Should throw an error lp_setup .b_mock @@ -1292,11 +1290,128 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { &rust_biguint!(0), |sc| { let mut blacklist = MultiValueEncoded::new(); - blacklist.push(managed_address!(&new_participant)); + blacklist.push(managed_address!(&new_participant2)); sc.remove_guaranteed_users_from_blacklist_endpoint(blacklist); }, ) .assert_error(4, "Number of winning tickets exceeded"); + + // Confirm tickets + lp_setup.b_mock.set_block_nonce(CONFIRM_START_BLOCK); + lp_setup.confirm(&participants[0], 1).assert_ok(); + lp_setup.confirm(&participants[1], 2).assert_ok(); + lp_setup.confirm(&participants[2], 3).assert_ok(); + lp_setup.confirm(&participants[3], 3).assert_ok(); + + lp_setup.confirm(&participants[4], 3).assert_error( + 4, + "You have been put into the blacklist and may not confirm tickets", + ); + + // Set block to winner selection + lp_setup + .b_mock + .set_block_nonce(WINNER_SELECTION_START_BLOCK); + + // Filter tickets and select winners + lp_setup.filter_tickets().assert_ok(); + lp_setup.select_winners().assert_ok(); + lp_setup.distribute_tickets().assert_ok(); + + lp_setup + .b_mock + .execute_query(&lp_setup.lp_wrapper, |sc| { + assert_eq!(sc.nr_winning_tickets().get(), nr_winning_tickets); + }) + .assert_ok(); + + // Set block to claim period + lp_setup.b_mock.set_block_nonce(CLAIM_START_BLOCK); + + // Claim for all users + for participant in participants.iter().take(4) { + lp_setup.claim_user(participant).assert_ok(); + } + + // Check balances + // First user: 1 winning ticket + let winning_tickets_user1 = 1; + lp_setup + .b_mock + .check_egld_balance(&participants[0], &rust_biguint!(2 * TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[0], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user1 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Second user: 1 winning ticket + let winning_tickets_user2 = 1; + lp_setup + .b_mock + .check_egld_balance(&participants[1], &rust_biguint!(2 * TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[1], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user2 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Third user: 1 winning ticket + let winning_tickets_user3 = 1; + lp_setup + .b_mock + .check_egld_balance(&participants[2], &rust_biguint!(2 * TICKET_COST)); + lp_setup.b_mock.check_esdt_balance( + &participants[2], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user3 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // New participant: 3 winning tickets + let winning_tickets_user4 = 3; + lp_setup + .b_mock + .check_egld_balance(&participants[3], &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + &participants[3], + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(winning_tickets_user4 * LAUNCHPAD_TOKENS_PER_TICKET), + ); + + // Check correct nr_winning_tickets + // Does not work without ticket permutation + assert_eq!( + nr_winning_tickets as u64, + winning_tickets_user1 + + winning_tickets_user2 + + winning_tickets_user3 + + winning_tickets_user4 + ); + + // Owner claims + lp_setup.claim_owner().assert_ok(); + + // Check owner's balances + let expected_owner_egld = TICKET_COST * nr_winning_tickets as u64; // 6 winning tickets + + lp_setup + .b_mock + .check_egld_balance(&lp_setup.owner_address, &rust_biguint!(expected_owner_egld)); + lp_setup.b_mock.check_esdt_balance( + &lp_setup.owner_address, + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), // All tokens were distributed + ); + + // Check contract balances + lp_setup + .b_mock + .check_egld_balance(lp_setup.lp_wrapper.address_ref(), &rust_biguint!(0)); + lp_setup.b_mock.check_esdt_balance( + lp_setup.lp_wrapper.address_ref(), + LAUNCHPAD_TOKEN_ID, + &rust_biguint!(0), + ); } #[test] @@ -2077,7 +2192,7 @@ fn no_unlock_schedule_test() { lp_setup.claim_owner().assert_ok(); // Check owner's balances - let expected_owner_egld = TICKET_COST * nr_winning_tickets as u64; // 9 winning tickets + let expected_owner_egld = TICKET_COST * nr_winning_tickets as u64; // 6 winning tickets lp_setup .b_mock From d5b06671e3a348f5608a7d28778276ee135b611d Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 22 Nov 2024 12:47:43 +0000 Subject: [PATCH 6/9] fix remove from blacklist checks --- .../src/guaranteed_tickets_init.rs | 17 ++++++----------- .../tests/guaranteed_tickets_test.rs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs index 86385e9..0ceb4d2 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs @@ -152,19 +152,13 @@ pub trait GuaranteedTicketsInitModule: let mut total_guaranteed_tickets = self.total_guaranteed_tickets().get(); let mut whitelist = self.users_with_guaranteed_ticket(); for user in users { - let user_ticket_status_mapper = self.user_ticket_status(&user); - if !user_ticket_status_mapper.is_empty() { - continue; - } - - if self.ticket_range_for_address(&user).is_empty() { - continue; - } - let blacklist_user_ticket_status_mapper = self.blacklist_user_ticket_status(&user); - if blacklist_user_ticket_status_mapper.is_empty() { + if blacklist_user_ticket_status_mapper.is_empty() + || self.ticket_range_for_address(&user).is_empty() + { continue; } + let blacklist_user_ticket_status = blacklist_user_ticket_status_mapper.take(); let guaranteed_tickets_added = blacklist_user_ticket_status .guaranteed_tickets_info @@ -179,7 +173,8 @@ pub trait GuaranteedTicketsInitModule: whitelist.insert(user.clone()); nr_winning_tickets -= guaranteed_tickets_added; total_guaranteed_tickets += guaranteed_tickets_added; - user_ticket_status_mapper.set(blacklist_user_ticket_status); + self.user_ticket_status(&user) + .set(blacklist_user_ticket_status); } } diff --git a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs index 7b5cb19..01bca6a 100644 --- a/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs +++ b/launchpad-guaranteed-tickets-v2/tests/guaranteed_tickets_test.rs @@ -1232,8 +1232,8 @@ fn remove_from_blacklist_underflow_and_empty_storage_test() { &rust_biguint!(0), |sc| { let mut blacklist = MultiValueEncoded::new(); - blacklist.push(managed_address!(&new_participant2)); blacklist.push(managed_address!(&participants[0])); + blacklist.push(managed_address!(&new_participant2)); sc.add_users_to_blacklist_endpoint(blacklist); }, ) From ab7ee57f72cee6badedb9f2a9e608d717cfe9b43 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Mon, 25 Nov 2024 12:00:30 +0200 Subject: [PATCH 7/9] blacklist storage logic optimization --- .../src/guaranteed_tickets_init.rs | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs index 0ceb4d2..be368ee 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs @@ -127,19 +127,17 @@ pub trait GuaranteedTicketsInitModule: let mut nr_winning_tickets = self.nr_winning_tickets().get(); let mut total_guaranteed_tickets = self.total_guaranteed_tickets().get(); for user in users { - let was_whitelisted = whitelist.swap_remove(&user); - if was_whitelisted { - let user_ticket_status = self.user_ticket_status(&user).take(); - let guaranteed_tickets_recovered = user_ticket_status - .guaranteed_tickets_info - .iter() - .fold(0, |acc, info| acc + info.guaranteed_tickets); - - nr_winning_tickets += guaranteed_tickets_recovered; - total_guaranteed_tickets -= guaranteed_tickets_recovered; - self.blacklist_user_ticket_status(&user) - .set(user_ticket_status); - } + let _ = whitelist.swap_remove(&user); + let user_ticket_status = self.user_ticket_status(&user).take(); + let guaranteed_tickets_recovered = user_ticket_status + .guaranteed_tickets_info + .iter() + .fold(0, |acc, info| acc + info.guaranteed_tickets); + + nr_winning_tickets += guaranteed_tickets_recovered; + total_guaranteed_tickets -= guaranteed_tickets_recovered; + self.blacklist_user_ticket_status(&user) + .set(user_ticket_status); } self.nr_winning_tickets().set(nr_winning_tickets); @@ -152,14 +150,11 @@ pub trait GuaranteedTicketsInitModule: let mut total_guaranteed_tickets = self.total_guaranteed_tickets().get(); let mut whitelist = self.users_with_guaranteed_ticket(); for user in users { - let blacklist_user_ticket_status_mapper = self.blacklist_user_ticket_status(&user); - if blacklist_user_ticket_status_mapper.is_empty() - || self.ticket_range_for_address(&user).is_empty() - { + if self.ticket_range_for_address(&user).is_empty() { continue; } - let blacklist_user_ticket_status = blacklist_user_ticket_status_mapper.take(); + let blacklist_user_ticket_status = self.blacklist_user_ticket_status(&user).take(); let guaranteed_tickets_added = blacklist_user_ticket_status .guaranteed_tickets_info .iter() @@ -173,9 +168,10 @@ pub trait GuaranteedTicketsInitModule: whitelist.insert(user.clone()); nr_winning_tickets -= guaranteed_tickets_added; total_guaranteed_tickets += guaranteed_tickets_added; - self.user_ticket_status(&user) - .set(blacklist_user_ticket_status); } + + self.user_ticket_status(&user) + .set(blacklist_user_ticket_status); } self.nr_winning_tickets().set(nr_winning_tickets); From 9345234e683dcb96cc8ee2caebc660796641df86 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Tue, 26 Nov 2024 10:19:57 +0200 Subject: [PATCH 8/9] remove unneeded comments --- launchpad-guaranteed-tickets-v2/src/guaranteed_ticket_winners.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_ticket_winners.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_ticket_winners.rs index 01fc3ad..038c0bf 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_ticket_winners.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_ticket_winners.rs @@ -160,7 +160,6 @@ pub trait GuaranteedTicketWinnersModule: op.leftover_tickets += remaining_tickets; } - // TODO - add a check if current_ticket_pos > last_ticket_pos fn distribute_leftover_tickets( &self, op: &mut GuaranteedTicketsSelectionOperation, From ddf0836868b054e6de30dc3bef937cafe6d438c0 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Tue, 26 Nov 2024 10:47:46 +0200 Subject: [PATCH 9/9] addTickets extra check --- .../src/guaranteed_tickets_init.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs index be368ee..446274d 100644 --- a/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs +++ b/launchpad-guaranteed-tickets-v2/src/guaranteed_tickets_init.rs @@ -56,6 +56,10 @@ pub trait GuaranteedTicketsInitModule: for multi_arg in address_number_pairs.into_iter() { let (buyer, total_tickets_allowance, guaranteed_ticket_raw) = multi_arg.into_tuple(); + if total_tickets_allowance == 0 { + continue; + } + require!( !self.blockchain().is_smart_contract(&buyer), "Only user accounts can participate"