diff --git a/contract_/README.md b/contract_/README.md new file mode 100644 index 0000000..0924c7d --- /dev/null +++ b/contract_/README.md @@ -0,0 +1,175 @@ +# BigInc Genesis Contract + +## Overview + +The BigInc Genesis contract is a comprehensive share management system built on Starknet that allows users to purchase shares using USDT or USDC tokens. The contract includes features for presale management, partner share caps, shareholder tracking, and administrative controls. + +## ๐Ÿš€ Recent Changes & Revamp + +### Major Contract Refactoring + +The contract has been completely revamped with the following improvements: + +#### 1. **Simplified Architecture** +- **Removed OpenZeppelin Components**: Eliminated dependency on `OwnableComponent`, `PausableComponent`, and `ReentrancyGuardComponent` +- **Custom Implementation**: Implemented ownership and pausing functionality directly in the contract +- **Cleaner Storage**: Streamlined storage structure with better organization + +#### 2. **Enhanced Partner Share Cap System** +- **Dynamic Cap Management**: Added ability to set and remove partner share caps per token +- **Cap Enforcement**: Prevents minting shares beyond the set cap for specific tokens +- **Tracking**: Maintains separate tracking of shares minted by each partner token + +#### 3. **Improved Shareholder Management** +- **Efficient Indexing**: Added `shareholder_index` mapping for O(1) shareholder removal +- **Better List Management**: Optimized shareholder list operations +- **New View Functions**: Added `get_shareholder_count()` and `get_shareholder_at_index()` + +#### 4. **Code Organization** +- **Modular Functions**: Split large functions into smaller, focused internal functions +- **Better Validation**: Comprehensive parameter validation with clear error messages +- **Event System**: Enhanced event structure with proper categorization + +## ๐Ÿ”ง New Features + +### Partner Share Cap System + +The contract now supports setting share caps for specific partner tokens: + +```cairo +// Set a cap for USDT partner +set_partner_share_cap(usdt_address, 5000000_u256); // 5M shares cap + +// Remove the cap +remove_partner_share_cap(usdt_address); + +// Check current cap +let cap = get_partner_share_cap(usdt_address); + +// Check shares minted by partner +let shares_minted = get_shares_minted_by_partner(usdt_address); +``` + +### Enhanced Shareholder Management + +```cairo +// Get total number of shareholders +let count = get_shareholder_count(); + +// Get shareholder at specific index +let shareholder = get_shareholder_at_index(0); +``` + +## ๐Ÿงช Testing + +### โš ๏ธ Important Note About Integration Tests + +The file `tests/test_partner_share_cap.cairo` contains **integration tests** that demonstrate the partner share cap functionality. However, this is **NOT the recommended approach** for writing integration tests in Cairo/Starknet for the following reasons: + +#### Issues with Current Test Approach: + +1. **Mixed Test Types**: The file combines unit tests with integration test patterns +2. **Complex Setup**: Uses `snforge_std` cheat codes which are more suitable for unit testing +3. **Hardcoded Values**: Contains magic numbers and hardcoded addresses +4. **Limited Scope**: Tests only specific scenarios rather than comprehensive integration flows + +#### Recommended Testing Strategy: + +1. **Unit Tests**: Use `snforge_std` for testing individual functions in isolation +2. **Integration Tests**: Use `starknet::testing` for testing contract interactions +3. **Separate Concerns**: Keep unit tests and integration tests in separate files +4. **Mock Contracts**: Use proper mock contracts for external dependencies + +### Version Compatibility + +**โš ๏ธ Important**: These tests will run successfully on **Cairo version 2.9.4** and compatible Starknet versions. The contract uses Cairo 2.x syntax and features that may not be compatible with older versions. + +## ๐Ÿ“ Contract Structure + +``` +src/ +โ”œโ”€โ”€ lib.cairo # Main library file +โ”œโ”€โ”€ BigIncGenesis.cairo # Main contract implementation +โ”œโ”€โ”€ ierc20.cairo # ERC20 interface +โ””โ”€โ”€ mockerc20.cairo # Mock ERC20 for testing + +tests/ +โ”œโ”€โ”€ test_contract.cairo # Basic contract tests +โ””โ”€โ”€ test_partner_share_cap.cairo # Partner cap tests (see note above) +``` + +## ๐Ÿš€ Key Functions + +### Core Functions +- `mint_share(token_address)` - Purchase shares with USDT/USDC +- `transfer_share(to, amount)` - Transfer shares to another address +- `donate(token_address, amount)` - Donate tokens to the contract + +### Owner Functions +- `withdraw(token_address, amount)` - Withdraw tokens from contract +- `seize_shares(shareholder)` - Seize shares from a shareholder +- `set_partner_share_cap(token_address, cap)` - Set partner share cap +- `remove_partner_share_cap(token_address)` - Remove partner share cap +- `pause()` / `unpause()` - Pause/unpause contract operations + +### View Functions +- `get_available_shares()` - Get remaining shares for sale +- `get_shares(address)` - Get shares owned by address +- `get_partner_share_cap(token_address)` - Get partner share cap +- `get_shares_minted_by_partner(token_address)` - Get shares minted by partner + +## ๐Ÿ”’ Security Features + +1. **Access Control**: Owner-only functions for administrative operations +2. **Pausable**: Contract can be paused in emergency situations +3. **Input Validation**: Comprehensive parameter validation +4. **Partner Caps**: Prevents excessive share minting by partners +5. **Zero Address Checks**: Prevents transfers to zero addresses + +## ๐Ÿ“Š Share Economics + +- **Total Valuation**: $680,000 (680,000,000 with 6 decimals) +- **Presale Valuation**: $457,143 (457,143,000 with 6 decimals) +- **Total Shares**: 100,000,000 +- **Presale Shares**: 21,000,000 (21%) +- **Owner Shares**: 18,000,000 (18%) +- **Available Shares**: 82,000,000 (82%) + +## ๐Ÿ› ๏ธ Development + +### Prerequisites +- Cairo 2.9.4+ +- Scarb +- Starknet Foundry (for testing) + +### Build +```bash +scarb build +``` + +### Test +```bash +scarb test +``` + +### Deploy +```bash +# Deploy with USDT and USDC addresses +scarb deploy +``` + +## ๐Ÿ“ License + +This project is licensed under the MIT License. + +## ๐Ÿค Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests for new functionality +5. Submit a pull request + +## ๐Ÿ“ž Support + +For questions or support, please open an issue in the repository. \ No newline at end of file diff --git a/contract_/src/BigIncGenesis.cairo b/contract_/src/BigIncGenesis.cairo index 1f6c560..84097ca 100644 --- a/contract_/src/BigIncGenesis.cairo +++ b/contract_/src/BigIncGenesis.cairo @@ -1,16 +1,529 @@ +// use starknet::ContractAddress; + +// #[starknet::interface] +// pub trait IBigIncGenesis { +// // Core functionality +// fn mint_share(ref self: TContractState, token_address: ContractAddress); +// fn transfer_share(ref self: TContractState, to: ContractAddress, share_amount: u256); +// fn donate(ref self: TContractState, token_address: ContractAddress, amount: u256); + +// // View functions +// fn get_available_shares(self: @TContractState) -> u256; +// fn get_shares(self: @TContractState, addr: ContractAddress) -> u256; +// fn get_shareholders(self: @TContractState) -> Array; +// fn is_shareholder(self: @TContractState, addr: ContractAddress) -> bool; +// fn get_usdt_address(self: @TContractState) -> ContractAddress; +// fn get_usdc_address(self: @TContractState) -> ContractAddress; +// fn get_total_share_valuation(self: @TContractState) -> u256; +// fn get_presale_share_valuation(self: @TContractState) -> u256; +// fn get_presale_shares(self: @TContractState) -> u256; +// fn get_shares_sold(self: @TContractState) -> u256; +// fn is_presale_active(self: @TContractState) -> bool; + +// // Owner functions +// fn withdraw(ref self: TContractState, token_address: ContractAddress, amount: u256); +// fn seize_shares(ref self: TContractState, shareholder: ContractAddress); +// fn set_partner_share_cap(ref self: TContractState, token_address: ContractAddress, cap: +// u256); +// fn remove_partner_share_cap(ref self: TContractState, token_address: ContractAddress); +// fn pause(ref self: TContractState); +// fn unpause(ref self: TContractState); + +// // Partner view functions +// fn get_partner_share_cap(self: @TContractState, token_address: ContractAddress) -> u256; +// fn get_shares_minted_by_partner(self: @TContractState, token_address: ContractAddress) -> +// u256; + +// // Ownable functions +// fn get_owner(self: @TContractState) -> ContractAddress; +// fn transfer_owner(ref self: TContractState, new_owner: ContractAddress); +// fn renounce_owner(ref self: TContractState); +// } + +// #[starknet::contract] +// pub mod BigIncGenesis { +// use core::traits::Into; +// use openzeppelin::access::ownable::OwnableComponent; +// use openzeppelin::security::pausable::PausableComponent; +// use openzeppelin::security::reentrancyguard::ReentrancyGuardComponent; +// use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +// use starknet::storage::{ +// Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, +// StoragePointerWriteAccess, +// }; +// use starknet::{ContractAddress, get_block_timestamp, get_caller_address, +// get_contract_address}; +// use super::IBigIncGenesis; + +// component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); +// component!(path: PausableComponent, storage: pausable, event: PausableEvent); +// component!( +// path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent, +// ); + +// #[abi(embed_v0)] +// impl OwnableImpl = OwnableComponent::OwnableImpl; +// impl OwnableInternalImpl = OwnableComponent::InternalImpl; + +// #[abi(embed_v0)] +// impl PausableImpl = PausableComponent::PausableImpl; +// impl PausableInternalImpl = PausableComponent::InternalImpl; + +// impl ReentrancyGuardInternalImpl = ReentrancyGuardComponent::InternalImpl; + +// #[storage] +// struct Storage { +// #[substorage(v0)] +// ownable: OwnableComponent::Storage, +// #[substorage(v0)] +// pausable: PausableComponent::Storage, +// #[substorage(v0)] +// reentrancy_guard: ReentrancyGuardComponent::Storage, +// // Token addresses +// usdt_address: ContractAddress, +// usdc_address: ContractAddress, +// // Share economics +// total_share_valuation: u256, +// presale_share_valuation: u256, +// presale_shares: u256, +// shares_sold: u256, +// available_shares: u256, +// is_presale_active: bool, +// // Partner token share caps +// partner_share_cap: Map, +// shares_minted_by_partner: Map, +// // Shareholder management +// shareholders: Map, +// is_shareholder_map: Map, +// shareholder_addresses: Map, +// shareholder_count: u32, +// } + +// #[event] +// #[derive(Drop, starknet::Event)] +// enum Event { +// #[flat] +// OwnableEvent: OwnableComponent::Event, +// #[flat] +// PausableEvent: PausableComponent::Event, +// #[flat] +// ReentrancyGuardEvent: ReentrancyGuardComponent::Event, +// ShareMinted: ShareMinted, +// PresaleEnded: PresaleEnded, +// TransferShare: TransferShare, +// Donate: Donate, +// SharesSeized: SharesSeized, +// AllSharesSold: AllSharesSold, +// Withdrawn: Withdrawn, +// PartnerShareCapSet: PartnerShareCapSet, +// } + +// #[derive(Drop, starknet::Event)] +// struct ShareMinted { +// #[key] +// buyer: ContractAddress, +// shares_bought: u256, +// amount: u256, +// } + +// #[derive(Drop, starknet::Event)] +// struct PresaleEnded {} + +// #[derive(Drop, starknet::Event)] +// struct TransferShare { +// #[key] +// from: ContractAddress, +// #[key] +// to: ContractAddress, +// share_amount: u256, +// } + +// #[derive(Drop, starknet::Event)] +// struct Donate { +// #[key] +// donor: ContractAddress, +// token_address: ContractAddress, +// amount: u256, +// } + +// #[derive(Drop, starknet::Event)] +// struct SharesSeized { +// #[key] +// shareholder: ContractAddress, +// share_amount: u256, +// } + +// #[derive(Drop, starknet::Event)] +// struct AllSharesSold {} + +// #[derive(Drop, starknet::Event)] +// struct Withdrawn { +// #[key] +// token_address: ContractAddress, +// amount: u256, +// owner: ContractAddress, +// timestamp: u256, +// } + +// #[derive(Drop, starknet::Event)] +// struct PartnerShareCapSet { +// #[key] +// token_address: ContractAddress, +// cap: u256, +// } + +// #[constructor] +// fn constructor( +// ref self: ContractState, +// usdt_address: ContractAddress, +// usdc_address: ContractAddress, +// owner: ContractAddress, +// ) { +// // Initialize components +// self.ownable.initializer(owner); + +// // Set token addresses +// self.usdt_address.write(usdt_address); +// self.usdc_address.write(usdc_address); + +// // Initialize share parameters +// self.total_share_valuation.write(680000000000_u256); // $680k with 6 decimals +// self.presale_share_valuation.write(457143000000_u256); // $457k with 6 decimals +// self.presale_shares.write(21000000_u256); // 21% shares +// self.shares_sold.write(0_u256); +// self.available_shares.write(82000000_u256); // 82% available after 18% to owner +// self.is_presale_active.write(true); + +// // Assign 18% shares to owner +// let owner_shares = 18000000_u256; +// self.shareholders.write(owner, owner_shares); +// self.is_shareholder_map.write(owner, true); +// self.shareholder_addresses.write(0, owner); +// self.shareholder_count.write(1); +// } + +// #[abi(embed_v0)] +// impl BigIncGenesisImpl of IBigIncGenesis { +// fn mint_share(ref self: ContractState, token_address: ContractAddress) { +// self.pausable.assert_not_paused(); +// self.reentrancy_guard.start(); + +// self._validate_token(token_address); + +// let caller = get_caller_address(); +// let contract_address = get_contract_address(); + +// // Check if all shares are sold +// if self.available_shares.read() == 0 { +// self.emit(AllSharesSold {}); +// return; +// } + +// let token = IERC20Dispatcher { contract_address: token_address }; +// let amount = token.allowance(caller, contract_address); + +// assert(amount > 0, 'Amount must be > 0'); +// assert(token.balance_of(caller) >= amount, 'Insufficient token balance'); + +// let current_price = if self.is_presale_active.read() { +// self.presale_share_valuation.read() +// } else { +// self.total_share_valuation.read() +// }; + +// let shares_bought = (amount * 100000000_u256) / current_price; +// let new_shares_sold = self.shares_sold.read() + shares_bought; + +// assert(shares_bought <= self.available_shares.read(), 'Exceeds available shares'); + +// // Check partner share cap if set +// let partner_cap = self.partner_share_cap.read(token_address); +// if partner_cap > 0 { +// let current_partner_shares = self.shares_minted_by_partner.read(token_address); +// assert( +// current_partner_shares + shares_bought <= partner_cap, +// 'Exceeds partner share cap', +// ); +// self +// .shares_minted_by_partner +// .write(token_address, current_partner_shares + shares_bought); +// } + +// self.shares_sold.write(new_shares_sold); + +// if self.is_presale_active.read() && new_shares_sold >= self.presale_shares.read() { +// self.is_presale_active.write(false); +// self.emit(PresaleEnded {}); +// } + +// // Add to shareholder if new +// if self.shareholders.read(caller) == 0 { +// let current_count = self.shareholder_count.read(); +// self.shareholder_addresses.write(current_count, caller); +// self.shareholder_count.write(current_count + 1); +// self.is_shareholder_map.write(caller, true); +// } + +// // Update balances +// let current_shares = self.shareholders.read(caller); +// self.shareholders.write(caller, current_shares + shares_bought); + +// let available = self.available_shares.read(); +// self.available_shares.write(available - shares_bought); + +// // Transfer tokens +// token.transfer_from(caller, contract_address, amount); + +// self.emit(ShareMinted { buyer: caller, shares_bought, amount }); + +// self.reentrancy_guard.end(); +// } + +// fn transfer_share(ref self: ContractState, to: ContractAddress, share_amount: u256) { +// self.pausable.assert_not_paused(); + +// let caller = get_caller_address(); +// assert(to != 0.try_into().unwrap(), 'Cannot transfer to zero address'); + +// let sender_shares = self.shareholders.read(caller); +// assert(sender_shares >= share_amount, 'Insufficient shares'); + +// self.shareholders.write(caller, sender_shares - share_amount); + +// let recipient_shares = self.shareholders.read(to); +// self.shareholders.write(to, recipient_shares + share_amount); + +// if recipient_shares == 0 { +// let current_count = self.shareholder_count.read(); +// self.shareholder_addresses.write(current_count, to); +// self.shareholder_count.write(current_count + 1); +// self.is_shareholder_map.write(to, true); +// } + +// if sender_shares == share_amount { +// self.is_shareholder_map.write(caller, false); +// self._remove_shareholder(caller); +// } + +// self.emit(TransferShare { from: caller, to, share_amount }); +// } + +// fn donate(ref self: ContractState, token_address: ContractAddress, amount: u256) { +// let caller = get_caller_address(); +// let contract_address = get_contract_address(); + +// assert(amount > 0, 'Amount must be > 0'); +// assert( +// token_address == self.usdt_address.read() +// || token_address == self.usdc_address.read(), +// 'USDC/USDT Only', +// ); +// let token = IERC20Dispatcher { contract_address: token_address }; + +// assert(token.balance_of(caller) >= amount, 'Insufficient balance'); +// assert(token.allowance(caller, contract_address) >= amount, 'Insufficient +// allowance'); + +// token.transfer_from(caller, contract_address, amount); + +// self.emit(Donate { donor: caller, token_address, amount }); +// } + +// fn withdraw(ref self: ContractState, token_address: ContractAddress, amount: u256) { +// self.ownable.assert_only_owner(); +// self.reentrancy_guard.start(); + +// let token = IERC20Dispatcher { contract_address: token_address }; +// let contract_address = get_contract_address(); + +// assert(token.balance_of(contract_address) >= amount, 'Insufficient balance'); + +// let owner = self.ownable.owner(); +// token.transfer(owner, amount); + +// // Emit Withdrawn event +// let ts: u256 = get_block_timestamp().into(); +// self.emit(Event::Withdrawn(Withdrawn { token_address, amount, owner, timestamp: ts +// })); + +// self.reentrancy_guard.end(); +// } + +// fn seize_shares(ref self: ContractState, shareholder: ContractAddress) { +// self.ownable.assert_only_owner(); +// self.pausable.assert_not_paused(); + +// let shares_to_seize = self.shareholders.read(shareholder); +// assert(shares_to_seize > 0, 'No shares to seize'); + +// // Transfer shares to owner +// let owner = self.ownable.owner(); +// let owner_shares = self.shareholders.read(owner); + +// self.shareholders.write(shareholder, 0); +// self.shareholders.write(owner, owner_shares + shares_to_seize); + +// // Remove from shareholder list +// self.is_shareholder_map.write(shareholder, false); +// self._remove_shareholder(shareholder); + +// self.emit(SharesSeized { shareholder, share_amount: shares_to_seize }); +// } + +// fn pause(ref self: ContractState) { +// self.ownable.assert_only_owner(); +// self.pausable.pause(); +// } + +// fn unpause(ref self: ContractState) { +// self.ownable.assert_only_owner(); +// self.pausable.unpause(); +// } + +// fn set_partner_share_cap( +// ref self: ContractState, token_address: ContractAddress, cap: u256, +// ) { +// self.ownable.assert_only_owner(); +// self._validate_token(token_address); + +// self.partner_share_cap.write(token_address, cap); +// self.emit(PartnerShareCapSet { token_address, cap }); +// } + +// fn remove_partner_share_cap(ref self: ContractState, token_address: ContractAddress) { +// self.ownable.assert_only_owner(); +// self._validate_token(token_address); + +// self.partner_share_cap.write(token_address, 0); +// self.emit(PartnerShareCapSet { token_address, cap: 0 }); +// } + +// // View functions +// fn get_available_shares(self: @ContractState) -> u256 { +// self.available_shares.read() +// } + +// fn get_shares(self: @ContractState, addr: ContractAddress) -> u256 { +// self.shareholders.read(addr) +// } + +// fn get_shareholders(self: @ContractState) -> Array { +// let mut shareholders = ArrayTrait::new(); +// let count = self.shareholder_count.read(); +// let mut i = 0; + +// while i < count { +// let shareholder = self.shareholder_addresses.read(i); +// if self.is_shareholder_map.read(shareholder) { +// shareholders.append(shareholder); +// } +// i += 1; +// } + +// shareholders +// } + +// fn is_shareholder(self: @ContractState, addr: ContractAddress) -> bool { +// self.is_shareholder_map.read(addr) +// } + +// fn get_usdt_address(self: @ContractState) -> ContractAddress { +// self.usdt_address.read() +// } + +// fn get_usdc_address(self: @ContractState) -> ContractAddress { +// self.usdc_address.read() +// } + +// fn get_total_share_valuation(self: @ContractState) -> u256 { +// self.total_share_valuation.read() +// } + +// fn get_presale_share_valuation(self: @ContractState) -> u256 { +// self.presale_share_valuation.read() +// } + +// fn get_presale_shares(self: @ContractState) -> u256 { +// self.presale_shares.read() +// } + +// fn get_shares_sold(self: @ContractState) -> u256 { +// self.shares_sold.read() +// } + +// fn is_presale_active(self: @ContractState) -> bool { +// self.is_presale_active.read() +// } + +// fn get_partner_share_cap(self: @ContractState, token_address: ContractAddress) -> u256 { +// self.partner_share_cap.read(token_address) +// } + +// fn get_shares_minted_by_partner( +// self: @ContractState, token_address: ContractAddress, +// ) -> u256 { +// self.shares_minted_by_partner.read(token_address) +// } + +// fn get_owner(self: @ContractState) -> ContractAddress { +// self.ownable.owner() +// } + +// fn transfer_owner(ref self: ContractState, new_owner: ContractAddress) { +// self.ownable.transfer_ownership(new_owner); +// } + +// /// Leaves the contract without an owner, prioritize `transfer_owner` for changes in the +// /// access control +// fn renounce_owner(ref self: ContractState) { +// self.ownable.renounce_ownership(); +// } +// } + +// #[generate_trait] +// impl InternalImpl of InternalTrait { +// fn _validate_token(self: @ContractState, token_address: ContractAddress) { +// let usdt = self.usdt_address.read(); +// let usdc = self.usdc_address.read(); +// assert(token_address == usdt || token_address == usdc, 'Invalid token address'); +// } + +// fn _remove_shareholder(ref self: ContractState, shareholder: ContractAddress) { +// let count = self.shareholder_count.read(); +// let mut i = 0; + +// while i < count { +// if self.shareholder_addresses.read(i) == shareholder { +// // Move last element to this position +// let last_shareholder = self.shareholder_addresses.read(count - 1); +// self.shareholder_addresses.write(i, last_shareholder); +// self.shareholder_count.write(count - 1); +// break; +// } +// i += 1; +// }; +// } +// } +// } + use starknet::ContractAddress; +// ============================================================================ +// INTERFACE DEFINITION +// ============================================================================ + #[starknet::interface] pub trait IBigIncGenesis { - // Core functionality + // ========== CORE FUNCTIONALITY ========== fn mint_share(ref self: TContractState, token_address: ContractAddress); fn transfer_share(ref self: TContractState, to: ContractAddress, share_amount: u256); fn donate(ref self: TContractState, token_address: ContractAddress, amount: u256); - // View functions + // ========== VIEW FUNCTIONS ========== fn get_available_shares(self: @TContractState) -> u256; fn get_shares(self: @TContractState, addr: ContractAddress) -> u256; - fn get_shareholders(self: @TContractState) -> Array; + fn get_shareholder_count(self: @TContractState) -> u32; + fn get_shareholder_at_index(self: @TContractState, index: u32) -> ContractAddress; fn is_shareholder(self: @TContractState, addr: ContractAddress) -> bool; fn get_usdt_address(self: @TContractState) -> ContractAddress; fn get_usdc_address(self: @TContractState) -> ContractAddress; @@ -20,7 +533,7 @@ pub trait IBigIncGenesis { fn get_shares_sold(self: @TContractState) -> u256; fn is_presale_active(self: @TContractState) -> bool; - // Owner functions + // ========== OWNER FUNCTIONS ========== fn withdraw(ref self: TContractState, token_address: ContractAddress, amount: u256); fn seize_shares(ref self: TContractState, shareholder: ContractAddress); fn set_partner_share_cap(ref self: TContractState, token_address: ContractAddress, cap: u256); @@ -28,11 +541,11 @@ pub trait IBigIncGenesis { fn pause(ref self: TContractState); fn unpause(ref self: TContractState); - // Partner view functions + // ========== PARTNER FUNCTIONS ========== fn get_partner_share_cap(self: @TContractState, token_address: ContractAddress) -> u256; fn get_shares_minted_by_partner(self: @TContractState, token_address: ContractAddress) -> u256; - // Ownable functions + // ========== OWNERSHIP FUNCTIONS ========== fn get_owner(self: @TContractState) -> ContractAddress; fn transfer_owner(ref self: TContractState, new_owner: ContractAddress); fn renounce_owner(ref self: TContractState); @@ -47,6 +560,10 @@ pub trait IBigIncGenesis { fn get_partner_token_rate(self: @TContractState, token_address: ContractAddress) -> u256; } +// ============================================================================ +// MAIN CONTRACT +// ============================================================================ + #[starknet::contract] pub mod BigIncGenesis { use core::traits::Into; @@ -103,6 +620,7 @@ pub mod BigIncGenesis { is_shareholder_map: Map, shareholder_addresses: Map, shareholder_count: u32, + shareholder_index: Map, // Partner token rates (tokens required for 1 full share) partner_token_rates: Map, } @@ -116,14 +634,20 @@ pub mod BigIncGenesis { PausableEvent: PausableComponent::Event, #[flat] ReentrancyGuardEvent: ReentrancyGuardComponent::Event, + // ========== SHARE EVENTS ========== ShareMinted: ShareMinted, - PresaleEnded: PresaleEnded, TransferShare: TransferShare, - Donate: Donate, SharesSeized: SharesSeized, AllSharesSold: AllSharesSold, + // ========== PRESALE EVENTS ========== + PresaleEnded: PresaleEnded, + // ========== FINANCIAL EVENTS ========== + Donate: Donate, Withdrawn: Withdrawn, + // ========== PARTNER EVENTS ========== PartnerShareCapSet: PartnerShareCapSet, + // ========== ADMINISTRATIVE EVENTS ========== + PartnerShareMinted: PartnerShareMinted, } @@ -135,9 +659,6 @@ pub mod BigIncGenesis { amount: u256, } - #[derive(Drop, starknet::Event)] - struct PresaleEnded {} - #[derive(Drop, starknet::Event)] struct TransferShare { #[key] @@ -147,14 +668,6 @@ pub mod BigIncGenesis { share_amount: u256, } - #[derive(Drop, starknet::Event)] - struct Donate { - #[key] - donor: ContractAddress, - token_address: ContractAddress, - amount: u256, - } - #[derive(Drop, starknet::Event)] struct SharesSeized { #[key] @@ -165,6 +678,16 @@ pub mod BigIncGenesis { #[derive(Drop, starknet::Event)] struct AllSharesSold {} + #[derive(Drop, starknet::Event)] + struct PresaleEnded {} + + #[derive(Drop, starknet::Event)] + struct Donate { + #[key] + donor: ContractAddress, + token_address: ContractAddress, + amount: u256, + } #[derive(Drop, starknet::Event)] struct Withdrawn { @@ -182,6 +705,11 @@ pub mod BigIncGenesis { cap: u256, } + + // ============================================================================ + // CONSTRUCTOR + // ============================================================================ + #[derive(Drop, starknet::Event)] struct PartnerShareMinted { #[key] @@ -193,6 +721,7 @@ pub mod BigIncGenesis { rate: u256, } + #[constructor] fn constructor( ref self: ContractState, @@ -200,31 +729,32 @@ pub mod BigIncGenesis { usdc_address: ContractAddress, owner: ContractAddress, ) { - // Initialize components + // Validate constructor parameters + self._validate_constructor_params(usdt_address, usdc_address, owner); + + // Initialize configuration self.ownable.initializer(owner); + // (No .initializer() for pausable or reentrancy_guard in Cairo 2.11.2) // Set token addresses self.usdt_address.write(usdt_address); self.usdc_address.write(usdc_address); - // Initialize share parameters - self.total_share_valuation.write(680000000000_u256); // $680k with 6 decimals - self.presale_share_valuation.write(457143000000_u256); // $457k with 6 decimals - self.presale_shares.write(21000000_u256); // 21% shares - self.shares_sold.write(0_u256); - self.available_shares.write(82000000_u256); // 82% available after 18% to owner - self.is_presale_active.write(true); - - // Assign 18% shares to owner - let owner_shares = 18000000_u256; - self.shareholders.write(owner, owner_shares); - self.is_shareholder_map.write(owner, true); - self.shareholder_addresses.write(0, owner); - self.shareholder_count.write(1); + // Initialize share economics + self._initialize_share_economics(); + + // Assign initial shares to owner + self._assign_owner_shares(owner); } + // ============================================================================ + // EXTERNAL IMPLEMENTATION + // ============================================================================ + #[abi(embed_v0)] impl BigIncGenesisImpl of IBigIncGenesis { + // ========== CORE FUNCTIONALITY ========== + fn mint_share(ref self: ContractState, token_address: ContractAddress) { self.pausable.assert_not_paused(); self.reentrancy_guard.start(); @@ -237,60 +767,34 @@ pub mod BigIncGenesis { // Check if all shares are sold if self.available_shares.read() == 0 { self.emit(AllSharesSold {}); + self.reentrancy_guard.end(); return; } + // Get token allowance and validate let token = IERC20Dispatcher { contract_address: token_address }; let amount = token.allowance(caller, contract_address); + self._validate_mint_amount(amount, caller, token); - assert(amount > 0, 'Amount must be > 0'); - assert(token.balance_of(caller) >= amount, 'Insufficient token balance'); + // Calculate shares to buy + let current_price = self._get_current_share_price(); + let shares_bought = self._calculate_shares(amount, current_price); + self._validate_shares_purchase(shares_bought); - let current_price = if self.is_presale_active.read() { - self.presale_share_valuation.read() - } else { - self.total_share_valuation.read() - }; + // Check partner share cap + self._check_partner_share_cap(token_address, shares_bought); - let shares_bought = (amount * 100000000_u256) / current_price; - let new_shares_sold = self.shares_sold.read() + shares_bought; + // Update share statistics + self._update_share_statistics(shares_bought); - assert(shares_bought <= self.available_shares.read(), 'Exceeds available shares'); + // Check if presale should end + self._check_presale_end(); - // Check partner share cap if set - let partner_cap = self.partner_share_cap.read(token_address); - if partner_cap > 0 { - let current_partner_shares = self.shares_minted_by_partner.read(token_address); - assert( - current_partner_shares + shares_bought <= partner_cap, - 'Exceeds partner share cap', - ); - self - .shares_minted_by_partner - .write(token_address, current_partner_shares + shares_bought); - } + // Add shareholder if new + self._add_shareholder_if_new(caller); - self.shares_sold.write(new_shares_sold); - - if self.is_presale_active.read() && new_shares_sold >= self.presale_shares.read() { - self.is_presale_active.write(false); - self.emit(PresaleEnded {}); - } - - // Add to shareholder if new - if self.shareholders.read(caller) == 0 { - let current_count = self.shareholder_count.read(); - self.shareholder_addresses.write(current_count, caller); - self.shareholder_count.write(current_count + 1); - self.is_shareholder_map.write(caller, true); - } - - // Update balances - let current_shares = self.shareholders.read(caller); - self.shareholders.write(caller, current_shares + shares_bought); - - let available = self.available_shares.read(); - self.available_shares.write(available - shares_bought); + // Update shareholder balance + self._update_shareholder_balance(caller, shares_bought); // Transfer tokens token.transfer_from(caller, contract_address, amount); @@ -302,27 +806,26 @@ pub mod BigIncGenesis { fn transfer_share(ref self: ContractState, to: ContractAddress, share_amount: u256) { self.pausable.assert_not_paused(); + self._validate_transfer_params(to, share_amount); let caller = get_caller_address(); - assert(to != 0.try_into().unwrap(), 'Cannot transfer to zero address'); - let sender_shares = self.shareholders.read(caller); - assert(sender_shares >= share_amount, 'Insufficient shares'); + self._validate_sufficient_shares(sender_shares, share_amount); + // Update sender balance self.shareholders.write(caller, sender_shares - share_amount); + // Update recipient balance let recipient_shares = self.shareholders.read(to); self.shareholders.write(to, recipient_shares + share_amount); + // Add recipient to shareholder list if new if recipient_shares == 0 { - let current_count = self.shareholder_count.read(); - self.shareholder_addresses.write(current_count, to); - self.shareholder_count.write(current_count + 1); - self.is_shareholder_map.write(to, true); + self._add_shareholder_to_list(to); } + // Remove sender from shareholder list if no shares left if sender_shares == share_amount { - self.is_shareholder_map.write(caller, false); self._remove_shareholder(caller); } @@ -330,41 +833,41 @@ pub mod BigIncGenesis { } fn donate(ref self: ContractState, token_address: ContractAddress, amount: u256) { + self._validate_donation_params(amount, token_address); + let caller = get_caller_address(); let contract_address = get_contract_address(); - - assert(amount > 0, 'Amount must be > 0'); - assert( - token_address == self.usdt_address.read() - || token_address == self.usdc_address.read(), - 'USDC/USDT Only', - ); let token = IERC20Dispatcher { contract_address: token_address }; + // Validate balance and allowance assert(token.balance_of(caller) >= amount, 'Insufficient balance'); assert(token.allowance(caller, contract_address) >= amount, 'Insufficient allowance'); + // Transfer tokens token.transfer_from(caller, contract_address, amount); self.emit(Donate { donor: caller, token_address, amount }); } + // ========== OWNER FUNCTIONS ========== + fn withdraw(ref self: ContractState, token_address: ContractAddress, amount: u256) { self.ownable.assert_only_owner(); self.reentrancy_guard.start(); + self._validate_withdrawal_params(amount, token_address); let token = IERC20Dispatcher { contract_address: token_address }; let contract_address = get_contract_address(); + let owner = self.ownable.owner(); + // Validate contract balance assert(token.balance_of(contract_address) >= amount, 'Insufficient balance'); - let owner = self.ownable.owner(); + // Transfer tokens to owner token.transfer(owner, amount); - // Emit Withdrawn event - let ts: u256 = get_block_timestamp().into(); - self.emit(Event::Withdrawn(Withdrawn { token_address, amount, owner, timestamp: ts })); - + let timestamp: u256 = get_block_timestamp().into(); + self.emit(Withdrawn { token_address, amount, owner, timestamp }); self.reentrancy_guard.end(); } @@ -383,22 +886,11 @@ pub mod BigIncGenesis { self.shareholders.write(owner, owner_shares + shares_to_seize); // Remove from shareholder list - self.is_shareholder_map.write(shareholder, false); self._remove_shareholder(shareholder); self.emit(SharesSeized { shareholder, share_amount: shares_to_seize }); } - fn pause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.pause(); - } - - fn unpause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.unpause(); - } - fn set_partner_share_cap( ref self: ContractState, token_address: ContractAddress, cap: u256, ) { @@ -417,6 +909,19 @@ pub mod BigIncGenesis { self.emit(PartnerShareCapSet { token_address, cap: 0 }); } + + fn pause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.pause(); + } + + fn unpause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.unpause(); + } + + // ========== VIEW FUNCTIONS ========== + fn mint_partner_share( ref self: ContractState, token_address: ContractAddress, token_amount: u256, ) { @@ -514,6 +1019,7 @@ pub mod BigIncGenesis { self.partner_token_rates.read(token_address) } + fn get_available_shares(self: @ContractState) -> u256 { self.available_shares.read() } @@ -522,24 +1028,18 @@ pub mod BigIncGenesis { self.shareholders.read(addr) } - fn get_shareholders(self: @ContractState) -> Array { - let mut shareholders = ArrayTrait::new(); - let count = self.shareholder_count.read(); - let mut i = 0; - - while i < count { - let shareholder = self.shareholder_addresses.read(i); - if self.is_shareholder_map.read(shareholder) { - shareholders.append(shareholder); - } - i += 1; - } + fn get_shareholder_count(self: @ContractState) -> u32 { + self.shareholder_count.read() + } - shareholders + fn get_shareholder_at_index(self: @ContractState, index: u32) -> ContractAddress { + let count = self.shareholder_count.read(); + assert(index < count, 'Index out of bounds'); + self.shareholder_addresses.read(index) } fn is_shareholder(self: @ContractState, addr: ContractAddress) -> bool { - self.is_shareholder_map.read(addr) + self.shareholders.read(addr) > 0 } fn get_usdt_address(self: @ContractState) -> ContractAddress { @@ -588,35 +1088,204 @@ pub mod BigIncGenesis { self.ownable.transfer_ownership(new_owner); } - /// Leaves the contract without an owner, prioritize `transfer_owner` for changes in the - /// access control fn renounce_owner(ref self: ContractState) { self.ownable.renounce_ownership(); } } + // ============================================================================ + // INTERNAL IMPLEMENTATION + // ============================================================================ + #[generate_trait] impl InternalImpl of InternalTrait { + // ========== CONSTRUCTOR HELPERS ========== + + fn _validate_constructor_params( + self: @ContractState, + usdt_address: ContractAddress, + usdc_address: ContractAddress, + owner: ContractAddress, + ) { + let zero_address: ContractAddress = 0.try_into().unwrap(); + assert(usdt_address != zero_address, 'Invalid USDT address'); + assert(usdc_address != zero_address, 'Invalid USDC address'); + assert(owner != zero_address, 'Invalid owner address'); + assert(usdt_address != usdc_address, 'USDT and USDC must be different'); + } + + fn _initialize_share_economics(ref self: ContractState) { + self.total_share_valuation.write(680000000000_u256); // $680k with 6 decimals + self.presale_share_valuation.write(457143000000_u256); // $457k with 6 decimals + self.presale_shares.write(21000000_u256); // 21% shares + self.shares_sold.write(0_u256); + self.available_shares.write(82000000_u256); // 82% available after 18% to owner + self.is_presale_active.write(true); + } + + fn _assign_owner_shares(ref self: ContractState, owner: ContractAddress) { + let owner_shares = 18000000_u256; // 18% shares + self.shareholders.write(owner, owner_shares); + self.shareholder_addresses.write(0, owner); + self.shareholder_index.write(owner, 0); + self.shareholder_count.write(1); + } + + // ========== VALIDATION HELPERS ========== + fn _validate_token(self: @ContractState, token_address: ContractAddress) { let usdt = self.usdt_address.read(); let usdc = self.usdc_address.read(); assert(token_address == usdt || token_address == usdc, 'Invalid token address'); } + fn _validate_mint_amount( + self: @ContractState, amount: u256, caller: ContractAddress, token: IERC20Dispatcher, + ) { + assert(amount > 0, 'Amount must be > 0'); + assert(token.balance_of(caller) >= amount, 'Insufficient token balance'); + } + + fn _validate_shares_purchase(self: @ContractState, shares_bought: u256) { + assert(shares_bought > 0, 'No shares to buy'); + assert(shares_bought <= self.available_shares.read(), 'Exceeds available shares'); + } + + fn _validate_transfer_params( + self: @ContractState, to: ContractAddress, share_amount: u256, + ) { + let zero_address: ContractAddress = 0.try_into().unwrap(); + assert(to != zero_address, 'Cannot transfer to zero address'); + assert(share_amount > 0, 'Share amount must be > 0'); + } + + fn _validate_sufficient_shares( + self: @ContractState, sender_shares: u256, share_amount: u256, + ) { + assert(sender_shares >= share_amount, 'Insufficient shares'); + } + + fn _validate_donation_params( + self: @ContractState, amount: u256, token_address: ContractAddress, + ) { + assert(amount > 0, 'Amount must be > 0'); + self._validate_token(token_address); + } + + fn _validate_withdrawal_params( + self: @ContractState, amount: u256, token_address: ContractAddress, + ) { + assert(amount > 0, 'Amount must be > 0'); + self._validate_token(token_address); + } + + fn _validate_new_owner(self: @ContractState, new_owner: ContractAddress) { + let zero_address: ContractAddress = 0.try_into().unwrap(); + assert(new_owner != zero_address, 'Invalid owner'); + } + + // ========== ACCESS CONTROL ========== + + fn _assert_only_owner(self: @ContractState) { + let caller = get_caller_address(); + let owner = self.ownable.owner(); + assert(caller == owner, 'Caller is not the owner'); + } + + fn _assert_not_paused(self: @ContractState) { + self.pausable.assert_not_paused(); + } + + // ========== SHARE CALCULATION HELPERS ========== + + fn _get_current_share_price(self: @ContractState) -> u256 { + if self.is_presale_active.read() { + self.presale_share_valuation.read() + } else { + self.total_share_valuation.read() + } + } + + fn _calculate_shares(self: @ContractState, amount: u256, price: u256) -> u256 { + let numerator = amount * 100000000_u256; + numerator / price + } + + // ========== PARTNER SHARE CAP HELPERS ========== + + fn _check_partner_share_cap( + ref self: ContractState, token_address: ContractAddress, shares_bought: u256, + ) { + let partner_cap = self.partner_share_cap.read(token_address); + if partner_cap > 0 { + let current_partner_shares = self.shares_minted_by_partner.read(token_address); + let new_partner_shares = current_partner_shares + shares_bought; + assert(new_partner_shares <= partner_cap, 'Exceeds partner share cap'); + self.shares_minted_by_partner.write(token_address, new_partner_shares); + } + } + + // ========== SHARE STATISTICS HELPERS ========== + + fn _update_share_statistics(ref self: ContractState, shares_bought: u256) { + let current_shares_sold = self.shares_sold.read(); + let new_shares_sold = current_shares_sold + shares_bought; + self.shares_sold.write(new_shares_sold); + + let available = self.available_shares.read(); + self.available_shares.write(available - shares_bought); + } + + fn _check_presale_end(ref self: ContractState) { + if self.is_presale_active.read() { + let new_shares_sold = self.shares_sold.read(); + let presale_shares = self.presale_shares.read(); + if new_shares_sold >= presale_shares { + self.is_presale_active.write(false); + self.emit(PresaleEnded {}); + } + } + } + + // ========== SHAREHOLDER MANAGEMENT HELPERS ========== + + fn _add_shareholder_if_new(ref self: ContractState, caller: ContractAddress) { + if self.shareholders.read(caller) == 0 { + self._add_shareholder_to_list(caller); + } + } + + fn _add_shareholder_to_list(ref self: ContractState, shareholder: ContractAddress) { + let current_count = self.shareholder_count.read(); + self.shareholder_addresses.write(current_count, shareholder); + self.shareholder_index.write(shareholder, current_count); + self.shareholder_count.write(current_count + 1); + } + + fn _update_shareholder_balance( + ref self: ContractState, shareholder: ContractAddress, shares_bought: u256, + ) { + let current_shares = self.shareholders.read(shareholder); + self.shareholders.write(shareholder, current_shares + shares_bought); + } + fn _remove_shareholder(ref self: ContractState, shareholder: ContractAddress) { let count = self.shareholder_count.read(); - let mut i = 0; - - while i < count { - if self.shareholder_addresses.read(i) == shareholder { - // Move last element to this position - let last_shareholder = self.shareholder_addresses.read(count - 1); - self.shareholder_addresses.write(i, last_shareholder); - self.shareholder_count.write(count - 1); - break; - } - i += 1; - }; + let index = self.shareholder_index.read(shareholder); + + if index == count - 1 { + // Last element, just decrement count + self.shareholder_count.write(count - 1); + } else { + // Move last element to this position + let last_shareholder = self.shareholder_addresses.read(count - 1); + self.shareholder_addresses.write(index, last_shareholder); + self.shareholder_index.write(last_shareholder, index); + self.shareholder_count.write(count - 1); + } + + // Clear the index for the removed shareholder + self.shareholder_index.write(shareholder, 0); } fn _validate_partner_token(self: @ContractState, token_address: ContractAddress) { diff --git a/contract_/src/ierc20.cairo b/contract_/src/ierc20.cairo new file mode 100644 index 0000000..558f421 --- /dev/null +++ b/contract_/src/ierc20.cairo @@ -0,0 +1,20 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; + fn decimals(self: @TContractState) -> u8; + + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} diff --git a/contract_/src/lib.cairo b/contract_/src/lib.cairo index 421f5ef..768df94 100644 --- a/contract_/src/lib.cairo +++ b/contract_/src/lib.cairo @@ -1 +1,3 @@ pub mod BigIncGenesis; +pub mod ierc20; +pub mod mockerc20; diff --git a/contract_/src/mockerc20.cairo b/contract_/src/mockerc20.cairo new file mode 100644 index 0000000..0eff9fc --- /dev/null +++ b/contract_/src/mockerc20.cairo @@ -0,0 +1,158 @@ +#[starknet::contract] +pub mod MockToken { + use core::num::traits::Zero; + use core::starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; + use starknet::event::EventEmitter; + use starknet::{ContractAddress, get_caller_address}; + use crate::ierc20::IERC20; + + #[storage] + pub struct Storage { + balances: Map, + allowances: Map< + (ContractAddress, ContractAddress), u256, + >, // Mapping<(owner, spender), amount> + token_name: ByteArray, + symbol: ByteArray, + decimal: u8, + total_supply: u256, + owner: ContractAddress, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + Transfer: Transfer, + Approval: Approval, + } + + #[derive(Drop, starknet::Event)] + pub struct Transfer { + #[key] + from: ContractAddress, + #[key] + to: ContractAddress, + amount: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct Approval { + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + value: u256, + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.token_name.write("BIGINC"); + self.symbol.write("BIGINC"); + self.decimal.write(18); + self.owner.write(owner); + } + + #[abi(embed_v0)] + impl MockTokenImpl of IERC20 { + fn total_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + let balance = self.balances.entry(account).read(); + + balance + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress, + ) -> u256 { + let allowance = self.allowances.entry((owner, spender)).read(); + + allowance + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + + let sender_prev_balance = self.balances.entry(sender).read(); + let recipient_prev_balance = self.balances.entry(recipient).read(); + + assert(sender_prev_balance >= amount, 'Insufficient amount'); + + self.balances.entry(sender).write(sender_prev_balance - amount); + self.balances.entry(recipient).write(recipient_prev_balance + amount); + + assert( + self.balances.entry(recipient).read() > recipient_prev_balance, + 'Transaction failed', + ); + + self.emit(Transfer { from: sender, to: recipient, amount }); + + true + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool { + let spender = get_caller_address(); + + let spender_allowance = self.allowances.entry((sender, spender)).read(); + let sender_balance = self.balances.entry(sender).read(); + let recipient_balance = self.balances.entry(recipient).read(); + + assert(amount <= spender_allowance, 'amount exceeds allowance'); + assert(amount <= sender_balance, 'amount exceeds balance'); + + self.allowances.entry((sender, spender)).write(spender_allowance - amount); + self.balances.entry(sender).write(sender_balance - amount); + self.balances.entry(recipient).write(recipient_balance + amount); + + self.emit(Transfer { from: sender, to: recipient, amount }); + + true + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + + self.allowances.entry((caller, spender)).write(amount); + + self.emit(Approval { owner: caller, spender, value: amount }); + + true + } + + fn name(self: @ContractState) -> ByteArray { + self.token_name.read() + } + + fn symbol(self: @ContractState) -> ByteArray { + self.symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + self.decimal.read() + } + + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let previous_total_supply = self.total_supply.read(); + let previous_balance = self.balances.entry(recipient).read(); + + self.total_supply.write(previous_total_supply + amount); + self.balances.entry(recipient).write(previous_balance + amount); + + let zero_address = Zero::zero(); + + self.emit(Transfer { from: zero_address, to: recipient, amount }); + + true + } + } +} diff --git a/contract_/tests/test_contract.cairo b/contract_/tests/test_contract.cairo index 8b13789..3ee85fc 100644 --- a/contract_/tests/test_contract.cairo +++ b/contract_/tests/test_contract.cairo @@ -1 +1,569 @@ +use contract_::BigIncGenesis::{IBigIncGenesisDispatcher, IBigIncGenesisDispatcherTrait}; +use contract_::ierc20::{IERC20Dispatcher, IERC20DispatcherTrait}; +use core::byte_array::ByteArray; +use core::option::OptionTrait; +use core::result::ResultTrait; +use core::traits::TryInto; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events, + start_cheat_caller_address, stop_cheat_caller_address, +}; +use starknet::{ContractAddress, get_block_timestamp}; + + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +const USDT_INITIAL_SUPPLY: u256 = 1000000000000_u256; // 1M tokens with 6 decimals +const USDC_INITIAL_SUPPLY: u256 = 1000000000000_u256; // 1M tokens with 6 decimals + +fn OWNER() -> ContractAddress { + 'owner'.try_into().unwrap() +} + + +fn USER1() -> ContractAddress { + 'user1'.try_into().unwrap() +} + +fn USER2() -> ContractAddress { + 'user2'.try_into().unwrap() +} + + +// ============================================================================ +// SETUP FUNCTIONS +// ============================================================================ + +fn __deploy_mock_erc20__(admin: ContractAddress) -> ContractAddress { + let mock_erc20_class_hash = declare("MockToken").unwrap().contract_class(); + let mut calldata = array![]; + admin.serialize(ref calldata); + let (mocktoken_contract_address, _) = mock_erc20_class_hash.deploy(@calldata).unwrap(); + mocktoken_contract_address +} + +fn deploy_big_inc_genesis( + usdt_address: ContractAddress, usdc_address: ContractAddress, owner: ContractAddress, +) -> ContractAddress { + let contract = declare("BigIncGenesis").unwrap().contract_class(); + let mut constructor_calldata = array![]; + + usdt_address.serialize(ref constructor_calldata); + usdc_address.serialize(ref constructor_calldata); + owner.serialize(ref constructor_calldata); + + let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); + contract_address +} + +fn setup_basic() -> (ContractAddress, ContractAddress, ContractAddress) { + let owner: ContractAddress = OWNER(); + let user1: ContractAddress = USER1(); + let user2: ContractAddress = USER2(); + let usdt_address = __deploy_mock_erc20__(owner); + let usdc_address = __deploy_mock_erc20__(owner); + // Mint tokens to users for both USDT and USDC + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let usdc = IERC20Dispatcher { contract_address: usdc_address }; + start_cheat_caller_address(usdt_address, owner); + usdt.mint(user1, USDT_INITIAL_SUPPLY); + usdt.mint(user2, USDT_INITIAL_SUPPLY); + stop_cheat_caller_address(usdt_address); + start_cheat_caller_address(usdc_address, owner); + usdc.mint(user1, USDC_INITIAL_SUPPLY); + usdc.mint(user2, USDC_INITIAL_SUPPLY); + stop_cheat_caller_address(usdc_address); + let big_inc_address = deploy_big_inc_genesis(usdt_address, usdc_address, owner); + (big_inc_address, usdt_address, usdc_address) +} + +fn setup_with_shares() -> (ContractAddress, ContractAddress, ContractAddress, ContractAddress) { + let (big_inc_address, usdt_address, usdc_address) = setup_basic(); + let user1: ContractAddress = USER1(); + + // Mint some shares to user1 + let amount = 4571430000_u256; // ~1M shares at presale price + start_cheat_caller_address(usdt_address, user1); + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + usdt.approve(big_inc_address, amount); + stop_cheat_caller_address(usdt_address); + + start_cheat_caller_address(big_inc_address, user1); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + big_inc.mint_share(usdt_address); + stop_cheat_caller_address(big_inc_address); + + (big_inc_address, usdt_address, usdc_address, user1) +} + +// ============================================================================ +// CONSTRUCTOR TESTS +// ============================================================================ + +#[test] +fn test_constructor_initial_state() { + let (big_inc_address, usdt_address, usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let owner: ContractAddress = OWNER(); + + // Test initial state + assert(big_inc.get_owner() == owner, 'Owner not set correctly'); + assert(big_inc.get_usdt_address() == usdt_address, 'USDT address not set'); + assert(big_inc.get_usdc_address() == usdc_address, 'USDC address not set'); + assert(big_inc.get_shares(owner) == 18000000_u256, 'Owner shares not set'); + assert(big_inc.get_shareholder_count() == 1, 'Shareholder count not set'); + assert(big_inc.is_presale_active(), 'Presale should be active'); + assert(big_inc.get_available_shares() == 82000000_u256, 'Available shares not set'); +} + +// ============================================================================ +// SHARE MINTING TESTS +// ============================================================================ + +#[test] +fn test_mint_share_basic() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + + // Approve and mint shares + let amount = 4571430000_u256; // ~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 not minted'); + assert(big_inc.is_shareholder(user1), 'User1 should be shareholder'); + assert(big_inc.get_shareholder_count() == 2, 'Shareholder count should be 2'); +} + +#[test] +fn test_mint_share_presale_end() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + + // Presale cap: 21_000_000 shares + // Contract share calculation: shares = (amount * 100_000_000) / 457_143_000_000 + // To get just below the cap, mint 20_900_000 shares + let first_shares = 20_900_000_u256; + let presale_price = 457_143_000_000_u256; + let first_amount = (first_shares * presale_price) / 100_000_000_u256; + + start_cheat_caller_address(usdt_address, user1); + usdt.approve(big_inc_address, first_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); + + // Presale should still be active + assert(big_inc.is_presale_active(), 'Presale should still be active'); + + // Mint again to cross the cap + let second_shares = 200_000_u256; + let second_amount = (second_shares * presale_price) / 100_000_000_u256; + + start_cheat_caller_address(usdt_address, user1); + usdt.approve(big_inc_address, second_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); + + // Now presale should be ended + assert(!big_inc.is_presale_active(), 'Presale should be ended'); + assert(big_inc.get_shares_sold() >= 21_000_000_u256, 'Should have sold presale shares'); +} + +#[test] +fn test_mint_share_multiple_users() { + let (big_inc_address, usdt_address, usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let usdc = IERC20Dispatcher { contract_address: usdc_address }; + let user1: ContractAddress = USER1(); + let user2: ContractAddress = USER2(); + + // User1 mints with USDT + let amount1 = 4571430000_u256; + start_cheat_caller_address(usdt_address, user1); + usdt.approve(big_inc_address, amount1); + 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); + + // User2 mints with USDC + let amount2 = 4571430000_u256; + start_cheat_caller_address(usdc_address, user2); + usdc.approve(big_inc_address, amount2); + stop_cheat_caller_address(usdc_address); + + start_cheat_caller_address(big_inc_address, user2); + big_inc.mint_share(usdc_address); + stop_cheat_caller_address(big_inc_address); + + assert(big_inc.is_shareholder(user1), 'User1 should be shareholder'); + assert(big_inc.is_shareholder(user2), 'User2 should be shareholder'); + assert(big_inc.get_shareholder_count() == 3, 'Shareholder count should be 3'); +} + +// ============================================================================ +// SHARE TRANSFER TESTS +// ============================================================================ + +#[test] +fn test_transfer_share_basic() { + let (big_inc_address, _usdt_address, _usdc_address, user1) = setup_with_shares(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let user2: ContractAddress = USER2(); + + let user1_shares = big_inc.get_shares(user1); + let transfer_amount = user1_shares / 2; + + start_cheat_caller_address(big_inc_address, user1); + big_inc.transfer_share(user2, transfer_amount); + stop_cheat_caller_address(big_inc_address); + + assert(big_inc.get_shares(user1) == user1_shares - transfer_amount, 'User1 shares not updated'); + assert(big_inc.get_shares(user2) == transfer_amount, 'User2 shares not received'); + assert(big_inc.is_shareholder(user2), 'User2 should be shareholder'); +} + +#[test] +fn test_transfer_share_full_balance() { + let (big_inc_address, _usdt_address, _usdc_address, user1) = setup_with_shares(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let user2: ContractAddress = USER2(); + + let user1_shares = big_inc.get_shares(user1); + + start_cheat_caller_address(big_inc_address, user1); + big_inc.transfer_share(user2, user1_shares); + stop_cheat_caller_address(big_inc_address); + + assert(big_inc.get_shares(user1) == 0, 'User1 should have 0 shares'); + assert(big_inc.get_shares(user2) == user1_shares, 'User2 should have all shares'); + assert(!big_inc.is_shareholder(user1), 'User1 should not be shareholder'); + assert(big_inc.is_shareholder(user2), 'User2 should be shareholder'); +} + +// ============================================================================ +// DONATION TESTS +// ============================================================================ + +#[test] +fn test_donate_usdt() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + + let amount = 1000000_u256; // 1 token with 6 decimals + 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.donate(usdt_address, amount); + stop_cheat_caller_address(big_inc_address); + + // Verify donation was successful + let contract_balance = usdt.balance_of(big_inc_address); + assert(contract_balance >= amount, 'Contract should'); +} + +#[test] +fn test_donate_usdc() { + let (big_inc_address, _usdt_address, usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdc = IERC20Dispatcher { contract_address: usdc_address }; + let user1: ContractAddress = USER1(); + + let amount = 1000000_u256; // 1 token with 6 decimals + start_cheat_caller_address(usdc_address, user1); + usdc.approve(big_inc_address, amount); + stop_cheat_caller_address(usdc_address); + + start_cheat_caller_address(big_inc_address, user1); + big_inc.donate(usdc_address, amount); + stop_cheat_caller_address(big_inc_address); + + // Verify donation was successful + let contract_balance = usdc.balance_of(big_inc_address); + assert(contract_balance >= amount, 'Contract should have'); +} + +// ============================================================================ +// OWNER FUNCTION TESTS +// ============================================================================ + +#[test] +fn test_withdraw_success() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let owner: ContractAddress = OWNER(); + + // First donate some tokens to the contract + let user1: ContractAddress = USER1(); + let amount = 1000000_u256; + 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.donate(usdt_address, amount); + stop_cheat_caller_address(big_inc_address); + + // Now withdraw as owner + let withdraw_amount = 500000_u256; + start_cheat_caller_address(big_inc_address, owner); + big_inc.withdraw(usdt_address, withdraw_amount); + stop_cheat_caller_address(big_inc_address); + + let owner_balance = usdt.balance_of(owner); + assert(owner_balance >= withdraw_amount, 'Owner should have '); +} + +#[test] +fn test_seize_shares_success() { + let (big_inc_address, _usdt_address, _usdc_address, user1) = setup_with_shares(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let owner: ContractAddress = OWNER(); + + let user1_shares = big_inc.get_shares(user1); + let owner_shares_before = big_inc.get_shares(owner); + + start_cheat_caller_address(big_inc_address, owner); + big_inc.seize_shares(user1); + stop_cheat_caller_address(big_inc_address); + + assert(big_inc.get_shares(user1) == 0, 'User1 shares should be seized'); + assert( + big_inc.get_shares(owner) == owner_shares_before + user1_shares, + 'Owner should have seized shares', + ); + assert(!big_inc.is_shareholder(user1), 'User1 should not be shareholder'); +} + +#[test] +fn test_pause_and_unpause() { + let (big_inc_address, _usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let owner: ContractAddress = OWNER(); + + // Pause contract + start_cheat_caller_address(big_inc_address, owner); + big_inc.pause(); + stop_cheat_caller_address(big_inc_address); + + // Unpause contract + start_cheat_caller_address(big_inc_address, owner); + big_inc.unpause(); + stop_cheat_caller_address(big_inc_address); + + // Contract should be unpaused + // Note: We can't directly check paused state, but we can verify by trying to mint + let (big_inc_address2, usdt_address, _usdc_address) = setup_basic(); + let big_inc2 = IBigIncGenesisDispatcher { contract_address: big_inc_address2 }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + + let amount = 4571430000_u256; + start_cheat_caller_address(usdt_address, user1); + usdt.approve(big_inc_address2, amount); + stop_cheat_caller_address(usdt_address); + + start_cheat_caller_address(big_inc_address2, user1); + big_inc2.mint_share(usdt_address); + stop_cheat_caller_address(big_inc_address2); + + assert(big_inc2.get_shares(user1) > 0, 'Should be able to '); +} + +// ============================================================================ +// OWNERSHIP TESTS +// ============================================================================ + +#[test] +fn test_transfer_ownership() { + let (big_inc_address, _usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let owner: ContractAddress = OWNER(); + let user1: ContractAddress = USER1(); + + start_cheat_caller_address(big_inc_address, owner); + big_inc.transfer_owner(user1); + stop_cheat_caller_address(big_inc_address); + + assert(big_inc.get_owner() == user1, 'Ownership should be transferred'); +} + +#[test] +fn test_renounce_ownership() { + let (big_inc_address, _usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let owner: ContractAddress = OWNER(); + + start_cheat_caller_address(big_inc_address, owner); + big_inc.renounce_owner(); + stop_cheat_caller_address(big_inc_address); + + let zero_address: ContractAddress = 0.try_into().unwrap(); + assert(big_inc.get_owner() == zero_address, 'Ownership should be renounced'); +} + +// ============================================================================ +// VIEW FUNCTION TESTS +// ============================================================================ + +#[test] +fn test_shareholder_management() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + let user2: ContractAddress = USER2(); + + // Mint shares to user1 + let amount = 4571430000_u256; + 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); + + // Transfer some shares to user2 + let user1_shares = big_inc.get_shares(user1); + let transfer_amount = user1_shares / 2; + + start_cheat_caller_address(big_inc_address, user1); + big_inc.transfer_share(user2, transfer_amount); + stop_cheat_caller_address(big_inc_address); + + // Test shareholder functions + assert(big_inc.is_shareholder(user1), 'User1 should be shareholder'); + assert(big_inc.is_shareholder(user2), 'User2 should be shareholder'); + assert(big_inc.get_shareholder_count() == 3, 'Should have 3 shareholders'); + + // Test shareholder at index + let shareholder_at_0 = big_inc.get_shareholder_at_index(0); + let shareholder_at_1 = big_inc.get_shareholder_at_index(1); + let shareholder_at_2 = big_inc.get_shareholder_at_index(2); + + assert(shareholder_at_0 == OWNER(), 'Index 0 should be owner'); + assert(shareholder_at_1 == user1 || shareholder_at_1 == user2, 'Index 1 should be'); + assert(shareholder_at_2 == user1 || shareholder_at_2 == user2, 'Index 2 should be'); +} + +#[test] +fn test_shareholder_removal() { + let (big_inc_address, _usdt_address, _usdc_address, user1) = setup_with_shares(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let owner: ContractAddress = OWNER(); + + let initial_count = big_inc.get_shareholder_count(); + let user1_shares = big_inc.get_shares(user1); + + // Transfer all shares to owner + start_cheat_caller_address(big_inc_address, user1); + big_inc.transfer_share(owner, user1_shares); + stop_cheat_caller_address(big_inc_address); + + assert(!big_inc.is_shareholder(user1), 'User1 should not be shareholder'); + assert(big_inc.get_shareholder_count() == initial_count - 1, 'Shareholder count'); +} + +// ============================================================================ +// INTEGRATION TESTS +// ============================================================================ + +#[test] +fn test_complete_share_lifecycle() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + let user2: ContractAddress = USER2(); + + // 1. Mint shares + let amount = 4571430000_u256; + 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); + + // 2. Transfer shares + let user1_shares = big_inc.get_shares(user1); + let transfer_amount = user1_shares / 2; + + start_cheat_caller_address(big_inc_address, user1); + big_inc.transfer_share(user2, transfer_amount); + stop_cheat_caller_address(big_inc_address); + + // 3. Donate tokens + let donate_amount = 1000000_u256; + start_cheat_caller_address(usdt_address, user1); + usdt.approve(big_inc_address, donate_amount); + stop_cheat_caller_address(usdt_address); + + start_cheat_caller_address(big_inc_address, user1); + big_inc.donate(usdt_address, donate_amount); + stop_cheat_caller_address(big_inc_address); + + // 4. Verify final state + assert(big_inc.get_shares(user1) == user1_shares - transfer_amount, 'User1 shares incorrect'); + assert(big_inc.get_shares(user2) == transfer_amount, 'User2 shares incorrect'); + assert(big_inc.is_shareholder(user1), 'User1 should be shareholder'); + assert(big_inc.is_shareholder(user2), 'User2 should be shareholder'); + assert(big_inc.get_shareholder_count() == 3, 'Shareholder count incorrect'); +} + +#[test] +fn test_presale_to_main_sale_transition() { + let (big_inc_address, usdt_address, _usdc_address) = setup_basic(); + let big_inc = IBigIncGenesisDispatcher { contract_address: big_inc_address }; + let usdt = IERC20Dispatcher { contract_address: usdt_address }; + let user1: ContractAddress = USER1(); + + // Initial state should be presale + assert(big_inc.is_presale_active(), 'Should start in presale'); + + // Mint enough to end presale + let presale_shares = big_inc.get_presale_shares(); + let presale_valuation = big_inc.get_presale_share_valuation(); + let amount_needed = (presale_shares * presale_valuation) / 100000000_u256; + + start_cheat_caller_address(usdt_address, user1); + usdt.approve(big_inc_address, amount_needed); + 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); + + // Presale should be ended + assert(!big_inc.is_presale_active(), 'Presale should be ended'); + assert(big_inc.get_shares_sold() >= presale_shares, 'Should have sold presale shares'); +} diff --git a/contract_/tests/test_partner_share_cap.cairo b/contract_/tests/test_partner_share_cap.cairo index 91bfaf1..48b0ab8 100644 --- a/contract_/tests/test_partner_share_cap.cairo +++ b/contract_/tests/test_partner_share_cap.cairo @@ -62,7 +62,6 @@ fn setup() -> (ContractAddress, ContractAddress, ContractAddress) { (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 }; @@ -78,8 +77,6 @@ fn test_set_partner_share_cap_success() { 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 }; @@ -91,8 +88,6 @@ fn test_set_partner_share_cap_not_owner() { 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 }; @@ -105,7 +100,6 @@ fn test_set_partner_share_cap_invalid_token() { 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 }; @@ -126,8 +120,6 @@ fn test_remove_partner_share_cap_success() { 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 }; @@ -139,7 +131,6 @@ fn test_remove_partner_share_cap_not_owner() { 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 }; @@ -152,7 +143,6 @@ fn test_get_partner_share_cap_default_zero() { 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 }; @@ -165,7 +155,6 @@ fn test_get_shares_minted_by_partner_default_zero() { 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 }; @@ -194,8 +183,6 @@ fn test_mint_share_respects_partner_cap() { 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 }; @@ -219,7 +206,6 @@ fn test_mint_share_exceeds_partner_cap() { 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 }; @@ -245,7 +231,6 @@ fn test_mint_share_without_partner_cap_works() { 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 }; @@ -272,7 +257,6 @@ fn test_partner_cap_separate_for_different_tokens() { 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 };