From 8da808d581d925f0a7d8249a42507daaa5195c68 Mon Sep 17 00:00:00 2001 From: loothero Date: Wed, 12 Nov 2025 07:58:09 -0800 Subject: [PATCH 1/2] dedicated premint endpoint --- src/contract.cairo | 41 ++-- src/interfaces.cairo | 3 + tests/helper.cairo | 8 - tests/test_contract.cairo | 476 ++++++++++++++++++++------------------ 4 files changed, 271 insertions(+), 257 deletions(-) diff --git a/src/contract.cairo b/src/contract.cairo index 1c53a80..daca47f 100644 --- a/src/contract.cairo +++ b/src/contract.cairo @@ -106,8 +106,6 @@ pub mod TicketMaster { /// @param issuance_reduction_bips The issuance reduction expressed in basis points (out of /// 10,000) /// @param treasury_address The address of the treasury - /// @param recipients The addresses of the recipients of the tokens - /// @param amounts The amounts of tokens to mint to the recipients /// @param distribution_end_time The end time of the token distribution /// @param buyback_order_config The configuration for the buyback orders #[constructor] @@ -131,8 +129,6 @@ pub mod TicketMaster { issuance_reduction_price_duration: u64, issuance_reduction_bips: u128, treasury_address: ContractAddress, - recipients: Array, - amounts: Array, distribution_end_time: u64, buyback_order_config: BuybackOrderConfig, ) { @@ -150,11 +146,6 @@ pub mod TicketMaster { assert(treasury_address != zero_address, 'Invalid treasury address'); assert(total_supply > 0, 'Invalid total supply'); - // Validate recipients and amounts arrays - let recipients_len = recipients.len(); - let amounts_len = amounts.len(); - assert(recipients_len == amounts_len, 'Arrays length mismatch'); - let current_time = starknet::get_block_timestamp(); assert!(distribution_end_time > current_time, "End time must be greater than now"); @@ -191,22 +182,33 @@ pub mod TicketMaster { self.distribution_end_time.write(distribution_end_time); self.buyback_order_config.write(buyback_order_config); - // Distribute tokens to initial recipients - let total_distributed = _distribute_initial_tokens( - ref self, recipients, amounts, total_supply, zero_address, - ); - - let remaining_supply = total_supply.into() - total_distributed; - // register token with Ekubo registry _register_token(ref self); // Store tokens remaining for distribution - self.tokens_for_distribution.write(remaining_supply - ERC20_UNIT.into()); + self.tokens_for_distribution.write(total_supply.into()); } #[abi(embed_v0)] impl TicketMasterImpl of ITicketMaster { + fn premint_tokens( + ref self: ContractState, recipients: Array, amounts: Array, + ) { + self.ownable.assert_only_owner(); + assert(self.deployment_state.read() == 0, 'Deployment state is not 0'); + + let token_available = self.tokens_for_distribution.read(); + + // Distribute tokens to initial recipients + let total_distributed = _distribute_initial_tokens( + ref self, recipients, amounts, token_available.try_into().unwrap(), + ); + + let remaining_supply = token_available - total_distributed; + + self.tokens_for_distribution.write(remaining_supply); + } + /// @notice Initializes the TWAMM pools for the distribution and buyback tokens /// @dev This function should be called as step 1 after deployment /// @param distribution_initial_tick The initial tick for the distribution pool @@ -1226,14 +1228,12 @@ pub mod TicketMaster { /// @param recipients Array of recipient addresses /// @param amounts Array of token amounts to distribute /// @param total_supply Total supply of tokens - /// @param zero_address The zero address for validation /// @return Total amount of tokens distributed fn _distribute_initial_tokens( ref self: ContractState, recipients: Array, amounts: Array, total_supply: u128, - zero_address: ContractAddress, ) -> u256 { let recipients_len = recipients.len(); let mut total_distributed: u256 = 0; @@ -1244,9 +1244,6 @@ pub mod TicketMaster { let recipient = *recipients.at(i); let amount = *amounts.at(i); - // Ensure recipient is not zero address - assert(recipient != zero_address, 'Invalid recipient address'); - // Mint tokens directly to recipient self.erc20.mint(recipient, amount); diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 5e2745c..c05bef6 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -10,6 +10,9 @@ use starknet::ContractAddress; #[starknet::interface] pub trait ITicketMaster { fn init_distribution_pool(ref self: TContractState, distribution_initial_tick: i129) -> u256; + fn premint_tokens( + ref self: TContractState, recipients: Array, amounts: Array, + ); fn provide_initial_liquidity( ref self: TContractState, payment_token_amount: u128, diff --git a/tests/helper.cairo b/tests/helper.cairo index 39de100..4fd8192 100644 --- a/tests/helper.cairo +++ b/tests/helper.cairo @@ -130,8 +130,6 @@ pub fn ticket_master_calldata_custom( issuance_reduction_price_duration: u64, issuance_reduction_bips: u128, treasury_address: ContractAddress, - recipients: Array, - amounts: Array, distribution_end_time: u64, buyback_order_config: BuybackOrderConfig, ) -> Array { @@ -155,8 +153,6 @@ pub fn ticket_master_calldata_custom( issuance_reduction_price_duration.serialize(ref calldata); issuance_reduction_bips.serialize(ref calldata); treasury_address.serialize(ref calldata); - recipients.serialize(ref calldata); - amounts.serialize(ref calldata); distribution_end_time.serialize(ref calldata); buyback_order_config.serialize(ref calldata); @@ -183,8 +179,6 @@ pub fn ticket_master_calldata( let symbol: ByteArray = "BDT"; let total_supply: u128 = DUNGEON_TICKET_SUPPLY; let distribution_pool_fee: u128 = DISTRIBUTION_POOL_FEE_BPS; - let recipients: Array = array![]; - let amounts: Array = array![]; let distribution_end_time: u64 = DISTRIBUTION_END_TIME; let buyback_order_config = BUYBACK_ORDER_CONFIG; @@ -207,8 +201,6 @@ pub fn ticket_master_calldata( issuance_reduction_price_duration, issuance_reduction_bips, treasury_address, - recipients, - amounts, distribution_end_time, buyback_order_config, ) diff --git a/tests/test_contract.cairo b/tests/test_contract.cairo index 8978a43..3c09747 100644 --- a/tests/test_contract.cairo +++ b/tests/test_contract.cairo @@ -50,15 +50,6 @@ fn constructor_sets_initial_state() { let ticket_master_class = declare_class("TicketMaster"); - let alice: ContractAddress = 'alice'.try_into().unwrap(); - let bob: ContractAddress = 'bob'.try_into().unwrap(); - - let amount_alice: u128 = 1_000_000_000_000_000_000; - let amount_bob: u128 = 2_000_000_000_000_000_000; - - let recipients = array![alice, bob]; - let amounts = array![amount_alice.into(), amount_bob.into()]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -78,8 +69,6 @@ fn constructor_sets_initial_state() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -90,6 +79,15 @@ fn constructor_sets_initial_state() { }; let ticket_token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; + // Call premint_tokens after deployment + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + let recipient: ContractAddress = 'recipient'.try_into().unwrap(); + let recipients: Array = array![recipient]; + let premint_amount: u256 = 1000000_u256; + let amounts: Array = array![premint_amount]; + ticket_master_dispatcher.premint_tokens(recipients, amounts); + stop_cheat_caller_address(ticket_master_dispatcher.contract_address); + assert!( ticket_master_dispatcher.get_payment_token() == payment_token_address, "payment token mismatch", @@ -152,74 +150,156 @@ fn constructor_sets_initial_state() { assert!(ticket_master_dispatcher.get_buyback_rate() == 0, "buyback rate should start at 0"); let registry_token_amount: u256 = 1000000000000000000_u128.into(); - let distributed_to_recipients: u256 = amount_alice.into() + amount_bob.into(); - let expected_minted_supply = distributed_to_recipients + registry_token_amount; - assert!( - ticket_token_dispatcher.total_supply() == expected_minted_supply, - "unexpected total supply after constructor", - ); - assert!( - ticket_token_dispatcher.balance_of(alice) == amount_alice.into(), - "alice should receive requested amount", - ); - assert!( - ticket_token_dispatcher.balance_of(bob) == amount_bob.into(), - "bob should receive requested amount", - ); assert!( ticket_token_dispatcher.balance_of(MOCK_REGISTRY_ADDRESS) == registry_token_amount, "registry should receive single token", ); - let total_supply_u256: u256 = DUNGEON_TICKET_SUPPLY.into(); - let expected_tokens_for_distribution = total_supply_u256 - expected_minted_supply; + let expected_tokens_for_distribution = DUNGEON_TICKET_SUPPLY.into() - premint_amount; assert!( ticket_master_dispatcher.get_tokens_for_distribution() == expected_tokens_for_distribution, - "tokens_for_distribution mismatch", + "tokens_for_distribution mismatch. Expected: {}, Actual: {}", + expected_tokens_for_distribution, + ticket_master_dispatcher.get_tokens_for_distribution(), ); } #[test] -#[fuzzer] -fn test_distribute_initial_tokens_multiple_recipients(delta_a: u64, delta_b: u64, delta_c: u64) { +fn premint_tokens_distributes_correctly() { start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); let erc20_class = declare_class("ERC20Mock"); - let payment_token_address = deploy( erc20_class, token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), ); + let reward_token_address = deploy( + erc20_class, + token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), + ); + let ticket_master_class = declare_class("TicketMaster"); + let calldata = ticket_master_calldata_custom( + DEPLOYER_ADDRESS, + "Beasts Dungeon Ticket", + "BDT", + DUNGEON_TICKET_SUPPLY, + DISTRIBUTION_POOL_FEE_BPS, + payment_token_address, + reward_token_address, + MOCK_CORE_ADDRESS, + MOCK_POSITIONS_ADDRESS, + MOCK_POSITION_NFT_ADDRESS, + MOCK_TWAMM_EXTENSION_ADDRESS, + MOCK_REGISTRY_ADDRESS, + EKUBO_ORACLE_MAINNET, + MOCK_VELORDS_ADDRESS, + ISSUANCE_REDUCTION_PRICE_X128, + ISSUANCE_REDUCTION_PRICE_DURATION, + ISSUANCE_REDUCTION_BIPS, + MOCK_TREASURY, + DISTRIBUTION_END_TIME, + BUYBACK_ORDER_CONFIG, + ); + + let ticket_master_address = deploy(ticket_master_class, calldata); + let ticket_master_dispatcher = ITicketMasterDispatcher { + contract_address: ticket_master_address, + }; + let ticket_token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; + + let alice: ContractAddress = 'alice_premint'.try_into().unwrap(); + let bob: ContractAddress = 'bob_premint'.try_into().unwrap(); + let amount_alice: u128 = 1_000_000_000_000_000_000; + let amount_bob: u128 = 2_000_000_000_000_000_000; + + let recipients = array![alice, bob]; + let amounts = array![amount_alice.into(), amount_bob.into()]; + + let tokens_before = ticket_master_dispatcher.get_tokens_for_distribution(); + + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + ticket_master_dispatcher.premint_tokens(recipients, amounts); + stop_cheat_caller_address(ticket_master_dispatcher.contract_address); + + assert_eq!(ticket_token_dispatcher.balance_of(alice), amount_alice.into()); + assert_eq!(ticket_token_dispatcher.balance_of(bob), amount_bob.into()); + + let tokens_after = ticket_master_dispatcher.get_tokens_for_distribution(); + let expected_remaining = tokens_before - amount_alice.into() - amount_bob.into(); + assert_eq!(tokens_after, expected_remaining); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn premint_tokens_rejects_non_owner() { + start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); + + let erc20_class = declare_class("ERC20Mock"); + let payment_token_address = deploy( + erc20_class, + token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), + ); let reward_token_address = deploy( erc20_class, token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), ); let ticket_master_class = declare_class("TicketMaster"); + let calldata = ticket_master_calldata_custom( + DEPLOYER_ADDRESS, + "Beasts Dungeon Ticket", + "BDT", + DUNGEON_TICKET_SUPPLY, + DISTRIBUTION_POOL_FEE_BPS, + payment_token_address, + reward_token_address, + MOCK_CORE_ADDRESS, + MOCK_POSITIONS_ADDRESS, + MOCK_POSITION_NFT_ADDRESS, + MOCK_TWAMM_EXTENSION_ADDRESS, + MOCK_REGISTRY_ADDRESS, + EKUBO_ORACLE_MAINNET, + MOCK_VELORDS_ADDRESS, + ISSUANCE_REDUCTION_PRICE_X128, + ISSUANCE_REDUCTION_PRICE_DURATION, + ISSUANCE_REDUCTION_BIPS, + MOCK_TREASURY, + DISTRIBUTION_END_TIME, + BUYBACK_ORDER_CONFIG, + ); - let alice: ContractAddress = 'alice_multi'.try_into().unwrap(); - let bob: ContractAddress = 'bob_multi'.try_into().unwrap(); - let carol: ContractAddress = 'carol_multi'.try_into().unwrap(); + let ticket_master_address = deploy(ticket_master_class, calldata); + let ticket_master_dispatcher = ITicketMasterDispatcher { + contract_address: ticket_master_address, + }; - let registry_token_amount: u128 = 1_000_000_000_000_000_000; - let base_alice: u128 = 1_000_000_000_000_000_000; - let base_bob: u128 = 2_000_000_000_000_000_000; - let base_carol: u128 = 3_000_000_000_000_000_000; + let alice: ContractAddress = 'alice'.try_into().unwrap(); + let recipients = array![alice]; + let amounts = array![1_000_u128.into()]; - let perturbation_bound: u128 = 1_000; - let amount_alice: u128 = base_alice + ((delta_a.into()) % perturbation_bound); - let amount_bob: u128 = base_bob + ((delta_b.into()) % perturbation_bound); - let amount_carol: u128 = base_carol + ((delta_c.into()) % perturbation_bound); + let non_owner: ContractAddress = 'non_owner'.try_into().unwrap(); + start_cheat_caller_address(ticket_master_dispatcher.contract_address, non_owner); + ticket_master_dispatcher.premint_tokens(recipients, amounts); +} - let recipients = array![alice, bob, carol]; - let amounts = array![amount_alice.into(), amount_bob.into(), amount_carol.into()]; +#[test] +#[should_panic(expected: ('Deployment state is not 0',))] +fn premint_tokens_rejects_after_state_transition() { + start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); + mock_ekubo_core(1_u256); - let total_recipient_mints: u128 = amount_alice + amount_bob + amount_carol; - if total_recipient_mints + registry_token_amount >= DUNGEON_TICKET_SUPPLY { - return; - } + let erc20_class = declare_class("ERC20Mock"); + let payment_token_address = deploy( + erc20_class, + token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), + ); + let reward_token_address = deploy( + erc20_class, + token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), + ); + let ticket_master_class = declare_class("TicketMaster"); let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -239,32 +319,90 @@ fn test_distribute_initial_tokens_multiple_recipients(delta_a: u64, delta_b: u64 ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); - let ticket_master_address = match ticket_master_class.deploy(@calldata) { - Result::Ok((address, _)) => address, - Result::Err(_) => { return; }, + let ticket_master_address = deploy(ticket_master_class, calldata); + let ticket_master_dispatcher = ITicketMasterDispatcher { + contract_address: ticket_master_address, }; + + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + + // Transition to state 1 + ticket_master_dispatcher.init_distribution_pool(DISTRIBUTION_INITIAL_TICK); + + // Try to premint after state transition + let alice: ContractAddress = 'alice'.try_into().unwrap(); + let recipients = array![alice]; + let amounts = array![1_000_u128.into()]; + + ticket_master_dispatcher.premint_tokens(recipients, amounts); +} + +#[test] +fn premint_tokens_can_be_called_multiple_times_in_state_0() { + start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); + + let erc20_class = declare_class("ERC20Mock"); + let payment_token_address = deploy( + erc20_class, + token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), + ); + let reward_token_address = deploy( + erc20_class, + token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), + ); + + let ticket_master_class = declare_class("TicketMaster"); + let calldata = ticket_master_calldata_custom( + DEPLOYER_ADDRESS, + "Beasts Dungeon Ticket", + "BDT", + DUNGEON_TICKET_SUPPLY, + DISTRIBUTION_POOL_FEE_BPS, + payment_token_address, + reward_token_address, + MOCK_CORE_ADDRESS, + MOCK_POSITIONS_ADDRESS, + MOCK_POSITION_NFT_ADDRESS, + MOCK_TWAMM_EXTENSION_ADDRESS, + MOCK_REGISTRY_ADDRESS, + EKUBO_ORACLE_MAINNET, + MOCK_VELORDS_ADDRESS, + ISSUANCE_REDUCTION_PRICE_X128, + ISSUANCE_REDUCTION_PRICE_DURATION, + ISSUANCE_REDUCTION_BIPS, + MOCK_TREASURY, + DISTRIBUTION_END_TIME, + BUYBACK_ORDER_CONFIG, + ); + + let ticket_master_address = deploy(ticket_master_class, calldata); let ticket_master_dispatcher = ITicketMasterDispatcher { contract_address: ticket_master_address, }; let ticket_token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; - assert_eq!(ticket_token_dispatcher.balance_of(alice), amount_alice.into()); - assert_eq!(ticket_token_dispatcher.balance_of(bob), amount_bob.into()); - assert_eq!(ticket_token_dispatcher.balance_of(carol), amount_carol.into()); + let alice: ContractAddress = 'alice_multi'.try_into().unwrap(); + let bob: ContractAddress = 'bob_multi'.try_into().unwrap(); + + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + + // First premint + ticket_master_dispatcher.premint_tokens(array![alice], array![1_000_u128.into()]); + assert_eq!(ticket_token_dispatcher.balance_of(alice), 1_000_u128.into()); - let distributed_total: u256 = amount_alice.into() - + amount_bob.into() - + amount_carol.into() - + registry_token_amount.into(); - let expected_remaining = DUNGEON_TICKET_SUPPLY.into() - distributed_total; + // Second premint + ticket_master_dispatcher.premint_tokens(array![bob], array![2_000_u128.into()]); + assert_eq!(ticket_token_dispatcher.balance_of(bob), 2_000_u128.into()); - assert_eq!(ticket_master_dispatcher.get_tokens_for_distribution(), expected_remaining); + stop_cheat_caller_address(ticket_master_dispatcher.contract_address); + + // Both should have their balances + assert_eq!(ticket_token_dispatcher.balance_of(alice), 1_000_u128.into()); + assert_eq!(ticket_token_dispatcher.balance_of(bob), 2_000_u128.into()); } #[test] @@ -284,12 +422,9 @@ fn burn_reduces_balance_and_total_supply() { let ticket_master_class = declare_class("TicketMaster"); let burner: ContractAddress = 'burner'.try_into().unwrap(); - let minted_amount: u128 = 10_u128 * ERC20_UNIT; + let minted_amount: u256 = 10 * ERC20_UNIT.into(); let burn_amount: u128 = 4_u128 * ERC20_UNIT; - let recipients = array![burner]; - let amounts = array![minted_amount.into()]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -309,8 +444,6 @@ fn burn_reduces_balance_and_total_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -319,6 +452,11 @@ fn burn_reduces_balance_and_total_supply() { let ticket_master_dispatcher = ITicketMasterDispatcher { contract_address: ticket_master_address, }; + + // set caller to OWNER + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + ticket_master_dispatcher.premint_tokens(array![burner], array![minted_amount.into()]); + let ticket_token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; let initial_balance = ticket_token_dispatcher.balance_of(burner); @@ -356,12 +494,9 @@ fn burn_from_reduces_balance_and_total_supply() { let burner: ContractAddress = 'burner'.try_into().unwrap(); let spender: ContractAddress = 'spender'.try_into().unwrap(); - let minted_amount: u128 = 10_u128 * ERC20_UNIT; + let minted_amount: u256 = 10 * ERC20_UNIT.into(); let burn_amount: u128 = 4_u128 * ERC20_UNIT; - let recipients = array![burner]; - let amounts = array![minted_amount.into()]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -381,8 +516,6 @@ fn burn_from_reduces_balance_and_total_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -391,6 +524,10 @@ fn burn_from_reduces_balance_and_total_supply() { let ticket_master_dispatcher = ITicketMasterDispatcher { contract_address: ticket_master_address, }; + // set caller to OWNER + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + ticket_master_dispatcher.premint_tokens(array![burner], array![minted_amount.into()]); + let ticket_token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; let initial_balance = ticket_token_dispatcher.balance_of(burner); @@ -436,7 +573,7 @@ fn burn_from_rejects_without_allowance() { let burner: ContractAddress = 'burner'.try_into().unwrap(); let spender: ContractAddress = 'spender'.try_into().unwrap(); - let minted_amount: u128 = 10_u128 * ERC20_UNIT; + let minted_amount: u256 = 10 * ERC20_UNIT.into(); let burn_amount: u128 = 4_u128 * ERC20_UNIT; let recipients = array![burner]; @@ -461,8 +598,6 @@ fn burn_from_rejects_without_allowance() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -471,6 +606,10 @@ fn burn_from_rejects_without_allowance() { let ticket_master_dispatcher = ITicketMasterDispatcher { contract_address: ticket_master_address, }; + // set caller to OWNER + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + ticket_master_dispatcher.premint_tokens(recipients, amounts); + let ticket_token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; assert_eq!(ticket_token_dispatcher.balance_of(burner), minted_amount.into()); @@ -499,56 +638,6 @@ fn fuzz_constructor_valid_configs( token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), ); - let recipient_count = (recipient_seed % 4) + 1; - let max_distributable: u128 = DUNGEON_TICKET_SUPPLY - 1_000_000_000_000_000_000; - let mut recipients: Array = array![]; - let mut amounts: Array = array![]; - let mut remaining: u128 = max_distributable; - let mut distributed: u128 = 0; - let amount_seed: u128 = amount_seed_low.into(); - - let offset = recipient_seed % 4; - - let mut index: u64 = 0; - loop { - if index == recipient_count { - break; - } - - let candidate_index = (offset + index) % 4; - let recipient = match candidate_index { - 0 => 'seed0'.try_into().unwrap(), - 1 => 'seed1'.try_into().unwrap(), - 2 => 'seed2'.try_into().unwrap(), - _ => 'seed3'.try_into().unwrap(), - }; - recipients.append(recipient); - - let recipients_left = recipient_count - index; - let recipients_left_u128: u128 = recipients_left.into(); - let max_for_this = if recipients_left_u128 == 0 { - 0 - } else { - remaining / recipients_left_u128 + 1 - }; - let index_u128: u128 = index.into(); - let raw = amount_seed + index_u128 * 7919_u128; - let chosen = if max_for_this == 0 { - 0 - } else { - raw % max_for_this - }; - amounts.append(chosen.into()); - distributed += chosen; - if chosen > remaining { - remaining = 0; - } else { - remaining -= chosen; - } - - index += 1; - } - let base_time = 1_000_u64; start_cheat_block_timestamp_global(base_time); @@ -578,8 +667,6 @@ fn fuzz_constructor_valid_configs( ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, end_time, BUYBACK_ORDER_CONFIG, ); @@ -595,26 +682,8 @@ fn fuzz_constructor_valid_configs( let ticket_master_dispatcher = ITicketMasterDispatcher { contract_address: ticket_master_address, }; - let token_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; assert_eq!(ticket_master_dispatcher.get_deployment_state(), 0_u8); - - let registry_token_amount: u128 = 1_000_000_000_000_000_000; - let expected_total_minted: u128 = distributed + registry_token_amount; - let expected_tokens_for_distribution: u128 = DUNGEON_TICKET_SUPPLY - expected_total_minted; - - if expected_tokens_for_distribution == 0 { - start_cheat_block_timestamp_global(0_u64); - return; - } - - let total_supply: u256 = token_dispatcher.total_supply(); - assert_eq!(total_supply, expected_total_minted.into()); - - let recorded_tokens_for_distribution = ticket_master_dispatcher.get_tokens_for_distribution(); - assert_eq!(recorded_tokens_for_distribution, expected_tokens_for_distribution.into()); - - start_cheat_block_timestamp_global(0_u64); } #[test] @@ -648,8 +717,6 @@ fn constructor_rejects_zero_payment_token() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -689,8 +756,6 @@ fn constructor_rejects_zero_reward_token() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -734,8 +799,6 @@ fn constructor_rejects_zero_core_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -779,8 +842,6 @@ fn constructor_rejects_zero_positions_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -824,8 +885,6 @@ fn constructor_rejects_zero_position_nft_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -869,8 +928,6 @@ fn constructor_rejects_zero_extension_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -912,8 +969,6 @@ fn constructor_rejects_zero_registry_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -957,8 +1012,6 @@ fn constructor_rejects_zero_oracle_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1002,8 +1055,6 @@ fn constructor_rejects_zero_velords_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1047,8 +1098,6 @@ fn constructor_rejects_zero_treasury_address() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, ZERO_ADDRESS, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1073,10 +1122,6 @@ fn constructor_rejects_mismatched_lengths() { let ticket_master_class = declare_class("TicketMaster"); - let recipient: ContractAddress = 'alice'.try_into().unwrap(); - let recipients = array![recipient]; - let empty_amounts: Array = array![]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -1096,8 +1141,6 @@ fn constructor_rejects_mismatched_lengths() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - empty_amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1143,8 +1186,6 @@ fn constructor_rejects_end_before_now() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], invalid_end_time, BUYBACK_ORDER_CONFIG, ); @@ -1202,8 +1243,6 @@ fn constructor_rejects_end_past_max_duration() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], invalid_end_time, BUYBACK_ORDER_CONFIG, ); @@ -1228,9 +1267,6 @@ fn constructor_rejects_zero_recipient() { let ticket_master_class = declare_class("TicketMaster"); - let recipients = array![ZERO_ADDRESS]; - let amounts = array![1_000_u128.into()]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -1250,8 +1286,6 @@ fn constructor_rejects_zero_recipient() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1276,11 +1310,6 @@ fn constructor_rejects_distribution_exceeding_supply() { let ticket_master_class = declare_class("TicketMaster"); - let recipient: ContractAddress = 'alice'.try_into().unwrap(); - let recipients = array![recipient]; - let excessive_amount: u256 = DUNGEON_TICKET_SUPPLY.into() + 1_u128.into(); - let amounts = array![excessive_amount]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -1300,8 +1329,6 @@ fn constructor_rejects_distribution_exceeding_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1345,8 +1372,6 @@ fn constructor_rejects_zero_total_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1390,8 +1415,6 @@ fn constructor_rejects_reduction_bips_at_limit() { ISSUANCE_REDUCTION_PRICE_DURATION, BIPS_BASIS, // issuance_reduction_bips at limit (should reject) MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1435,8 +1458,6 @@ fn constructor_accepts_reduction_bips_boundary() { ISSUANCE_REDUCTION_PRICE_DURATION, BIPS_BASIS - 1, // issuance_reduction_bips at boundary (should accept) MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1853,8 +1874,6 @@ fn test_provide_initial_liquidity_consumes_stored_config( ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -1931,8 +1950,6 @@ fn test_provide_initial_liquidity_updates_tokens_for_distribution() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![], - array![], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -5248,9 +5265,6 @@ fn burn_reduces_total_supply() { let ticket_master_class = declare_class("TicketMaster"); // Create a deployment with initial distribution to deployer so they have tokens to burn - let recipients: Array = array![DEPLOYER_ADDRESS]; - let amounts: Array = array![1000000_u256]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -5270,8 +5284,6 @@ fn burn_reduces_total_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -5283,6 +5295,10 @@ fn burn_reduces_total_supply() { let erc20_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; start_cheat_caller_address(ticket_master_address, DEPLOYER_ADDRESS); + let recipients: Array = array![DEPLOYER_ADDRESS]; + let amounts: Array = array![1000000_u256]; + ticket_master_dispatcher.premint_tokens(recipients, amounts); + stop_cheat_caller_address(ticket_master_address); let initial_supply = erc20_dispatcher.total_supply(); let burn_amount = 500000_u256; @@ -5339,10 +5355,6 @@ fn burn_from_reduces_target_balance() { let ticket_master_class = declare_class("TicketMaster"); - let recipient: ContractAddress = 'recipient'.try_into().unwrap(); - let recipients: Array = array![recipient]; - let amounts: Array = array![1000000_u256]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -5362,8 +5374,6 @@ fn burn_from_reduces_target_balance() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -5374,6 +5384,14 @@ fn burn_from_reduces_target_balance() { }; let erc20_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; + // change to OWNER + start_cheat_caller_address(ticket_master_address, DEPLOYER_ADDRESS); + let recipient: ContractAddress = 'recipient'.try_into().unwrap(); + let recipients: Array = array![recipient]; + let amounts: Array = array![1000000_u256]; + ticket_master_dispatcher.premint_tokens(recipients, amounts); + stop_cheat_caller_address(ticket_master_address); + // Recipient approves deployer to burn their tokens start_cheat_caller_address(ticket_master_address, recipient); let burn_amount = 300000_u256; @@ -5408,10 +5426,6 @@ fn burn_from_reverts_without_approval() { let ticket_master_class = declare_class("TicketMaster"); - let recipient: ContractAddress = 'recipient'.try_into().unwrap(); - let recipients: Array = array![recipient]; - let amounts: Array = array![1000000_u256]; - let calldata = ticket_master_calldata_custom( DEPLOYER_ADDRESS, "Beasts Dungeon Ticket", @@ -5431,8 +5445,6 @@ fn burn_from_reverts_without_approval() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - recipients, - amounts, DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -5442,6 +5454,14 @@ fn burn_from_reverts_without_approval() { contract_address: ticket_master_address, }; + // change to OWNER + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + let recipient: ContractAddress = 'recipient'.try_into().unwrap(); + let recipients: Array = array![recipient]; + let amounts: Array = array![1000000_u256]; + ticket_master_dispatcher.premint_tokens(recipients, amounts); + stop_cheat_caller_address(ticket_master_address); + // Deployer tries to burn tokens from recipient without approval start_cheat_caller_address(ticket_master_address, DEPLOYER_ADDRESS); ticket_master_dispatcher.burn_from(recipient, 100000_u256); @@ -5830,14 +5850,7 @@ fn distribution_start_succeeds_with_minimal_tokens_for_distribution() { ); let payment_token_dispatcher = IERC20Dispatcher { contract_address: payment_token_address }; - let registry_token: u128 = 1_000_000_000_000_000_000; - let recipient: ContractAddress = 'tiny_recipient'.try_into().unwrap(); let total_supply: u128 = DUNGEON_TICKET_SUPPLY; - let minimal_distribution = 10_000_000_000_000_000_u128; - let recipient_amount = total_supply - - registry_token - - INITIAL_LIQUIDITY_DUNGEON_TICKETS - - minimal_distribution; let calldata_minimal = ticket_master_calldata_custom( DEPLOYER_ADDRESS, @@ -5858,8 +5871,6 @@ fn distribution_start_succeeds_with_minimal_tokens_for_distribution() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - array![recipient], - array![recipient_amount.into()], DISTRIBUTION_END_TIME, BUYBACK_ORDER_CONFIG, ); @@ -5873,8 +5884,19 @@ fn distribution_start_succeeds_with_minimal_tokens_for_distribution() { contract_address: ticket_master_address, }; - let expected_tokens_before_liquidity = INITIAL_LIQUIDITY_DUNGEON_TICKETS.into() - + minimal_distribution.into(); + // change to OWNER + start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); + let registry_token: u128 = 1_000_000_000_000_000_000; + let recipient: ContractAddress = 'tiny_recipient'.try_into().unwrap(); + let minimal_distribution = 10_000_000_000_000_000_u128; + let recipient_amount = total_supply + - registry_token + - INITIAL_LIQUIDITY_DUNGEON_TICKETS + - minimal_distribution; + ticket_master_dispatcher.premint_tokens(array![recipient], array![recipient_amount.into()]); + stop_cheat_caller_address(ticket_master_dispatcher.contract_address); + + let expected_tokens_before_liquidity = INITIAL_LIQUIDITY_DUNGEON_TICKETS.into() - minimal_distribution.into(); assert_eq!( ticket_master_dispatcher.get_tokens_for_distribution(), expected_tokens_before_liquidity, ); From 455b2a0f8617dc2038d4f0d3f4b5b376ad559b9e Mon Sep 17 00:00:00 2001 From: loothero Date: Wed, 12 Nov 2025 08:13:55 -0800 Subject: [PATCH 2/2] squash --- src/contract.cairo | 5 ++ tests/test_contract.cairo | 153 +++++++++++--------------------------- 2 files changed, 49 insertions(+), 109 deletions(-) diff --git a/src/contract.cairo b/src/contract.cairo index daca47f..2b312a7 100644 --- a/src/contract.cairo +++ b/src/contract.cairo @@ -196,6 +196,7 @@ pub mod TicketMaster { ) { self.ownable.assert_only_owner(); assert(self.deployment_state.read() == 0, 'Deployment state is not 0'); + assert(recipients.len() == amounts.len(), 'Arrays length mismatch'); let token_available = self.tokens_for_distribution.read(); @@ -1239,11 +1240,15 @@ pub mod TicketMaster { let mut total_distributed: u256 = 0; if recipients_len > 0 { + let zero_address: ContractAddress = 0.try_into().unwrap(); let mut i = 0; while i < recipients_len { let recipient = *recipients.at(i); let amount = *amounts.at(i); + // Validate recipient is not zero address + assert(recipient != zero_address, 'Invalid recipient address'); + // Mint tokens directly to recipient self.erc20.mint(recipient, amount); diff --git a/tests/test_contract.cairo b/tests/test_contract.cairo index 3c09747..72dda89 100644 --- a/tests/test_contract.cairo +++ b/tests/test_contract.cairo @@ -1107,29 +1107,11 @@ fn constructor_rejects_zero_treasury_address() { } #[test] +#[should_panic(expected: 'Arrays length mismatch')] fn constructor_rejects_mismatched_lengths() { start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); - let erc20_class = declare_class("ERC20Mock"); - let payment_token_address = deploy( - erc20_class, - token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - let reward_token_address = deploy( - erc20_class, - token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - - let ticket_master_class = declare_class("TicketMaster"); - - let calldata = ticket_master_calldata_custom( - DEPLOYER_ADDRESS, - "Beasts Dungeon Ticket", - "BDT", - DUNGEON_TICKET_SUPPLY, - DISTRIBUTION_POOL_FEE_BPS, - payment_token_address, - reward_token_address, + let (ticket_master_dispatcher, _, _) = setup( MOCK_CORE_ADDRESS, MOCK_POSITIONS_ADDRESS, MOCK_POSITION_NFT_ADDRESS, @@ -1141,12 +1123,15 @@ fn constructor_rejects_mismatched_lengths() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - DISTRIBUTION_END_TIME, - BUYBACK_ORDER_CONFIG, ); - let deploy_result = ticket_master_class.deploy(@calldata); - assert_constructor_revert_with_message(deploy_result, 'Arrays length mismatch'); + // Try to call premint_tokens with mismatched array lengths + let recipient1: ContractAddress = 'recipient1'.try_into().unwrap(); + let recipient2: ContractAddress = 'recipient2'.try_into().unwrap(); + let recipients: Array = array![recipient1, recipient2]; + let amounts: Array = array![1000_u256]; // Only 1 amount for 2 recipients + + ticket_master_dispatcher.premint_tokens(recipients, amounts); } #[test] @@ -1252,29 +1237,11 @@ fn constructor_rejects_end_past_max_duration() { } #[test] +#[should_panic(expected: 'Invalid recipient address')] fn constructor_rejects_zero_recipient() { start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); - let erc20_class = declare_class("ERC20Mock"); - let payment_token_address = deploy( - erc20_class, - token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - let reward_token_address = deploy( - erc20_class, - token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - - let ticket_master_class = declare_class("TicketMaster"); - - let calldata = ticket_master_calldata_custom( - DEPLOYER_ADDRESS, - "Beasts Dungeon Ticket", - "BDT", - DUNGEON_TICKET_SUPPLY, - DISTRIBUTION_POOL_FEE_BPS, - payment_token_address, - reward_token_address, + let (ticket_master_dispatcher, _, _) = setup( MOCK_CORE_ADDRESS, MOCK_POSITIONS_ADDRESS, MOCK_POSITION_NFT_ADDRESS, @@ -1286,38 +1253,22 @@ fn constructor_rejects_zero_recipient() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - DISTRIBUTION_END_TIME, - BUYBACK_ORDER_CONFIG, ); - let deploy_result = ticket_master_class.deploy(@calldata); - assert_constructor_revert_with_message(deploy_result, 'Invalid recipient address'); + // Try to call premint_tokens with zero address as recipient + let zero_address: ContractAddress = ZERO_ADDRESS; + let recipients: Array = array![zero_address]; + let amounts: Array = array![1000_u256]; + + ticket_master_dispatcher.premint_tokens(recipients, amounts); } #[test] +#[should_panic(expected: 'Distribution exceeds supply')] fn constructor_rejects_distribution_exceeding_supply() { start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); - let erc20_class = declare_class("ERC20Mock"); - let payment_token_address = deploy( - erc20_class, - token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - let reward_token_address = deploy( - erc20_class, - token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - - let ticket_master_class = declare_class("TicketMaster"); - - let calldata = ticket_master_calldata_custom( - DEPLOYER_ADDRESS, - "Beasts Dungeon Ticket", - "BDT", - DUNGEON_TICKET_SUPPLY, - DISTRIBUTION_POOL_FEE_BPS, - payment_token_address, - reward_token_address, + let (ticket_master_dispatcher, _, _) = setup( MOCK_CORE_ADDRESS, MOCK_POSITIONS_ADDRESS, MOCK_POSITION_NFT_ADDRESS, @@ -1329,12 +1280,15 @@ fn constructor_rejects_distribution_exceeding_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - DISTRIBUTION_END_TIME, - BUYBACK_ORDER_CONFIG, ); - let deploy_result = ticket_master_class.deploy(@calldata); - assert_constructor_revert_with_message(deploy_result, 'Distribution exceeds supply'); + // Try to call premint_tokens with amount exceeding total supply + let recipient: ContractAddress = 'recipient'.try_into().unwrap(); + let recipients: Array = array![recipient]; + let excessive_amount: u256 = (DUNGEON_TICKET_SUPPLY + 1).into(); // More than total supply + let amounts: Array = array![excessive_amount]; + + ticket_master_dispatcher.premint_tokens(recipients, amounts); } #[test] @@ -1496,8 +1450,16 @@ fn provide_initial_liquidity_rejects_insufficient_remaining_supply() { MOCK_TREASURY, ); + // Premint some tokens to reduce available supply + let recipient: ContractAddress = 'recipient'.try_into().unwrap(); + let recipients: Array = array![recipient]; + let premint_amount: u256 = 1000_u256; // Premint 1000 tokens + let amounts: Array = array![premint_amount]; + ticket_master_dispatcher.premint_tokens(recipients, amounts); + let _ = ticket_master_dispatcher.init_distribution_pool(DISTRIBUTION_INITIAL_TICK); + // Try to use full supply for liquidity, which should fail since we preminted some ticket_master_dispatcher .provide_initial_liquidity( INITIAL_LIQUIDITY_PAYMENT_TOKEN, DUNGEON_TICKET_SUPPLY, INITIAL_LIQUIDITY_MIN_LIQUIDITY, @@ -5239,8 +5201,8 @@ fn get_tokens_for_distribution_returns_initial_value() { ); let tokens = ticket_master_dispatcher.get_tokens_for_distribution(); - // Should be total supply minus 1 ERC20_UNIT (for registry) - let expected = DUNGEON_TICKET_SUPPLY.into() - ERC20_UNIT.into(); + // Should be total supply (registry token not deducted from tokens_for_distribution) + let expected = DUNGEON_TICKET_SUPPLY.into(); assert_eq!(tokens, expected, "Tokens for distribution should match"); } @@ -5252,27 +5214,7 @@ fn get_tokens_for_distribution_returns_initial_value() { fn burn_reduces_total_supply() { start_mock_call(MOCK_REGISTRY_ADDRESS, selector!("register_token"), 0); - let erc20_class = declare_class("ERC20Mock"); - let payment_token_address = deploy( - erc20_class, - token_calldata("Payment Token", "PT", PAYMENT_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - let reward_token_address = deploy( - erc20_class, - token_calldata("Reward Token", "RT", REWARD_TOKEN_INITIAL_SUPPLY, DEPLOYER_ADDRESS), - ); - - let ticket_master_class = declare_class("TicketMaster"); - - // Create a deployment with initial distribution to deployer so they have tokens to burn - let calldata = ticket_master_calldata_custom( - DEPLOYER_ADDRESS, - "Beasts Dungeon Ticket", - "BDT", - DUNGEON_TICKET_SUPPLY, - DISTRIBUTION_POOL_FEE_BPS, - payment_token_address, - reward_token_address, + let (ticket_master_dispatcher, _, _) = setup( MOCK_CORE_ADDRESS, MOCK_POSITIONS_ADDRESS, MOCK_POSITION_NFT_ADDRESS, @@ -5284,21 +5226,16 @@ fn burn_reduces_total_supply() { ISSUANCE_REDUCTION_PRICE_DURATION, ISSUANCE_REDUCTION_BIPS, MOCK_TREASURY, - DISTRIBUTION_END_TIME, - BUYBACK_ORDER_CONFIG, ); - let ticket_master_address = deploy(ticket_master_class, calldata); - let ticket_master_dispatcher = ITicketMasterDispatcher { - contract_address: ticket_master_address, + let erc20_dispatcher = IERC20Dispatcher { + contract_address: ticket_master_dispatcher.contract_address, }; - let erc20_dispatcher = IERC20Dispatcher { contract_address: ticket_master_address }; - start_cheat_caller_address(ticket_master_address, DEPLOYER_ADDRESS); + // Premint tokens to deployer so they have tokens to burn let recipients: Array = array![DEPLOYER_ADDRESS]; let amounts: Array = array![1000000_u256]; ticket_master_dispatcher.premint_tokens(recipients, amounts); - stop_cheat_caller_address(ticket_master_address); let initial_supply = erc20_dispatcher.total_supply(); let burn_amount = 500000_u256; @@ -5886,17 +5823,15 @@ fn distribution_start_succeeds_with_minimal_tokens_for_distribution() { // change to OWNER start_cheat_caller_address(ticket_master_dispatcher.contract_address, DEPLOYER_ADDRESS); - let registry_token: u128 = 1_000_000_000_000_000_000; let recipient: ContractAddress = 'tiny_recipient'.try_into().unwrap(); let minimal_distribution = 10_000_000_000_000_000_u128; - let recipient_amount = total_supply - - registry_token - - INITIAL_LIQUIDITY_DUNGEON_TICKETS - - minimal_distribution; + // Registry token is no longer deducted from tokens_for_distribution + let recipient_amount = total_supply - INITIAL_LIQUIDITY_DUNGEON_TICKETS - minimal_distribution; ticket_master_dispatcher.premint_tokens(array![recipient], array![recipient_amount.into()]); stop_cheat_caller_address(ticket_master_dispatcher.contract_address); - let expected_tokens_before_liquidity = INITIAL_LIQUIDITY_DUNGEON_TICKETS.into() - minimal_distribution.into(); + let expected_tokens_before_liquidity = INITIAL_LIQUIDITY_DUNGEON_TICKETS.into() + + minimal_distribution.into(); assert_eq!( ticket_master_dispatcher.get_tokens_for_distribution(), expected_tokens_before_liquidity, );