diff --git a/contract_/src/BigIncGenesis.cairo b/contract_/src/BigIncGenesis.cairo index 1f6c560..057cb74 100644 --- a/contract_/src/BigIncGenesis.cairo +++ b/contract_/src/BigIncGenesis.cairo @@ -109,7 +109,7 @@ pub mod BigIncGenesis { #[event] #[derive(Drop, starknet::Event)] - enum Event { + pub enum Event { #[flat] OwnableEvent: OwnableComponent::Event, #[flat] @@ -128,69 +128,69 @@ pub mod BigIncGenesis { } #[derive(Drop, starknet::Event)] - struct ShareMinted { + pub struct ShareMinted { #[key] - buyer: ContractAddress, - shares_bought: u256, - amount: u256, + pub buyer: ContractAddress, + pub shares_bought: u256, + pub amount: u256, } #[derive(Drop, starknet::Event)] - struct PresaleEnded {} + pub struct PresaleEnded {} #[derive(Drop, starknet::Event)] - struct TransferShare { + pub struct TransferShare { #[key] - from: ContractAddress, + pub from: ContractAddress, #[key] - to: ContractAddress, - share_amount: u256, + pub to: ContractAddress, + pub share_amount: u256, } #[derive(Drop, starknet::Event)] - struct Donate { + pub struct Donate { #[key] - donor: ContractAddress, - token_address: ContractAddress, - amount: u256, + pub donor: ContractAddress, + pub token_address: ContractAddress, + pub amount: u256, } #[derive(Drop, starknet::Event)] - struct SharesSeized { + pub struct SharesSeized { #[key] - shareholder: ContractAddress, - share_amount: u256, + pub shareholder: ContractAddress, + pub share_amount: u256, } #[derive(Drop, starknet::Event)] - struct AllSharesSold {} + pub struct AllSharesSold {} #[derive(Drop, starknet::Event)] - struct Withdrawn { + pub struct Withdrawn { #[key] - token_address: ContractAddress, - amount: u256, - owner: ContractAddress, - timestamp: u256, + pub token_address: ContractAddress, + pub amount: u256, + pub owner: ContractAddress, + pub timestamp: u256, } #[derive(Drop, starknet::Event)] - struct PartnerShareCapSet { + pub struct PartnerShareCapSet { #[key] - token_address: ContractAddress, - cap: u256, + pub token_address: ContractAddress, + pub cap: u256, } #[derive(Drop, starknet::Event)] - struct PartnerShareMinted { + pub struct PartnerShareMinted { #[key] - token_address: ContractAddress, + pub token_address: ContractAddress, #[key] - buyer: ContractAddress, - amount_paid: u256, - shares_received: u256, - rate: u256, + pub buyer: ContractAddress, + pub amount_paid: u256, + pub shares_received: u256, + pub rate: u256, } #[constructor] diff --git a/contract_/tests/biginc_genesis_test.cairo b/contract_/tests/biginc_genesis_test.cairo new file mode 100644 index 0000000..64bbc59 --- /dev/null +++ b/contract_/tests/biginc_genesis_test.cairo @@ -0,0 +1,342 @@ +use contract_::BigIncGenesis::{ + BigIncGenesis, IBigIncGenesisDispatcher, IBigIncGenesisDispatcherTrait, +}; +use core::num::traits::Zero; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use snforge_std::{ + CheatSpan, EventSpyAssertionsTrait, cheat_block_timestamp, cheat_caller_address, spy_events, +}; +use starknet::ContractAddress; +use super::setup::{deploy_mock_erc20, owner, setup}; + +fn alice() -> ContractAddress { + 'alice'.try_into().unwrap() +} + +fn charlie() -> ContractAddress { + 'charlie'.try_into().unwrap() +} + +fn mint(targets: Array<(ContractAddress, u256)>, dispatcher: IERC20Dispatcher) { + for i in 0..targets.len() { + let (recipient, amount) = *targets.at(i); + cheat_caller_address(dispatcher.contract_address, owner(), CheatSpan::TargetCalls(1)); + dispatcher.transfer(recipient, amount); + } +} + +fn feign_mint_share(genesis: IBigIncGenesisDispatcher, token: IERC20Dispatcher, amount: u256) { + mint(array![(charlie(), amount)], token); + cheat_caller_address(token.contract_address, charlie(), CheatSpan::TargetCalls(1)); + token.approve(genesis.contract_address, amount); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::Indefinite); + genesis.mint_share(token.contract_address); +} + +fn default_mint_context() -> (IBigIncGenesisDispatcher, IERC20Dispatcher) { + let (genesis, usdt, _) = setup(); + let amount = 10000; + feign_mint_share(genesis, usdt, amount); + (genesis, usdt) +} + +#[test] +fn test_genesis_mint_share_success() { + let (genesis, usdt, _) = setup(); + let amount = 10000; + let mut spy = spy_events(); + feign_mint_share(genesis, usdt, amount); + + let presale_share_valuation = genesis.get_presale_share_valuation(); + let shares_bought = (amount * 100000000_u256) / presale_share_valuation; + let shares_sold = genesis.get_shares_sold(); + assert(shares_bought == shares_sold, 'SHARES VALUATION MISMATCH'); + let is_shareholder = genesis.is_shareholder(charlie()); + assert(is_shareholder, 'CHARLIE NOT SHAREHOLDER'); + + let event = BigIncGenesis::Event::ShareMinted( + BigIncGenesis::ShareMinted { buyer: charlie(), shares_bought, amount }, + ); + + spy.assert_emitted(@array![(genesis.contract_address, event)]); +} + +#[test] +#[should_panic(expected: 'Invalid token address')] +fn test_genesis_mint_share_should_panic_on_invalid_token() { + let (genesis, _, _) = setup(); + let amount = 1000; + let contract_address = deploy_mock_erc20("TOKEN", "TKN", 1000000, owner()); + let token = IERC20Dispatcher { contract_address }; + feign_mint_share(genesis, token, amount); +} + +#[test] +fn test_genesis_transfer_share_success() { + let (genesis, _) = default_mint_context(); + // shares have been minted to charlie + let shares = genesis.get_shares(charlie()); + let alice_shares = genesis.get_shares(alice()); + assert(alice_shares == 0, 'ALICE SHOULD HAVE NO SHARES'); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::TargetCalls(5)); + + let amount = shares / 2; + let remaining_amount = shares - amount; + + let mut spy = spy_events(); + genesis.transfer_share(alice(), amount); + let alice_shares = genesis.get_shares(alice()); + assert(alice_shares == amount, 'ALICE SHARES MISMATCH'); + + let shares = genesis.get_shares(charlie()); + assert(shares == remaining_amount, 'CHARLIE SHARES MISMATCH'); + + let share_holders = genesis.get_shareholders(); + println!("Number of share holders: {}", share_holders.len()); + for i in 0..share_holders.len() { + println!("Share at {} is: {}", i, genesis.get_shares(*share_holders.at(i))); + } + assert(share_holders.len() == 3, 'INCORRECT SHARE HOLDERS'); // since the owner holds by default + + let event = BigIncGenesis::Event::TransferShare( + BigIncGenesis::TransferShare { from: charlie(), to: alice(), share_amount: amount }, + ); + spy.assert_emitted(@array![(genesis.contract_address, event)]); +} + +#[test] +#[should_panic(expected: 'Insufficient shares')] +fn test_genesis_transfer_share_should_panic_on_insufficient_shares() { + let (genesis, _) = default_mint_context(); + let shares = genesis.get_shares(charlie()); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::Indefinite); + genesis.transfer_share(alice(), shares + 1); +} + +#[test] +#[should_panic(expected: 'Cannot transfer to zero address')] +fn test_genesis_transfer_share_should_panic_on_zero_address() { + let (genesis, _) = default_mint_context(); + let shares = genesis.get_shares(charlie()); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::Indefinite); + genesis.transfer_share(Zero::zero(), shares); +} + +#[test] +fn test_genesis_donate_success() { + let (genesis, usdt, _) = setup(); + let amount = 1000; + mint(array![(charlie(), amount)], usdt); + let mut spy = spy_events(); + let previous_balance = usdt.balance_of(genesis.contract_address); + assert(previous_balance == 0, 'PREV BALANCE SHOULD BE ZERO'); + + cheat_caller_address(usdt.contract_address, charlie(), CheatSpan::TargetCalls(1)); + usdt.approve(genesis.contract_address, amount); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::TargetCalls(1)); + genesis.donate(usdt.contract_address, amount); + + let new_balance = usdt.balance_of(genesis.contract_address); + assert(new_balance == amount, 'CONTRACT BALANCE MISMATCH'); + let event = BigIncGenesis::Event::Donate( + BigIncGenesis::Donate { donor: charlie(), token_address: usdt.contract_address, amount }, + ); + spy.assert_emitted(@array![(genesis.contract_address, event)]); +} + +#[test] +#[should_panic(expected: 'Exceeds available shares')] +fn test_genesis_mint_share_should_panic_on_exceeded_available_shares() { + let (genesis, usdt, _) = setup(); + let shares = genesis.get_available_shares(); + let presale_share_valuation = genesis.get_presale_share_valuation(); + println!("Available shares: {}", shares); + let amount = (shares * presale_share_valuation) / 100000000; + feign_mint_share(genesis, usdt, amount + 10000); +} + +#[test] +#[should_panic(expected: 'Insufficient balance')] +fn test_genesis_withdraw_success_and_should_panic_on_insufficient_funds() { + let (genesis, usdt) = default_mint_context(); + let balance = usdt.balance_of(genesis.contract_address); + let owner_balance = usdt.balance_of(owner()); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + let timestamp = 10; + cheat_block_timestamp(genesis.contract_address, timestamp, CheatSpan::Indefinite); + let mut spy = spy_events(); + genesis.withdraw(usdt.contract_address, balance); + + let genesis_balance = usdt.balance_of(genesis.contract_address); + assert(genesis_balance == 0, 'WITHDRAWAL FAILED 1.'); + let new_balance = usdt.balance_of(owner()); + assert(new_balance == (owner_balance + balance), 'OWNER BALANCE MISMATCH'); + + let event = BigIncGenesis::Event::Withdrawn( + BigIncGenesis::Withdrawn { + token_address: usdt.contract_address, + amount: balance, + owner: owner(), + timestamp: timestamp.into(), + }, + ); + spy.assert_emitted(@array![(genesis.contract_address, event)]); + + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + genesis.withdraw(usdt.contract_address, 1); +} + +#[test] +#[should_panic(expected: 'Caller is not the owner')] +fn test_genesis_withdraw_should_panic_on_non_owner() { + let (genesis, usdt) = default_mint_context(); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::TargetCalls(1)); + genesis.withdraw(usdt.contract_address, 100); +} + +#[test] +#[should_panic(expected: 'Insufficient shares')] +fn test_genesis_seize_shares_success_and_should_panic_on_transfer() { + let (genesis, _) = default_mint_context(); + let mut spy = spy_events(); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + + let shares_sold = genesis.get_shares_sold(); + let shares = genesis.get_shares(charlie()); + assert(shares == shares_sold, 'CHARLIE SHARES MISMATCH'); + + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + genesis.seize_shares(charlie()); + + let event = BigIncGenesis::Event::SharesSeized( + BigIncGenesis::SharesSeized { shareholder: charlie(), share_amount: shares }, + ); + spy.assert_emitted(@array![(genesis.contract_address, event)]); + + let shares = genesis.get_shares(charlie()); + assert(shares == 0, 'CHARLIE SHARES SHOULD BE ZERO'); + + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::Indefinite); + genesis.transfer_share(alice(), 1); +} + +#[test] +#[should_panic(expected: 'Exceeds partner share cap')] +fn test_genesis_partner_share_cap_operations_success_and_should_panic_on_exceeded_cap() { + let (genesis, usdt, _) = setup(); + let previous_cap = genesis.get_partner_share_cap(usdt.contract_address); + assert(previous_cap == 0, 'PREVIOUS CAP SHOULD BE ZERO'); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(2)); + let cap = 10000000; + let mut spy = spy_events(); + genesis.set_partner_share_cap(usdt.contract_address, cap); // 10M shares cap + + let rate = 10000; + genesis.set_partner_token_rate(usdt.contract_address, rate); + + let cap_ref = genesis.get_partner_share_cap(usdt.contract_address); + assert(cap == cap_ref, 'PRICE CAP MISMATCH'); + + let event1 = BigIncGenesis::Event::PartnerShareCapSet( + BigIncGenesis::PartnerShareCapSet { token_address: usdt.contract_address, cap }, + ); + + // let amount = 10000; + let share_precision = 100000000_u256; + // let shares_received = (amount * share_precision) / rate; + let amount = (cap * rate) / share_precision; + mint(array![(alice(), amount)], usdt); + cheat_caller_address(usdt.contract_address, alice(), CheatSpan::TargetCalls(1)); + usdt.approve(genesis.contract_address, amount); + + // Mint partner shares + cheat_caller_address(genesis.contract_address, alice(), CheatSpan::TargetCalls(1)); + println!("Got here, 1."); + genesis.mint_partner_share(usdt.contract_address, amount); + println!("Got here, 2."); + let shares_received = genesis.get_shares_sold(); + let shares = genesis.get_shares(alice()); + assert(shares == shares_received, 'ALICE SHARES MISMATCH'); + + let shares_minted_by_partner = genesis.get_shares_minted_by_partner(usdt.contract_address); + println!("Shares minted by partner: {}", shares_minted_by_partner); + println!("Cap: {}", cap); + feign_mint_share(genesis, usdt, amount); + + let event2 = BigIncGenesis::Event::PartnerShareMinted( + BigIncGenesis::PartnerShareMinted { + token_address: usdt.contract_address, + buyer: alice(), + amount_paid: amount, + shares_received, + rate, + }, + ); + + let events = array![(genesis.contract_address, event1), (genesis.contract_address, event2)]; + spy.assert_emitted(@events); + + // mint. should panic + println!("End."); + feign_mint_share(genesis, usdt, amount * amount); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_genesis_set_partner_share_cap_not_owner() { + let (genesis, usdt, _) = setup(); + cheat_caller_address(genesis.contract_address, alice(), CheatSpan::TargetCalls(1)); + genesis.set_partner_share_cap(usdt.contract_address, 1000); +} + +#[test] +#[should_panic(expected: ('Invalid token address',))] +fn test_genesis_set_partner_share_cap_invalid_token() { + let (genesis, _, _) = setup(); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + let random_token: ContractAddress = 'random token'.try_into().unwrap(); + genesis.set_partner_share_cap(random_token, 1000); +} + +#[test] +fn test_genesis_remove_partner_share_cap_success() { + let (genesis, _, usdc) = setup(); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::Indefinite); + genesis.set_partner_share_cap(usdc.contract_address, 1000); + let shares = genesis.get_partner_share_cap(usdc.contract_address); + assert(shares == 1000, 'PARTNER SHARES MISMATCH'); + + genesis.remove_partner_share_cap(usdc.contract_address); + let shares = genesis.get_partner_share_cap(usdc.contract_address); + assert(shares == 0, 'PARTNER SHARES CAP SHOULD BE 0'); +} + +#[test] +#[should_panic(expected: 'Pausable: paused')] +fn test_genesis_mint_should_panic_on_pause() { + let (genesis, usdt, _) = setup(); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + genesis.pause(); + feign_mint_share(genesis, usdt, 1000); +} + +#[test] +#[should_panic(expected: 'Pausable: paused')] +fn test_genesis_transfer_should_panic_on_pause() { + let (genesis, _) = default_mint_context(); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(1)); + genesis.pause(); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::TargetCalls(1)); + genesis.transfer_share(alice(), 1); +} + +#[test] +fn test_genesis_mint_and_transfer_success_on_unpause() { + let (genesis, usdt, _) = setup(); + cheat_caller_address(genesis.contract_address, owner(), CheatSpan::TargetCalls(2)); + genesis.pause(); + genesis.unpause(); + feign_mint_share(genesis, usdt, 100000); + cheat_caller_address(genesis.contract_address, charlie(), CheatSpan::TargetCalls(1)); + genesis.transfer_share(alice(), 1); +} diff --git a/contract_/tests/mock_erc20.cairo b/contract_/tests/mock_erc20.cairo new file mode 100644 index 0000000..d7d5e54 --- /dev/null +++ b/contract_/tests/mock_erc20.cairo @@ -0,0 +1,84 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IMintable { + // Add the external mint function for testing purposes + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256); +} + +#[starknet::contract] +pub mod MockERC20 { + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::{ClassHash, ContractAddress}; + use super::IMintable; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // External + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + + // Internal + impl ERC20InternalImpl = ERC20Component::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + name: ByteArray, + symbol: ByteArray, + supply: u256, + ) { + self.erc20.initializer(name, symbol); + // Mint some initial tokens to the owner + self.erc20.mint(owner, supply); + + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl ITestTokensImpl of IMintable { + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.ownable.assert_only_owner(); + self.erc20.mint(recipient, amount); + } + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } +} diff --git a/contract_/tests/setup.cairo b/contract_/tests/setup.cairo new file mode 100644 index 0000000..9f25b59 --- /dev/null +++ b/contract_/tests/setup.cairo @@ -0,0 +1,54 @@ +use contract_::BigIncGenesis::IBigIncGenesisDispatcher; +use core::result::ResultTrait; +use openzeppelin::token::erc20::interface::IERC20Dispatcher; +use openzeppelin::utils::serde::SerializedAppend; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, declare, start_cheat_caller_address, + stop_cheat_caller_address, +}; +use starknet::{ContractAddress, contract_address_const}; + +pub const OWNER: felt252 = 'owner'; +pub const USER1: felt252 = 'user1'; +pub const USER2: felt252 = 'user2'; +const USDT_INITIAL_SUPPLY: u256 = 1000000000000_u256; // 1M USDT with 6 decimals +const USDC_INITIAL_SUPPLY: u256 = 1000000000000_u256; // 1M USDC with 6 decimals + +pub fn owner() -> ContractAddress { + OWNER.try_into().unwrap() +} + +pub fn deploy_mock_erc20( + name: ByteArray, symbol: ByteArray, initial_supply: u256, recipient: ContractAddress, +) -> ContractAddress { + let contract = declare("MockERC20").unwrap().contract_class(); + let mut constructor_args = array![]; + constructor_args.append_serde(recipient); + constructor_args.append_serde(name); + constructor_args.append_serde(symbol); + constructor_args.append_serde(initial_supply); + + let (contract_address, _) = contract.deploy(@constructor_args).unwrap(); + contract_address +} + +pub fn deploy_big_inc_genesis( + usdt_address: ContractAddress, usdc_address: ContractAddress, +) -> ContractAddress { + let contract = declare("BigIncGenesis").unwrap().contract_class(); + let (contract_address, _) = contract + .deploy(@array![usdt_address.into(), usdc_address.into(), OWNER.try_into().unwrap()]) + .unwrap(); + contract_address +} + +pub fn setup() -> (IBigIncGenesisDispatcher, IERC20Dispatcher, IERC20Dispatcher) { + let usdt_address = deploy_mock_erc20("USDT", "USDT", USDT_INITIAL_SUPPLY, owner()); + let usdc_address = deploy_mock_erc20("USDC", "USDC", USDC_INITIAL_SUPPLY, owner()); + let big_inc_address = deploy_big_inc_genesis(usdt_address, usdc_address); + let genesis_dispatcher = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt_dispatcher = IERC20Dispatcher { contract_address: usdt_address }; + let usdc_dispatcher = IERC20Dispatcher { contract_address: usdc_address }; + + (genesis_dispatcher, usdt_dispatcher, usdc_dispatcher) +} diff --git a/contract_/tests/test_contract.cairo b/contract_/tests/test_contract.cairo deleted file mode 100644 index 8b13789..0000000 --- a/contract_/tests/test_contract.cairo +++ /dev/null @@ -1 +0,0 @@ - diff --git a/contract_/tests/test_partner_share_cap.cairo b/contract_/tests/test_partner_share_cap.cairo deleted file mode 100644 index 91bfaf1..0000000 --- a/contract_/tests/test_partner_share_cap.cairo +++ /dev/null @@ -1,293 +0,0 @@ -use contract_::BigIncGenesis::{IBigIncGenesisDispatcher, IBigIncGenesisDispatcherTrait}; -use core::result::ResultTrait; -use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; -use snforge_std::{ - ContractClassTrait, DeclareResultTrait, declare, start_cheat_caller_address, - stop_cheat_caller_address, -}; -use starknet::{ContractAddress, contract_address_const}; - -const OWNER: felt252 = 'owner'; -const USER1: felt252 = 'user1'; -const USER2: felt252 = 'user2'; -const USDT_INITIAL_SUPPLY: u256 = 1000000000000_u256; // 1M USDT with 6 decimals -const USDC_INITIAL_SUPPLY: u256 = 1000000000000_u256; // 1M USDC with 6 decimals - -fn deploy_mock_erc20( - name: felt252, symbol: felt252, initial_supply: u256, recipient: ContractAddress, -) -> ContractAddress { - let contract = declare("MockERC20").unwrap().contract_class(); - let (contract_address, _) = contract - .deploy( - @array![ - initial_supply.low.into(), - initial_supply.high.into(), - recipient.into(), - // ByteArray name - simple felt252 approach - 0, // data length - name, // pending word - 4, // pending word length - // ByteArray symbol - simple felt252 approach - 0, // data length - symbol, // pending word - 4, // pending word length - 6, // decimals - contract_address_const::().into(), - ], - ) - .unwrap(); - contract_address -} - -fn deploy_big_inc_genesis( - usdt_address: ContractAddress, usdc_address: ContractAddress, -) -> ContractAddress { - let contract = declare("BigIncGenesis").unwrap().contract_class(); - let (contract_address, _) = contract - .deploy( - @array![ - usdt_address.into(), usdc_address.into(), contract_address_const::().into(), - ], - ) - .unwrap(); - contract_address -} - -fn setup() -> (ContractAddress, ContractAddress, ContractAddress) { - let user1 = contract_address_const::(); - let usdt_address = deploy_mock_erc20('USDT', 'USDT', USDT_INITIAL_SUPPLY, user1); - let usdc_address = deploy_mock_erc20('USDC', 'USDC', USDC_INITIAL_SUPPLY, user1); - let big_inc_address = deploy_big_inc_genesis(usdt_address, usdc_address); - - (big_inc_address, usdt_address, usdc_address) -} - -#[cfg(test)] -fn test_set_partner_share_cap_success() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let owner = contract_address_const::(); - - // Set partner share cap as owner - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(usdt_address, 10000000_u256); // 10M shares cap - stop_cheat_caller_address(big_inc_address); - - // Verify the cap was set - let cap = big_inc.get_partner_share_cap(usdt_address); - assert(cap == 10000000_u256, 'Partner cap not set correctly'); -} - -#[cfg(test)] -#[should_panic(expected: ('Caller is not the owner',))] -fn test_set_partner_share_cap_not_owner() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let user1 = contract_address_const::(); - - // Try to set partner share cap as non-owner - start_cheat_caller_address(big_inc_address, user1); - big_inc.set_partner_share_cap(usdt_address, 10000000_u256); - stop_cheat_caller_address(big_inc_address); -} - -#[cfg(test)] -#[should_panic(expected: ('Invalid token address',))] -fn test_set_partner_share_cap_invalid_token() { - let (big_inc_address, _usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let owner = contract_address_const::(); - let invalid_token = contract_address_const::<'invalid_token'>(); - - // Try to set partner share cap for invalid token - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(invalid_token, 10000000_u256); - stop_cheat_caller_address(big_inc_address); -} - -#[cfg(test)] -fn test_remove_partner_share_cap_success() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let owner = contract_address_const::(); - - // Set partner share cap first - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(usdt_address, 10000000_u256); - let cap_before = big_inc.get_partner_share_cap(usdt_address); - assert(cap_before == 10000000_u256, 'Cap should be set'); - - // Remove partner share cap - big_inc.remove_partner_share_cap(usdt_address); - stop_cheat_caller_address(big_inc_address); - - // Verify the cap was removed - let cap_after = big_inc.get_partner_share_cap(usdt_address); - assert(cap_after == 0_u256, 'Partner cap not removed'); -} - -#[cfg(test)] -#[should_panic(expected: ('Caller is not the owner',))] -fn test_remove_partner_share_cap_not_owner() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let user1 = contract_address_const::(); - - // Try to remove partner share cap as non-owner - start_cheat_caller_address(big_inc_address, user1); - big_inc.remove_partner_share_cap(usdt_address); - stop_cheat_caller_address(big_inc_address); -} - -#[cfg(test)] -fn test_get_partner_share_cap_default_zero() { - let (big_inc_address, usdt_address, usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - - // Check default cap is zero - let usdt_cap = big_inc.get_partner_share_cap(usdt_address); - let usdc_cap = big_inc.get_partner_share_cap(usdc_address); - - assert(usdt_cap == 0_u256, 'Default USDT cap should be 0'); - assert(usdc_cap == 0_u256, 'Default USDC cap should be 0'); -} - -#[cfg(test)] -fn test_get_shares_minted_by_partner_default_zero() { - let (big_inc_address, usdt_address, usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - - // Check default shares minted is zero - let usdt_shares = big_inc.get_shares_minted_by_partner(usdt_address); - let usdc_shares = big_inc.get_shares_minted_by_partner(usdc_address); - - assert(usdt_shares == 0_u256, 'Default USDT shares should be 0'); - assert(usdc_shares == 0_u256, 'Default USDC shares should be 0'); -} - -#[cfg(test)] -fn test_mint_share_respects_partner_cap() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let usdt = IERC20Dispatcher { contract_address: usdt_address }; - let owner = contract_address_const::(); - let user1 = contract_address_const::(); - - // Set partner share cap - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(usdt_address, 5000000_u256); // 5M shares cap - stop_cheat_caller_address(big_inc_address); - - // Approve and mint shares within cap - small amount for 1M shares - let amount = 4571430000_u256; // Amount for ~1M shares at presale price (1M * 457143 / 100) - start_cheat_caller_address(usdt_address, user1); - usdt.approve(big_inc_address, amount); - stop_cheat_caller_address(usdt_address); - - start_cheat_caller_address(big_inc_address, user1); - big_inc.mint_share(usdt_address); - stop_cheat_caller_address(big_inc_address); - - // Verify shares were minted - let shares_minted = big_inc.get_shares_minted_by_partner(usdt_address); - assert(shares_minted > 0, 'Shares should be minted'); - assert(shares_minted <= 5000000_u256, 'Should not exceed cap'); -} - -#[cfg(test)] -#[should_panic(expected: ('Exceeds partner share cap',))] -fn test_mint_share_exceeds_partner_cap() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let usdt = IERC20Dispatcher { contract_address: usdt_address }; - let owner = contract_address_const::(); - let user1 = contract_address_const::(); - - // Set low partner share cap - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(usdt_address, 500000_u256); // 500K shares cap - stop_cheat_caller_address(big_inc_address); - - // Try to mint more shares than cap allows - 1M shares - let amount = 4571430000_u256; // Amount for ~1M shares at presale price - start_cheat_caller_address(usdt_address, user1); - usdt.approve(big_inc_address, amount); - stop_cheat_caller_address(usdt_address); - - start_cheat_caller_address(big_inc_address, user1); - big_inc.mint_share(usdt_address); - stop_cheat_caller_address(big_inc_address); -} - -#[cfg(test)] -fn test_mint_share_without_partner_cap_works() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let usdt = IERC20Dispatcher { contract_address: usdt_address }; - let user1 = contract_address_const::(); - - // No partner cap set (should be 0) - let cap = big_inc.get_partner_share_cap(usdt_address); - assert(cap == 0_u256, 'Cap should be 0 by default'); - - // Should be able to mint shares without cap restriction - small amount for 1M shares - let amount = 4571430000_u256; // Amount for ~1M shares at presale price - start_cheat_caller_address(usdt_address, user1); - usdt.approve(big_inc_address, amount); - stop_cheat_caller_address(usdt_address); - - start_cheat_caller_address(big_inc_address, user1); - big_inc.mint_share(usdt_address); - stop_cheat_caller_address(big_inc_address); - - // Verify shares were minted - let user_shares = big_inc.get_shares(user1); - assert(user_shares > 0, 'Shares should be minted'); -} - -#[cfg(test)] -fn test_partner_cap_separate_for_different_tokens() { - let (big_inc_address, usdt_address, usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let owner = contract_address_const::(); - - // Set different caps for different tokens - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(usdt_address, 5000000_u256); // 5M shares cap for USDT - big_inc.set_partner_share_cap(usdc_address, 7000000_u256); // 7M shares cap for USDC - stop_cheat_caller_address(big_inc_address); - - // Verify caps are set correctly - let usdt_cap = big_inc.get_partner_share_cap(usdt_address); - let usdc_cap = big_inc.get_partner_share_cap(usdc_address); - - assert(usdt_cap == 5000000_u256, 'USDT cap incorrect'); - assert(usdc_cap == 7000000_u256, 'USDC cap incorrect'); - - // Verify shares minted are tracked separately - let usdt_shares = big_inc.get_shares_minted_by_partner(usdt_address); - let usdc_shares = big_inc.get_shares_minted_by_partner(usdc_address); - - assert(usdt_shares == 0_u256, 'USDT shares should be 0'); - assert(usdc_shares == 0_u256, 'USDC shares should be 0'); -} - -#[cfg(test)] -fn test_partner_cap_update_overrides_previous() { - let (big_inc_address, usdt_address, _usdc_address) = setup(); - let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; - let owner = contract_address_const::(); - - // Set initial cap - start_cheat_caller_address(big_inc_address, owner); - big_inc.set_partner_share_cap(usdt_address, 5000000_u256); - let initial_cap = big_inc.get_partner_share_cap(usdt_address); - assert(initial_cap == 5000000_u256, 'Initial cap not set'); - - // Update cap - big_inc.set_partner_share_cap(usdt_address, 8000000_u256); - let updated_cap = big_inc.get_partner_share_cap(usdt_address); - assert(updated_cap == 8000000_u256, 'Cap not updated'); - - stop_cheat_caller_address(big_inc_address); -}