From 66cc9182e624617f2a4e03e9d51d3edd9465addb Mon Sep 17 00:00:00 2001 From: MSGhais Date: Thu, 30 Jan 2025 14:36:53 +0100 Subject: [PATCH 01/11] clean first iteration + change address --- .../launchpad/src/launchpad/launchpad.cairo | 151 +- .../src/launchpad/launchpad_not_clean.cairo | 1793 +++++++++++++++++ .../src/launchpad/launchpad_old.cairo | 14 +- .../cairo/launchpad/src/launchpad/unrug.cairo | 17 +- .../launchpad/src/tests/launchpad_tests.cairo | 6 +- .../launchpad/src/types/launchpad_types.cairo | 319 ++- packages/common/src/contracts.ts | 7 +- scripts/utils/escrow.ts | 4 +- 8 files changed, 2002 insertions(+), 309 deletions(-) create mode 100644 onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index d45316a99..bc88ae8be 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -1,7 +1,7 @@ use afk_launchpad::interfaces::launchpad::{ILaunchpadMarketplace}; use afk_launchpad::types::jediswap_types::{MintParams}; use afk_launchpad::types::launchpad_types::{ - MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, LaunchUpdated, + MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, TokenQuoteBuyCoin, TokenLaunch, SharesTokenUser, BondingType, Token, CreateLaunch, SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, @@ -64,7 +64,7 @@ pub mod LaunchpadMarketplace { contract_address_const, get_block_timestamp, get_contract_address, ClassHash }; use super::{ - StoredName, BuyToken, SellToken, CreateToken, LaunchUpdated, SharesTokenUser, MINTER_ROLE, + StoredName, BuyToken, SellToken, CreateToken, SharesTokenUser, MINTER_ROLE, ADMIN_ROLE, BondingType, Token, TokenLaunch, TokenQuoteBuyCoin, CreateLaunch, SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, MintParams, LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, @@ -222,7 +222,6 @@ pub mod LaunchpadMarketplace { BuyToken: BuyToken, SellToken: SellToken, CreateToken: CreateToken, - LaunchUpdated: LaunchUpdated, CreateLaunch: CreateLaunch, SetJediswapV2Factory: SetJediswapV2Factory, SetJediswapNFTRouterV2: SetJediswapNFTRouterV2, @@ -321,11 +320,6 @@ pub mod LaunchpadMarketplace { self.total_launch.write(0); self.protocol_fee_percent.write(MID_FEE_PROTOCOL); self.creator_fee_percent.write(MIN_FEE_CREATOR); - // self.factory_address.write(factory_address); - // self.ekubo_registry.write(ekubo_registry); - // self.core.write(core); - // self.positions.write(positions); - // self.ekubo_exchange_address.write(ekubo_exchange_address); self.unrug_liquidity_address.write(unrug_liquidity_address); } @@ -346,7 +340,27 @@ pub mod LaunchpadMarketplace { // Claim your tokens #[abi(embed_v0)] impl LaunchpadMarketplace of ILaunchpadMarketplace { - // ADMIN + // Views functions public + + fn get_default_token(self: @ContractState) -> TokenQuoteBuyCoin { + self.default_token.read() + } + + fn get_threshold_liquidity(self: @ContractState) -> u256 { + self.threshold_liquidity.read() + } + + fn get_coin_launch(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { + self.launched_coins.read(key_user) + } + + fn get_share_of_user_by_contract( + self: @ContractState, owner: ContractAddress, key_user: ContractAddress, + ) -> SharesTokenUser { + self.shares_by_users.entry(owner).entry(key_user).read() + } + + // ADMINS function with Access control fn set_token(ref self: ContractState, token_quote: TokenQuoteBuyCoin) { self.accesscontrol.assert_only_role(ADMIN_ROLE); self.is_tokens_buy_enable.entry(token_quote.token_address).write(token_quote); @@ -498,58 +512,10 @@ pub mod LaunchpadMarketplace { self.amount_to_paid_create_token.write(amount_to_paid_create_token); } - // Views functions public - - fn get_default_token(self: @ContractState) -> TokenQuoteBuyCoin { - self.default_token.read() - } - - fn get_threshold_liquidity(self: @ContractState) -> u256 { - self.threshold_liquidity.read() - } - - fn get_coin_launch(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { - self.launched_coins.read(key_user) - } - - fn get_share_of_user_by_contract( - self: @ContractState, owner: ContractAddress, key_user: ContractAddress, - ) -> SharesTokenUser { - self.shares_by_users.entry(owner).entry(key_user).read() - } - - // fn get_all_coins(self: @ContractState) -> Span { - // let max_coin_id = self.total_token.read() + 1; - // let mut coins: Array = ArrayTrait::new(); - // let mut i = 0; //Since the stream id starts from 0 - // loop { - // if i >= max_coin_id {} - // let coin = self.array_coins.read(i); - // if coin.owner.is_zero() { - // break coins.span(); - // } - // coins.append(coin); - // i += 1; - // } - // } - - // fn get_all_launch(self: @ContractState) -> Span { - // let max_key_id = self.total_launch.read() + 1; - // let mut launches: Array = ArrayTrait::new(); - // let mut i = 0; //Since the stream id starts from 0 - // loop { - // if i >= max_key_id {} - // let pool = self.array_launched_coins.read(i); - // if pool.owner.is_zero() { - // break launches.span(); - // } - // launches.append(pool); - // i += 1; - // } - // } // User call // Create token for an user + // Send the supply to the caller fn create_token( ref self: ContractState, recipient: ContractAddress, @@ -569,9 +535,9 @@ pub mod LaunchpadMarketplace { initial_supply, contract_address_salt, is_unruggable, - recipient, - caller, - contract_address, + recipient, // Send supply to this address + caller, // Owner of the address, Ownable access + contract_address, // Factory address to set_launched and others stuff ); token_address @@ -596,9 +562,9 @@ pub mod LaunchpadMarketplace { initial_supply, contract_address_salt, is_unruggable, - contract_address, - caller, - contract_address, + contract_address, // Send supply to this address + caller, // Owner of the address, Ownable access + contract_address, // Factory address to set_launched and others stuff ); self ._launch_token( @@ -699,6 +665,8 @@ pub mod LaunchpadMarketplace { let is_fees_protocol_buy_enabled = self.is_fees_protocol_buy_enabled.read(); // TODO edge cases + // Verify rounding of Fees + // Check fees send to protocol and liquidity and carefully verify if is_fees_protocol_enabled && is_fees_protocol_buy_enabled { // if pool_coin.liquidity_raised < quote_amount { // total_price = pool_coin.liquidity_raised.clone(); @@ -791,11 +759,6 @@ pub mod LaunchpadMarketplace { // pool_coin.liquidity_raised += remain_liquidity; // Amount quote buy with fees deducted if enabled // Optionally, re-calculate the quote amount based on the amount to ensure consistency - // println!("total_price {:?}", total_price); - // println!("update pool"); - // println!("subtract amount and available supply"); - // println!("available supply {:?}", pool_coin.available_supply); - // println!("amount {:?}", amount); pool_coin.liquidity_raised += remain_quote_to_liquidity; pool_coin.price = total_price; // TODO TEST @@ -806,7 +769,6 @@ pub mod LaunchpadMarketplace { pool_coin.available_supply = 0; pool_coin.total_token_holded += coin_amount; } else { - // println!("subtract amount"); pool_coin.total_token_holded += coin_amount; pool_coin.available_supply -= coin_amount; } @@ -819,7 +781,6 @@ pub mod LaunchpadMarketplace { .read(); let mut share_user = old_share.clone(); - // println!("update share"); if share_user.owner.is_zero() { share_user = SharesTokenUser { @@ -838,10 +799,6 @@ pub mod LaunchpadMarketplace { share_user.amount_buy += coin_amount; } // pool_coin.price = total_price / amount; - - // println!("threshold {:?}", threshold); - // println!("pool_coin.liquidity_raised {:?}", pool_coin.liquidity_raised); - // let mc = (pool_coin.price * total_supply_memecoin); // TODO add liquidity launch // TOTAL_SUPPLY / 5 @@ -861,15 +818,11 @@ pub mod LaunchpadMarketplace { .entry(coin_address) .write(share_user.clone()); - // println!("check threshold"); // TODO finish test and fix // Add slipage threshold // Fix price of the last - self.launched_coins.entry(coin_address).write(pool_coin.clone()); - if pool_coin.liquidity_raised >= threshold { - // println!("emit liquidity can be added"); self .emit( LiquidityCanBeAdded { @@ -878,11 +831,9 @@ pub mod LaunchpadMarketplace { quote_token_address: pool_coin.token_quote.token_address.clone(), } ); - // TODO fix add liquidity ekubo or other DEX - // let launch_dex= lauch.dex_launch + // TODO V2 Add Liquidity DEX selected by USER + // let launch_dex= pool_coin.dex_launch; // self._add_liquidity(coin_address, SupportedExchanges::Ekubo); - - // println!("try add liquidity"); self._add_liquidity_ekubo(coin_address); pool_coin.is_liquidity_launch = true; } @@ -890,8 +841,6 @@ pub mod LaunchpadMarketplace { // Update the state of the pool self.launched_coins.entry(coin_address).write(pool_coin.clone()); - // println!("emit buy token"); - self .emit( BuyToken { @@ -1570,15 +1519,15 @@ pub mod LaunchpadMarketplace { assert(allowance >= amount_needed, errors::INSUFFICIENT_ALLOWANCE); if allowance >= amount_needed { - // println!("allowance > amount_needed{:?}", allowance > amount_needed); memecoin .transfer_from( caller, get_contract_address(), total_supply - balance_contract ); + // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); + } } - // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); self.launched_coins.entry(coin_address).write(launch_token_pump.clone()); let total_launch = self.total_launch.read(); @@ -1617,7 +1566,6 @@ pub mod LaunchpadMarketplace { }; let launch = self.launched_coins.read(coin_address); - // assert(launch.liquidity_raised >= launch.threshold_liquidity, 'no threshold raised'); assert(launch.is_liquidity_launch == false, errors::LIQUIDITY_ALREADY_LAUNCHED); let threshold_liquidity = launch.threshold_liquidity.clone(); @@ -1641,17 +1589,12 @@ pub mod LaunchpadMarketplace { // Edge case to check and fix if fees enabled let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); - // println!("tick_spacing {:?}", tick_spacing); // let bound = calculate_aligned_bound_mag(starting_price, 2, tick_spacing); // TODO verify condition EKUBO // Verify conditions // Add these debug prints - // assert(bound % tick_spacing == 0, 'Bound not aligned'); // assert(bound <= MAX_TICK.try_into().unwrap(), 'Tick magnitude too high'); - // println!("Starting Price: {}", starting_price.mag); - // println!("Calculated Bound: {}", bound); - // println!("Tick Spacing: {}", tick_spacing); let bound_spacing = tick_spacing * 2; let pool_params = EkuboPoolParameters { fee: 0xc49ba5e353f7d00000000000000000, // TODO fee optional by user @@ -1672,10 +1615,10 @@ pub mod LaunchpadMarketplace { contract_address: launch.token_quote.token_address.clone() }; - // Assertion: Check if the contract has enough quote tokens to transfer to liquidity // TODO fix this // HIGH SECURITY // Can drained funk if edge case approximation issue + // Assertion: Check if the contract has enough quote tokens to transfer to liquidity // Edge case of approximation estimation amount and fees can cause it let contract_quote_balance = quote_token.balance_of(get_contract_address()); // println!("add liquidity contract_quote_balance final {:?}", contract_quote_balance); @@ -1684,22 +1627,11 @@ pub mod LaunchpadMarketplace { if contract_quote_balance < lp_quote_supply && contract_quote_balance < launch.threshold_liquidity { - // println!( - // "contract_quote_balance < lp_quote_supply - // && contract_quote_balance < launch.threshold_liquidity: {}", - // contract_quote_balance < lp_quote_supply - // && contract_quote_balance < launch.threshold_liquidity - // ); - // println!("launch.threshold_liquidity: {}", launch.threshold_liquidity.clone()); // TODO just calculate the difference of round and substract it - // let new_lp_quote_rounded= lp_quote_supply - contract_quote_balance; // lp_quote_supply = new_lp_quote_rounded.clone(); - lp_quote_supply = contract_quote_balance.clone(); } - // println!("liquidity raised: {}", lp_quote_supply.clone()); - let params = EkuboUnrugLaunchParameters { // owner: launch.owner, // TODO add optional parameters to be select LIQ percent to // be lock to Unrug at some point @@ -1713,17 +1645,12 @@ pub mod LaunchpadMarketplace { }; let position_ekubo_address = unrug_liquidity.get_position_ekubo_address(); - // quote_token.approve(position_ekubo_address, lp_quote_supply); quote_token.approve(unrug_liquidity_address, lp_quote_supply); - // Approve Memecoin let memecoin = IERC20Dispatcher { contract_address: coin_address }; - // memecoin.approve(position_ekubo_address, lp_supply); memecoin.approve(unrug_liquidity_address, lp_supply); - let (id, position) = unrug_liquidity.launch_on_ekubo(coin_address, params); let id_cast: u256 = id.try_into().unwrap(); - // Set token launched let mut launch_to_update = self.launched_coins.read(coin_address); launch_to_update.is_liquidity_launch = true; @@ -1786,20 +1713,16 @@ pub mod LaunchpadMarketplace { let threshold_liquidity = launch.threshold_liquidity.clone(); let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; let mut threshold = threshold_liquidity - slippage_threshold; - // assert(launch.liquidity_raised >= threshold, errors::NO_THRESHOLD_RAISED); - let quote_address = launch.token_quote.token_address.clone(); let lp_supply = launch.initial_pool_supply.clone(); let quote_supply = launch.liquidity_raised.clone(); let unlock_time = starknet::get_block_timestamp() + DEFAULT_MIN_LOCKTIME; - // let (id, position) = unrug_liquidity.launch_on_jediswap(coin_address, params); let id_cast = unrug_liquidity .launch_on_jediswap( coin_address, quote_address, lp_supply, quote_supply, unlock_time, owner ); - // TODO // Add Locked position // let (id, position) = unrug_liquidity.launch_on_jediswap(coin_address, quote_address, diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo new file mode 100644 index 000000000..68ae3a6d9 --- /dev/null +++ b/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo @@ -0,0 +1,1793 @@ +use afk_launchpad::interfaces::launchpad::{ILaunchpadMarketplace}; +use afk_launchpad::types::jediswap_types::{MintParams}; +use afk_launchpad::types::launchpad_types::{ + MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, + TokenQuoteBuyCoin, TokenLaunch, SharesTokenUser, BondingType, Token, CreateLaunch, + SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, LiquidityCreated, + LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, + LaunchParameters, EkuboLP, CallbackData, EkuboLaunchParameters, LaunchCallback, LiquidityType, + EkuboLiquidityParameters, LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams + // MemecoinCreated, MemecoinLaunched +}; +use starknet::ClassHash; +use starknet::ContractAddress; + +#[starknet::contract] +pub mod LaunchpadMarketplace { + use afk_launchpad::interfaces::launchpad::{ILaunchpadMarketplace}; + use afk_launchpad::interfaces::unrug::{ + IUnrugLiquidityDispatcher, IUnrugLiquidityDispatcherTrait, + // Event as LaunchpadEvent + }; + use afk_launchpad::launchpad::calcul::launch::{ + get_initial_price, get_amount_by_type_of_coin_or_quote + }; + use afk_launchpad::launchpad::calcul::linear::{ + calculate_starting_price_launch, get_coin_amount_by_quote_amount + }; + use afk_launchpad::launchpad::errors; + use afk_launchpad::launchpad::math::{PercentageMath, pow_256}; + use afk_launchpad::launchpad::utils::{ + sort_tokens, get_initial_tick_from_starting_price, get_next_tick_bounds, unique_count, + calculate_aligned_bound_mag + }; + use afk_launchpad::tokens::erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use afk_launchpad::tokens::memecoin::{IMemecoinDispatcher, IMemecoinDispatcherTrait}; + use afk_launchpad::utils::{sqrt}; + use core::num::traits::Zero; + use ekubo::components::clear::{IClearDispatcher, IClearDispatcherTrait}; + + use ekubo::interfaces::core::{ICoreDispatcher, ICoreDispatcherTrait, ILocker}; + use ekubo::interfaces::erc20::{ + IERC20Dispatcher as EKIERC20Dispatcher, IERC20DispatcherTrait as EKIERC20DispatcherTrait + }; + use ekubo::types::bounds::{Bounds}; + use ekubo::types::keys::PoolKey; + use ekubo::types::{i129::i129}; + + use openzeppelin::access::accesscontrol::{AccessControlComponent}; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_upgrades::interface::IUpgradeable; + + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, // Stor + StoragePointerReadAccess, + StoragePointerWriteAccess, StoragePathEntry, + // MutableEntryStoragePathEntry, StorableEntryReadAccess, StorageAsPathReadForward, + // MutableStorableEntryReadAccess, MutableStorableEntryWriteAccess, + // StorageAsPathWriteForward,PathableStorageEntryImpl + }; + use starknet::syscalls::deploy_syscall; + use starknet::{ + ContractAddress, get_caller_address, storage_access::StorageBaseAddress, + contract_address_const, get_block_timestamp, get_contract_address, ClassHash + }; + use super::{ + StoredName, BuyToken, SellToken, CreateToken, SharesTokenUser, MINTER_ROLE, + ADMIN_ROLE, BondingType, Token, TokenLaunch, TokenQuoteBuyCoin, CreateLaunch, + SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, MintParams, + LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, + EkuboPoolParameters, LaunchParameters, EkuboLP, LiquidityType, CallbackData, + EkuboLaunchParameters, LaunchCallback, EkuboLiquidityParameters, LiquidityParameters, + EkuboUnrugLaunchParameters, AdminsFeesParams + // MemecoinCreated, MemecoinLaunched + }; + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + const MAX_SUPPLY: u256 = 100_000_000; + const INITIAL_SUPPLY: u256 = MAX_SUPPLY / 5; + const MAX_STEPS_LOOP: u256 = 100; + + // TODO add optional parameters to be select LIQ percent to be lock to Unrug at some point + // Total supply / LIQUIDITY_RATIO + // Get the 20% of Bonding curve going to Liquidity + // Liquidity can be lock to Unrug + const LIQUIDITY_RATIO: u256 = 5; // Divid by 5 the total supply. + // TODO add with a enabled pay boolean to be free at some point + const PAY_TO_LAUNCH: u256 = 1; // amount in the coin used + const LIQUIDITY_PERCENTAGE: u256 = 2000; //20% + const MIN_FEE_PROTOCOL: u256 = 10; //0.1% + const MAX_FEE_PROTOCOL: u256 = 1000; //10% + const MID_FEE_PROTOCOL: u256 = 100; //1% + const SLIPPAGE_THRESHOLD: u256 = 100; //1% + + const MIN_FEE_CREATOR: u256 = 100; //1% + const MID_FEE_CREATOR: u256 = 1000; //10% + const MAX_FEE_CREATOR: u256 = 5000; //50% + + const BPS: u256 = 10_000; // 100% = 10_000 bps + const SCALE_FACTOR: u256 = + 100_000_000_000_000_000_u256; // Scale factor decimals place for price division and others stuff + + // MVP + // FIXED SUPPLY because edge cases not finish testing hard between threshold/supply + const FIXED_SUPPLY: u256 = 1_000_000_000_000_000_000_000_000_000_u256; // 1_000_000_000 + + // Unrug params + + // CHANGE it + /// The maximum percentage of the total supply that can be allocated to the team. + /// This is to prevent the team from having too much control over the supply. + const MAX_SUPPLY_PERCENTAGE_TEAM_ALLOCATION: u16 = 1_000; // 10% + + /// The maximum number of holders one can specify when launching. + /// This is to prevent the contract from being is_launched with a large number of holders. + /// Once reached, transfers are disabled until the memecoin is is_launched. + const MAX_HOLDERS_LAUNCH: u8 = 10; + + const DEFAULT_MIN_LOCKTIME: u64 = 15_721_200; + // const MAX_TRANSACTION_AMOUNT: u256 = 1_000_000 * pow_256(10, 18); + + // fn _validate_transaction_size(amount: u256) { + // assert(amount <= MAX_TRANSACTION_AMOUNT, 'transaction too large'); + // } + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + // Admin & others contract + coin_class_hash: ClassHash, + quote_tokens: Map::, + exchange_configs: Map, + quote_token: ContractAddress, + protocol_fee_destination: ContractAddress, + // User states + token_created: Map::, + launched_coins: Map::, + // distribute_team_alloc: Map::>, + metadata_coins: Map::, + shares_by_users: Map::>, + bonding_type: Map::, + array_launched_coins: Map::, + array_coins: Map::, + tokens_created: Map::, + launch_created: Map::, + // Admin params + default_init_supply: u256, + is_default_init_supply: bool, + admins_fees_params: AdminsFeesParams, + // Parameters + is_tokens_buy_enable: Map::, + default_token: TokenQuoteBuyCoin, + dollar_price_launch_pool: u256, + dollar_price_create_token: u256, + dollar_price_percentage: u256, + starting_price: u256, + threshold_liquidity: u256, + threshold_market_cap: u256, + liquidity_raised_amount_in_dollar: u256, + protocol_fee_percent: u256, + creator_fee_percent: u256, + is_fees_protocol: bool, + step_increase_linear: u256, + // Admins params fees + is_fees_protocol_sell_enabled: bool, + is_fees_protocol_buy_enabled: bool, + is_fees_protocol_enabled: bool, + is_fees_enabled: bool, + is_custom_launch_enable: bool, + // For create token + token_address_to_paid_create_token: ContractAddress, + token_address_to_paid_launch: ContractAddress, + amount_to_paid_create_token: u256, + is_paid_create_token_enable: bool, + is_custom_token_enable: bool, + // For launch token + amount_to_paid_launch: u256, + is_paid_launch_enable: bool, + is_create_token_paid: bool, + // Stats + total_token: u64, + total_launch: u64, + // TODO check edge case supply for Bonding curve + // HIGH SECURITY RISK + // EDGE CASE SUPPLY AND THRESHOLD + max_supply_launch: u256, + min_supply_launch: u256, + // External contract + address_jediswap_factory_v2: ContractAddress, + address_jediswap_nft_router_v2: ContractAddress, + address_ekubo_factory: ContractAddress, + address_ekubo_router: ContractAddress, + unrug_liquidity_address: ContractAddress, + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + StoredName: StoredName, + BuyToken: BuyToken, + SellToken: SellToken, + CreateToken: CreateToken, + CreateLaunch: CreateLaunch, + SetJediswapV2Factory: SetJediswapV2Factory, + SetJediswapNFTRouterV2: SetJediswapNFTRouterV2, + LiquidityCreated: LiquidityCreated, + LiquidityCanBeAdded: LiquidityCanBeAdded, + TokenClaimed: TokenClaimed, + MetadataCoinAdded: MetadataCoinAdded, + // MemecoinCreated: MemecoinCreated, + // MemecoinLaunched: MemecoinLaunched, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + admin: ContractAddress, + starting_price: u256, + token_address: ContractAddress, + step_increase_linear: u256, + coin_class_hash: ClassHash, + threshold_liquidity: u256, + threshold_market_cap: u256, + unrug_liquidity_address: ContractAddress, + ) { + self.coin_class_hash.write(coin_class_hash); + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(MINTER_ROLE, admin); + self.accesscontrol._grant_role(ADMIN_ROLE, admin); + + // TODO + // Launch and Create fees to false by default + // Still not test + self.is_paid_create_token_enable.write(false); + self.is_paid_launch_enable.write(false); + self.amount_to_paid_launch.write(1_u256); + self.amount_to_paid_create_token.write(1_u256); + + // TODO + // Fees protocol to true by default + // Still not test wisely + // Rounding issues after fees can happens. + + self.is_fees_protocol_buy_enabled.write(false); + self.is_fees_protocol_sell_enabled.write(false); + self.is_fees_protocol_enabled.write(false); + + // TODO + // Fees neabled by default + // Audit for fees calculation, rounding and edges cases + // fix BOUNDS_TICK_SPACINGS issue if fees are enabled + // EDGE CASE HIGH RISK = DRAIN VALUES, BLOCKING FUNCTIONS, ERRORS + + // self.is_fees_protocol_buy_enabled.write(false); + // self.is_fees_protocol_sell_enabled.write(false); + + self.is_fees_protocol_buy_enabled.write(true); + self.is_fees_protocol_sell_enabled.write(true); + self.is_fees_protocol_enabled.write(true); + + let admins_fees_params = AdminsFeesParams { + token_address_to_paid_launch: token_address, + token_address_to_paid_create_token: token_address, + amount_to_paid_launch: 1_u256, + amount_to_paid_create_token: 1_u256, + is_fees_protocol_buy_enabled: true, + is_fees_protocol_sell_enabled: true, + is_fees_protocol_enabled: true, + is_paid_create_token_enable: false, + is_paid_launch_enable: false, + }; + self.admins_fees_params.write(admins_fees_params); + + let init_token = TokenQuoteBuyCoin { + token_address: token_address, + starting_price, + price: starting_price, + is_enable: true, + step_increase_linear + }; + self.is_custom_launch_enable.write(false); + self.is_custom_token_enable.write(false); + self.default_token.write(init_token.clone()); + self.starting_price.write(init_token.starting_price); + + self.threshold_liquidity.write(threshold_liquidity); + self.threshold_market_cap.write(threshold_market_cap); + self.protocol_fee_destination.write(admin); + self.step_increase_linear.write(step_increase_linear); + self.total_token.write(0); + self.total_launch.write(0); + self.protocol_fee_percent.write(MID_FEE_PROTOCOL); + self.creator_fee_percent.write(MIN_FEE_CREATOR); + self.unrug_liquidity_address.write(unrug_liquidity_address); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + // This function can only be called by the ADMIN + self.accesscontrol.assert_only_role(ADMIN_ROLE); + // Replace the class hash upgrading the contract + self.upgradeable.upgrade(new_class_hash); + } + } + + // Public functions inside an impl block + // Create token + // Launch token with Bonding curve type: Linear, Exponential, more to come + // Buy and Sell a token in the pool + // Claim your tokens + #[abi(embed_v0)] + impl LaunchpadMarketplace of ILaunchpadMarketplace { + // Views functions public + + fn get_default_token(self: @ContractState) -> TokenQuoteBuyCoin { + self.default_token.read() + } + + fn get_threshold_liquidity(self: @ContractState) -> u256 { + self.threshold_liquidity.read() + } + + fn get_coin_launch(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { + self.launched_coins.read(key_user) + } + + fn get_share_of_user_by_contract( + self: @ContractState, owner: ContractAddress, key_user: ContractAddress, + ) -> SharesTokenUser { + self.shares_by_users.entry(owner).entry(key_user).read() + } + + // ADMINS function with Access control + fn set_token(ref self: ContractState, token_quote: TokenQuoteBuyCoin) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_tokens_buy_enable.entry(token_quote.token_address).write(token_quote); + } + + fn set_default_token(ref self: ContractState, default_token: TokenQuoteBuyCoin) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.default_token.write(default_token); + } + + fn set_default_init_supply(ref self: ContractState, default_init_supply: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.default_init_supply.write(default_init_supply); + } + + fn set_force_default_init_supply(ref self: ContractState, is_default_init_supply: bool) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_default_init_supply.write(is_default_init_supply); + } + + fn set_is_fees_protocol_enabled(ref self: ContractState, is_fees_protocol_enabled: bool) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_fees_protocol_enabled.write(is_fees_protocol_enabled); + } + + fn set_is_fees_protocol_buy_enabled( + ref self: ContractState, is_fees_protocol_buy_enabled: bool + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + + self.is_fees_protocol_buy_enabled.write(is_fees_protocol_buy_enabled); + } + + fn set_is_fees_protocol_sell_enabled( + ref self: ContractState, is_fees_protocol_sell_enabled: bool + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_fees_protocol_sell_enabled.write(is_fees_protocol_sell_enabled); + } + + fn set_is_fees(ref self: ContractState, is_fees_protocol_enabled: bool) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_fees_protocol_buy_enabled.write(is_fees_protocol_enabled); + self.is_fees_protocol_sell_enabled.write(is_fees_protocol_enabled); + self.is_fees_protocol_enabled.write(is_fees_protocol_enabled); + } + + fn set_protocol_fee_percent(ref self: ContractState, protocol_fee_percent: u256) { + // assert(protocol_fee_percent < MAX_FEE_PROTOCOL, 'protocol_fee_too_high'); + // assert(protocol_fee_percent > MIN_FEE_PROTOCOL, 'protocol_fee_too_low'); + assert(protocol_fee_percent < MAX_FEE_PROTOCOL, errors::PROTOCOL_FEE_TOO_HIGH); + assert(protocol_fee_percent > MIN_FEE_PROTOCOL, errors::PROTOCOL_FEE_TOO_LOW); + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.protocol_fee_percent.write(protocol_fee_percent); + } + + fn set_protocol_fee_destination( + ref self: ContractState, protocol_fee_destination: ContractAddress + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.protocol_fee_destination.write(protocol_fee_destination); + } + + fn set_unrug_liquidity_address( + ref self: ContractState, unrug_liquidity_address: ContractAddress + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.unrug_liquidity_address.write(unrug_liquidity_address); + } + + fn set_creator_fee_percent(ref self: ContractState, creator_fee_percent: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + assert(creator_fee_percent < MAX_FEE_CREATOR, errors::CREATOR_FEE_TOO_HIGH); + assert(creator_fee_percent > MIN_FEE_CREATOR, errors::CREATOR_FEE_TOO_LOW); + self.creator_fee_percent.write(creator_fee_percent); + } + + fn set_dollar_paid_coin_creation(ref self: ContractState, dollar_price: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.dollar_price_create_token.write(dollar_price); + } + + fn set_dollar_paid_launch_creation(ref self: ContractState, dollar_price: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.dollar_price_launch_pool.write(dollar_price); + } + + fn set_dollar_paid_finish_percentage(ref self: ContractState, bps: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.dollar_price_percentage.write(bps); + } + + // Set threshold liquidity + fn set_threshold_liquidity(ref self: ContractState, threshold_liquidity: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.threshold_liquidity.write(threshold_liquidity); + } + + fn set_exchanges_address( + ref self: ContractState, exchanges: Span<(SupportedExchanges, ContractAddress)> + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + let mut dex = exchanges; + // Add Exchanges configurations + loop { + match dex.pop_front() { + Option::Some((exchange, address)) => self + .exchange_configs + .entry(*exchange) + .write(*address), + Option::None => { break; } + } + }; + } + + fn set_class_hash(ref self: ContractState, class_hash: ClassHash) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.coin_class_hash.write(class_hash); + } + + + fn set_is_paid_create_token_enable( + ref self: ContractState, is_paid_create_token_enable: bool + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_paid_create_token_enable.write(is_paid_create_token_enable); + } + + fn set_is_paid_launch_enable(ref self: ContractState, is_paid_launch_enable: bool) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.is_paid_launch_enable.write(is_paid_launch_enable); + } + + fn set_token_address_for_action(ref self: ContractState, token_address: ContractAddress) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.token_address_to_paid_create_token.write(token_address); + self.token_address_to_paid_launch.write(token_address); + } + + fn set_amount_to_paid_launch(ref self: ContractState, amount_to_paid_launch: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.amount_to_paid_launch.write(amount_to_paid_launch); + } + + fn set_amount_to_paid_create_token( + ref self: ContractState, amount_to_paid_create_token: u256 + ) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.amount_to_paid_create_token.write(amount_to_paid_create_token); + } + + // User call + + // Create token for an user + // Send the supply to the caller + fn create_token( + ref self: ContractState, + recipient: ContractAddress, + symbol: ByteArray, + name: ByteArray, + initial_supply: u256, + contract_address_salt: felt252, + is_unruggable: bool + ) -> ContractAddress { + let caller = get_caller_address(); + let contract_address = get_contract_address(); + + let token_address = self + ._create_token( + symbol, + name, + initial_supply, + contract_address_salt, + is_unruggable, + recipient, // Send supply to this address + caller, // Owner of the address, Ownable access + contract_address, // Factory address to set_launched and others stuff + ); + + token_address + } + + // Create coin and launch in bonding curve + fn create_and_launch_token( + ref self: ContractState, + symbol: ByteArray, + name: ByteArray, + initial_supply: u256, + contract_address_salt: felt252, + is_unruggable: bool, + bonding_type: BondingType + ) -> ContractAddress { + let contract_address = get_contract_address(); + let caller = get_caller_address(); + let token_address = self + ._create_token( + symbol, + name, + initial_supply, + contract_address_salt, + is_unruggable, + contract_address, // Send supply to this address + caller, // Owner of the address, Ownable access + contract_address, // Factory address to set_launched and others stuff + ); + self + ._launch_token( + token_address, caller, contract_address, false, Option::Some(bonding_type) + ); + token_address + } + + // Launch coin to pool bonding curve + fn launch_token( + ref self: ContractState, coin_address: ContractAddress, bonding_type: BondingType + ) { + let caller = get_caller_address(); + let contract_address = get_contract_address(); + + let token = self.token_created.read(coin_address); + let is_unruggable = token.is_unruggable; + self + ._launch_token( + coin_address, + caller, + contract_address, + is_unruggable, + Option::Some(bonding_type) + ); + } + + // Buy coin by quote amount + // Get amount of coin receive based on token IN + // Calculate fees and protocol fees + // Transfer fees to the protocol fee destination + // Transfer quote to the user + // Update the state of the pool: Liquidity raised, available_supply, + // Update the share user: update amount_owned + // Emit the buy event + fn buy_coin_by_quote_amount( + ref self: ContractState, coin_address: ContractAddress, quote_amount: u256, + // ekubo_pool_params: Option + ) { + assert(quote_amount > 0, errors::AMOUNT_ZERO); + let caller = get_caller_address(); + let old_launch = self.launched_coins.read(coin_address); + assert(!old_launch.owner.is_zero(), errors::COIN_NOT_FOUND); + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + let mut pool_coin = old_launch.clone(); + // let total_supply_memecoin = memecoin.total_supply(); + let threshold_liquidity = pool_coin.threshold_liquidity.clone(); + + // TODO erc20 token transfer + let token_quote = old_launch.token_quote.clone(); + let quote_token_address = token_quote.token_address.clone(); + let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; + let protocol_fee_percent = self.protocol_fee_percent.read(); + + // IF AMOUNT COIN TO HAVE => GET AMOUNT QUOTE TO PAID + let mut total_price = quote_amount.clone(); + let old_price = pool_coin.price.clone(); + + // TODO edge case remaining supply to buy easily + // In case the user want to buy more than the threshold + // Give the available supply + // if total_price + old_launch.liquidity_raised.clone() > threshold_liquidity { + // total_price = threshold_liquidity - old_launch.liquidity_raised.clone(); + // amount = pool_coin.available_supply; + // amount_protocol_fee = total_price * protocol_fee_percent / BPS; + // // remain_liquidity = total_price - amount_protocol_fee; + // remain_liquidity = total_price; + // } else { + // amount = self + // ._get_amount_by_type_of_coin_or_quote(coin_address, total_price, false, + // true); + // // remain_liquidity = total_price - amount_protocol_fee; + // erc20 + // .transfer_from( + // get_caller_address(), + // self.protocol_fee_destination.read(), + // amount_protocol_fee + // ); + // // println!("remain_liquidity {:?}", remain_liquidity); + // erc20.transfer_from(get_caller_address(), get_contract_address(), + // remain_liquidity); + // } + + // TODO check if fees is enabled + let mut amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + let mut remain_liquidity = total_price - amount_protocol_fee; + let mut remain_quote_to_liquidity = total_price; + // let mut remain_quote_to_liquidity = total_price - amount_protocol_fee; + + let mut threshold_liquidity = pool_coin.threshold_liquidity.clone(); + // let mut amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; + // let mut threshold = threshold_liquidity; + let mut threshold = threshold_liquidity - slippage_threshold; + threshold_liquidity = threshold_liquidity - slippage_threshold; + // TODO without fees if it's correct + let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); + let is_fees_protocol_buy_enabled = self.is_fees_protocol_buy_enabled.read(); + + // TODO edge cases + // Verify rounding of Fees + // Check fees send to protocol and liquidity and carefully verify + if is_fees_protocol_enabled && is_fees_protocol_buy_enabled { + // if pool_coin.liquidity_raised < quote_amount { + // total_price = pool_coin.liquidity_raised.clone(); + // amount_protocol_fee = total_price * protocol_fee_percent / BPS; + // remain_quote_to_liquidity = pool_coin.liquidity_raised.clone() - + // amount_protocol_fee; + // } else { + // remain_quote_to_liquidity = quote_amount - amount_protocol_fee; + // } + remain_liquidity = total_price - amount_protocol_fee; + + remain_quote_to_liquidity = total_price - amount_protocol_fee; + // remain_quote_to_liquidity = quote_amount - amount_protocol_fee; + // TODO check slippage and fees + // TODO check threshold min and Supply threshold + // threshold = threshold_liquidity - (slippage_threshold * 2); // add slippage and + // fees + threshold = threshold_liquidity + - (slippage_threshold + amount_protocol_fee); // add slippage and fees + // threshold = threshold_liquidity + // - (slippage_threshold); // add slippage and fees + // threshold = threshold_liquidity + // - (amount_protocol_fee); // add slippage and fees + + erc20 + .transfer_from( + get_caller_address(), + self.protocol_fee_destination.read(), + amount_protocol_fee + ); + } + //new liquidity after purchase + let new_liquidity = pool_coin.liquidity_raised + remain_quote_to_liquidity; + + // Verify pool has sufficient available supply + //assertion + // Add slippage threshold + // assert(new_liquidity <= threshold, errors::THRESHOLD_LIQUIDITY_EXCEEDED); + + // Check if liquidity threshold raise + let threshold_liq = self.threshold_liquidity.read(); + let threshold_mc = self.threshold_market_cap.read(); + + // let mut amount = 0; + // Pay with quote token + // Transfer quote & coin + // TOdo fix issue price + let mut coin_amount = get_amount_by_type_of_coin_or_quote( + pool_coin.clone(), + coin_address.clone(), + remain_quote_to_liquidity.clone(), + false, + true + ); + + let mut amount_coin_received = coin_amount.clone(); + // TODO check available to buy + // TODO EDGES CASES + // Approximation amount + // TEST + // Can cause draining + // Todo check all memecoin amount possible to buy + // if pool_coin.total_token_holded < pool_coin.available_supply + amount { + // let amount = pool_coin.total_token_holded - pool_coin.available_supply; + // let amount_coin_received = pool_coin.total_token_holded - + // pool_coin.available_supply; + // // assert(amount < pool_coin.available_supply, + // errors::INSUFFICIENT_TOTAL_SUPPLY); + // // assert(pool_coin.total_token_holded < pool_coin.available_supply + amount, + // errors::INSUFFICIENT_TOTAL_SUPPLY); + + // } + // if pool_coin.available_supply < amount { + // amount = pool_coin.available_supply; + // } + assert(pool_coin.available_supply >= coin_amount, errors::INSUFFICIENT_SUPPLY); + // println!("amount memecoin to receive {:?}", amount); + // TODO readd this check and check why it's broken + // println!("transfer protocol fees {:?}", amount_protocol_fee); + // println!("transfer remain_liquidity {:?}", remain_quote_to_liquidity); + erc20 + .transfer_from( + get_caller_address(), get_contract_address(), remain_quote_to_liquidity + ); + + // Update the Stats of pool: + // Liquidity raised + // Available supply + // Token holded + // pool_coin.liquidity_raised += remain_liquidity; + // Amount quote buy with fees deducted if enabled + // Optionally, re-calculate the quote amount based on the amount to ensure consistency + // println!("total_price {:?}", total_price); + // println!("update pool"); + // println!("subtract amount and available supply"); + // println!("available supply {:?}", pool_coin.available_supply); + // println!("amount {:?}", amount); + pool_coin.liquidity_raised += remain_quote_to_liquidity; + pool_coin.price = total_price; + // TODO TEST + // EDGE CASE + // HIGH RISK = CAN DRAINED ALL POOL VALUE + // TODO check approximation, rounding and edges cases + if coin_amount >= pool_coin.available_supply { + pool_coin.available_supply = 0; + pool_coin.total_token_holded += coin_amount; + } else { + // println!("subtract amount"); + pool_coin.total_token_holded += coin_amount; + pool_coin.available_supply -= coin_amount; + } + + // Update share and coin stats for an user + let mut old_share = self + .shares_by_users + .entry(get_caller_address()) + .entry(coin_address) + .read(); + + let mut share_user = old_share.clone(); + // println!("update share"); + if share_user.owner.is_zero() { + share_user = + SharesTokenUser { + owner: get_caller_address(), + token_address: coin_address, + amount_owned: coin_amount, + amount_buy: coin_amount, + amount_sell: 0, + created_at: get_block_timestamp(), + total_paid: total_price, + is_claimable: true, + }; + } else { + share_user.total_paid += total_price; + share_user.amount_owned += coin_amount; + share_user.amount_buy += coin_amount; + } + // pool_coin.price = total_price / amount; + + // println!("threshold {:?}", threshold); + // println!("pool_coin.liquidity_raised {:?}", pool_coin.liquidity_raised); + + // let mc = (pool_coin.price * total_supply_memecoin); + // TODO add liquidity launch + // TOTAL_SUPPLY / 5 + // 20% go the liquidity + // 80% bought by others + + // TODO check reetrancy guard + // Update state + // self + // .shares_by_users + // .entry((get_caller_address(), coin_address)) + // .write(share_user.clone()); + + self + .shares_by_users + .entry(get_caller_address()) + .entry(coin_address) + .write(share_user.clone()); + + // println!("check threshold"); + // TODO finish test and fix + // Add slipage threshold + // Fix price of the last + + self.launched_coins.entry(coin_address).write(pool_coin.clone()); + + if pool_coin.liquidity_raised >= threshold { + // println!("emit liquidity can be added"); + self + .emit( + LiquidityCanBeAdded { + pool: pool_coin.token_address.clone(), + asset: pool_coin.token_address.clone(), + quote_token_address: pool_coin.token_quote.token_address.clone(), + } + ); + // TODO fix add liquidity ekubo or other DEX + // let launch_dex= lauch.dex_launch + // self._add_liquidity(coin_address, SupportedExchanges::Ekubo); + + // println!("try add liquidity"); + self._add_liquidity_ekubo(coin_address); + pool_coin.is_liquidity_launch = true; + } + + // Update the state of the pool + self.launched_coins.entry(coin_address).write(pool_coin.clone()); + + // println!("emit buy token"); + + self + .emit( + BuyToken { + caller: get_caller_address(), + token_address: coin_address, + amount: coin_amount, + price: total_price, + protocol_fee: amount_protocol_fee, + // creator_fee: 0, + last_price: old_price, + timestamp: get_block_timestamp(), + quote_amount: remain_quote_to_liquidity + // quote_amount: quote_amount + } + ); + } + + // params @coin_address @coin_amount + // Sell coin in the current bonding curve for Linear or Exponential atm + // Calculate amount of quote to receive by a coin_amount to sell + // Calculate fees and protocol fees & Transfer fees to the protocol fee destination + // Transfer quote to the user + // Update the state of the pool: Liquidity raised, available_supply, + // Update the share user: update amount_owned + // Emit the sell event + fn sell_coin(ref self: ContractState, coin_address: ContractAddress, coin_amount: u256) { + let old_pool = self.launched_coins.read(coin_address); + assert(!old_pool.owner.is_zero(), errors::COIN_SHARE_NOT_FOUND); + assert(old_pool.is_liquidity_launch == false, errors::TOKEN_ALREADY_TRADEABLE); + let caller = get_caller_address(); + let mut old_share = self + .shares_by_users + .entry(get_caller_address()) + .entry(coin_address) + .read(); + // Verify Amount owned + let mut share_user = old_share.clone(); + // assert(share_user.amount_owned >= coin_amount, 'above supply'); + + // TODO erc20 token transfer + let total_supply = old_pool.total_supply.clone(); + let token_quote = old_pool.token_quote.clone(); + let quote_token_address = token_quote.token_address.clone(); + + // Todo check user amount fee creator if needed + let creator_fee_percent = self.creator_fee_percent.read(); + let protocol_fee_percent = self.protocol_fee_percent.read(); + + // let amount_protocol_fee: u256 = coin_amount * protocol_fee_percent / BPS; + // let amount_creator_fee = coin_amount * creator_fee_percent / BPS; + + let mut remain_coin_amount = coin_amount.clone(); + // let mut remain_coin_amount = coin_amount; + // let remain_coin_amount = coin_amount - amount_protocol_fee; + + // TODO check + // Test edge case and calcul + // CAREFULLY CHECK AND TEST + let mut amount_owned = share_user.amount_owned.clone(); + // println!("sell share_user.amount_owned {:?}", share_user.amount_owned); + + assert( + share_user.amount_owned <= old_pool.total_token_holded, + errors::SUPPLY_ABOVE_TOTAL_OWNED + ); + + // if share_user.amount_owned >= old_pool.total_token_holded { + // assert( + // share_user.amount_owned >= old_pool.total_token_holded, + // errors::SUPPLY_ABOVE_TOTAL_OWNED + // ); + // } + + // TODO CHECK error even if used amount_owned as an input in test + // Edge case calculation rounding + // Use max owned + // CAREFULLY CHECK AND TEST + + // println!("sell remain_coin_amount {:?}", remain_coin_amount); + if share_user.amount_owned < remain_coin_amount { + // Used max amount_owned + remain_coin_amount = share_user.amount_owned.clone(); + // remain_coin_amount = share_user.amount_owned; + } + + let amount_protocol_fee: u256 = remain_coin_amount.clone() * protocol_fee_percent / BPS; + let amount_creator_fee = remain_coin_amount.clone() * creator_fee_percent / BPS; + assert(share_user.amount_owned >= remain_coin_amount, errors::ABOVE_SUPPLY); + // println!("sell remain_coin_amount edge {:?}", remain_coin_amount); + + let mut quote_amount_total = get_amount_by_type_of_coin_or_quote( + old_pool.clone(), coin_address.clone(), remain_coin_amount.clone(), true, false + ); + // println!("sell quote_amount_total received {:?}", quote_amount_total); + // println!("sell amount quote to receive {:?}", quote_amount_total); + + // println!("sell check quote_amount {:?}", quote_amount); + // println!("sell check liquidity_raised {:?}", old_pool.liquidity_raised); + + // TODO check fees + // TEST issue of Unrug + + let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); + let is_fees_protocol_sell_enabled = self.is_fees_protocol_sell_enabled.read(); + let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; + + let mut quote_amount_protocol_fee: u256 = quote_amount_total + * protocol_fee_percent + / BPS; + // let quote_amount = quote_amount_total - quote_amount_protocol_fee; + let mut quote_amount = quote_amount_total.clone(); + let mut total_quote_amount = quote_amount_total.clone(); + let mut quote_amount_received = quote_amount_total.clone(); + // CAREFULLY CHECK AND TEST + + if is_fees_protocol_enabled && is_fees_protocol_sell_enabled { + quote_amount = quote_amount_total - quote_amount_protocol_fee; + quote_amount_total = quote_amount_total - quote_amount_protocol_fee; + quote_amount_received = quote_amount_total - quote_amount_protocol_fee; + } + // println!("sell quote_amount received final {:?}", quote_amount); + // TODO + // Edge case calculation rounding + // HIGH SECURITY + // TODO due to estimation, approximation or rounding + // GET the approximation slippage tolerance too not drained liq if big error + // CAREFULLY CHECK AND TEST + // Can drained all fund + + if old_pool.liquidity_raised < quote_amount { + // TODO due to estimation, approximation or rounding + // maybe substract the difference between quote_amount and old_pool.liquidity_raised + // println!("old_pool.liquidity_raised < quote_amount"); + quote_amount = old_pool.liquidity_raised.clone(); + quote_amount_total = old_pool.liquidity_raised.clone(); + quote_amount_received = old_pool.liquidity_raised.clone(); + } + + // Overwrite protocol fees and quote amount after check liquidity raised and contract + // quote balance + quote_amount_protocol_fee = quote_amount * protocol_fee_percent / BPS; + + // quote_amount = quote_amount - quote_amount_protocol_fee; + // quote_amount_total = quote_amount - quote_amount_protocol_fee; + // quote_amount_received = quote_amount - quote_amount_protocol_fee; + if is_fees_protocol_enabled && is_fees_protocol_sell_enabled { + quote_amount = quote_amount_total - quote_amount_protocol_fee; + quote_amount_total = quote_amount_total - quote_amount_protocol_fee; + quote_amount_received = quote_amount_total - quote_amount_protocol_fee; + } + + // TODO edge case approximation, rounding + // CAREFULLY TEST EDGE CASE AND FUZZING + // HIGH RISK = MONEY DRAINING + // CAN DRAINED ALL MONEY + // TODO fixed rounding and approximation + // HIGH RISK SECURITY + // // Assertion: Check if the contract has enough quote tokens to transfer + let contract_quote_balance = erc20.balance_of(get_contract_address()); + // println!("sell contract_quote_balance final {:?}", contract_quote_balance); + + // // if contract_quote_balance < quote_amount { + // if contract_quote_balance < quote_amount && contract_quote_balance < + // old_pool.threshold_liquidity.clone() { + // println!("contract quote above try edge case rounding"); + // if is_fees_protocol_sell_enabled { + // quote_amount = contract_quote_balance.clone() - quote_amount_protocol_fee; + // } else { + // quote_amount = contract_quote_balance.clone(); + // } + // } + // println!("sell quote_amount received final after check balance {:?}", quote_amount); + + // assert(old_pool.liquidity_raised >= quote_amount, 'liquidity <= amount'); + assert(old_pool.liquidity_raised >= quote_amount, errors::LIQUIDITY_BELOW_AMOUNT); + + // TODO fix this function + // let mut total_price = amount; + // println!("amount {:?}", amount); + // println!("coin_amount {:?}", coin_amount); + // println!("total_price {:?}", total_price); + + // Ensure fee percentages are within valid bounds + assert( + protocol_fee_percent <= MAX_FEE_PROTOCOL + && protocol_fee_percent >= MIN_FEE_PROTOCOL, + errors::PROTOCOL_FEE_OUT_OF_BOUNDS + // 'protocol fee out' + ); + // assert( + // creator_fee_percent <= MAX_FEE_CREATOR && creator_fee_percent >= MIN_FEE_CREATOR, + // 'creator_fee out' + // ); + + // assert!(old_share.amount_owned >= amount, "share to sell > supply"); + // println!("amount{:?}", amount); + // assert!(total_supply >= quote_amount, "share to sell > supply"); + // assert( old_pool.liquidity_raised >= quote_amount, 'liquidity_raised <= amount'); + + // let old_price = old_pool.price.clone(); + let total_price = old_pool.price.clone(); + // Update keys with new values + let mut pool_update = old_pool.clone(); + + // let remain_coin_amount = total_price ; + + // Ensure fee calculations are correct + // assert( + // amount_to_user + amount_protocol_fee + amount_creator_fee == quote_amount, + // 'fee calculation mismatch' + // ); + + // Transfer protocol fee to the designated destination + // println!("sell transfer fees protocol"); + + // TODO edge case fees threshold + // CAREFULLY TEST + // HIGH RISK = BLOCKED SELL + // println!("sell quote_amount_protocol_fee {:?}", quote_amount_protocol_fee); + // // TODO fixed rounding before + + if is_fees_protocol_enabled && is_fees_protocol_sell_enabled { + // TODO edgecase + // println!("sell transfer FEES {:?}", quote_amount_protocol_fee); + erc20.transfer(self.protocol_fee_destination.read(), quote_amount_protocol_fee); + } + + // println!("sell transfer quote amount"); + // Transfer the remaining quote amount to the user + if quote_amount > 0 { + // println!("sell transfer quote amount {:?}", quote_amount); + erc20.transfer(caller, quote_amount); + } + + // Assertion: Ensure the user receives the correct amount + // let user_received = erc20.balance_of(caller); + // assert(user_received >= , 'user not receive amount'); + // TODO sell coin if it's already sendable and transferable + // ENABLE if direct launch coin + + // TODO fix amount owned and sellable. + // Update share user coin + // println!("sell update amount owned"); + + share_user.amount_owned -= remain_coin_amount; + share_user.amount_sell += remain_coin_amount; + + // TODO check reetrancy guard + + // Assertion: Ensure pool liquidity remains consistent + assert( + old_pool.liquidity_raised >= quote_amount, + errors::POOL_LIQUIDITY_INCONSISTENCY_AFTER_SALE + ); + // TODO finish update state + // pool_update.price = total_price; + // println!("sell update pool"); + + // println!("sell pool update liq raised"); + // println!("remain_coin_amount {:?}", remain_coin_amount); + + // TODO check + // HIGH SECURITY + if pool_update.liquidity_raised >= quote_amount { + // println!( + // "pool_update.liquidity_raised > quote_amount {:?}", + // pool_update.liquidity_raised > quote_amount + // ); + pool_update.liquidity_raised -= quote_amount; + } else { + pool_update.liquidity_raised = 0_u256; + } + // println!("try update total_token_holded {:?}", pool_update.total_token_holded); + pool_update.total_token_holded -= remain_coin_amount; + // println!("try update available supply {:?}", pool_update.available_supply); + pool_update.available_supply += remain_coin_amount; + self + .shares_by_users + .entry(get_caller_address()) + .entry(coin_address.clone()) + .write(share_user.clone()); + + self.launched_coins.entry(coin_address.clone()).write(pool_update.clone()); + self + .emit( + SellToken { + caller: caller, + key_user: coin_address, + amount: quote_amount, + price: total_price, // Adjust if necessary + protocol_fee: quote_amount_protocol_fee, + creator_fee: amount_creator_fee, + timestamp: get_block_timestamp(), + last_price: old_pool.price, + coin_amount: remain_coin_amount, + } + ); + } + + // TODO Finish this function + // Claim coin if liquidity is sent + // Check and modify the share of user + fn claim_coin_buy(ref self: ContractState, coin_address: ContractAddress, amount: u256) { + let caller = get_contract_address(); + // Verify if liquidity launch + let mut launch = self.launched_coins.read(coin_address); + assert(launch.is_liquidity_launch == true, errors::NOT_LAUNCHED_YET); + + let mut share_user = self + .shares_by_users + .entry(get_caller_address()) + .entry(coin_address) + .read(); + assert(share_user.is_claimable, errors::NOT_CLAIMABLE); + + let max_amount_claimable = share_user.amount_owned; + // assert(max_amount_claimable >= amount, 'share below'); + assert(max_amount_claimable >= amount, errors::SHARE_BELOW); + + // Transfer memecoin + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + memecoin.transfer(caller, amount); + + // Update new share and emit event + share_user.amount_owned -= amount; + if share_user.amount_owned == 0 { + share_user.is_claimable = false; + } + + self.shares_by_users.entry(get_caller_address()).entry(coin_address).write(share_user); + + self + .emit( + TokenClaimed { + token_address: coin_address, + owner: caller, + timestamp: get_block_timestamp(), + amount, + } + ); + } + fn claim_coin_all(ref self: ContractState, coin_address: ContractAddress) { + let caller = get_contract_address(); + // Verify if liquidity launch + let mut launch = self.launched_coins.read(coin_address); + assert(launch.is_liquidity_launch == true, errors::NOT_LAUNCHED_YET); + + // Verify share of user + let mut share_user = self + .shares_by_users + .entry(get_caller_address()) + .entry(coin_address) + .read(); + + assert(share_user.is_claimable, errors::NOT_CLAIMABLE); + + let max_amount_claimable = share_user.amount_owned; + let amount = max_amount_claimable; + // Transfer memecoin + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + memecoin.transfer(caller, amount); + + // Update new share and emit event + share_user.amount_owned -= amount; + share_user.is_claimable = false; + self.shares_by_users.entry(get_caller_address()).entry(coin_address).write(share_user); + + self + .emit( + TokenClaimed { + token_address: coin_address, + owner: caller, + timestamp: get_block_timestamp(), + amount, + } + ); + } + // Claim call for a friend + // Gonna be used to auto claim the rewards of all users of a pool bonding curve + // So we can pay the fees for the customers + fn claim_coin_all_for_friend( + ref self: ContractState, coin_address: ContractAddress, friend: ContractAddress + ) { + let caller = get_contract_address(); + // Verify if liquidity launch + let mut launch = self.launched_coins.read(coin_address); + assert(launch.is_liquidity_launch == true, errors::NOT_LAUNCHED_YET); + + // Verify share of user + let mut share_user = self.shares_by_users.entry(friend).entry(coin_address).read(); + + assert(share_user.is_claimable, errors::NOT_CLAIMABLE); + + let max_amount_claimable = share_user.amount_owned; + let amount = max_amount_claimable; + // Transfer memecoin + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + memecoin.transfer(friend, amount); + + // Update new share and emit event + share_user.amount_owned -= amount; + share_user.is_claimable = false; + self.shares_by_users.entry(friend).entry(coin_address).write(share_user); + + self + .emit( + TokenClaimed { + token_address: coin_address, + owner: friend, + timestamp: get_block_timestamp(), + amount, + } + ); + } + // TODO finish add Metadata + fn add_metadata( + ref self: ContractState, coin_address: ContractAddress, metadata: MetadataLaunch + ) { + let caller = get_contract_address(); + // Verify if caller is owner + let mut launch = self.launched_coins.read(coin_address); + assert(launch.owner == caller, errors::CALLER_NOT_OWNER); + // Add or update metadata + self.metadata_coins.entry(coin_address).write(metadata.clone()); + self + .emit( + MetadataCoinAdded { + token_address: coin_address, + url: metadata.url, + timestamp: get_block_timestamp(), + nostr_event_id: metadata.nostr_event_id, + } + ); + } + + // The function calculates the amiunt of quote_token you need to buy a coin in the pool + fn get_amount_by_type_of_coin_or_quote( + self: @ContractState, + coin_address: ContractAddress, + amount: u256, + is_decreased: bool, + is_quote_amount: bool + ) -> u256 { + let pool = self.launched_coins.read(coin_address).clone(); + get_amount_by_type_of_coin_or_quote( + pool.clone(), coin_address, amount, is_decreased, is_quote_amount + ) + } + + fn get_coin_amount_by_quote_amount( + self: @ContractState, + coin_address: ContractAddress, + quote_amount: u256, + is_decreased: bool + ) -> u256 { + let pool = self.launched_coins.read(coin_address).clone(); + get_coin_amount_by_quote_amount(pool.clone(), quote_amount, is_decreased) + } + + fn get_is_paid_launch_enable(self: @ContractState) -> bool { + self.is_paid_launch_enable.read() + } + + fn get_is_paid_create_token_enable(self: @ContractState) -> bool { + self.is_paid_create_token_enable.read() + } + + fn get_amount_to_paid_launch(self: @ContractState) -> u256 { + self.amount_to_paid_launch.read() + } + + fn get_amount_to_paid_create_token(self: @ContractState) -> u256 { + self.amount_to_paid_create_token.read() + } + } + + // // Internal functions for create token, launch, add liquidity in DEX + #[generate_trait] + impl InternalFunctions of InternalFunctionsTrait { + fn _create_token( + ref self: ContractState, + symbol: ByteArray, + name: ByteArray, + initial_supply: u256, + contract_address_salt: felt252, + is_unruggable: bool, + recipient: ContractAddress, + owner: ContractAddress, + factory: ContractAddress, + ) -> ContractAddress { + let caller = get_caller_address(); + + // TODO finish this + let is_paid_create_token_enable = self.is_paid_create_token_enable.read(); + if is_paid_create_token_enable { + let token_address_to_paid_create_token = self + .token_address_to_paid_create_token + .read(); + let amount_to_paid_create_token = self.amount_to_paid_create_token.read(); + let erc20 = IERC20Dispatcher { + contract_address: token_address_to_paid_create_token + }; + erc20 + .transfer_from( + caller, self.protocol_fee_destination.read(), amount_to_paid_create_token + ); + } + + let mut calldata = array![]; + Serde::serialize(@name.clone(), ref calldata); + Serde::serialize(@symbol.clone(), ref calldata); + Serde::serialize(@initial_supply, ref calldata); + Serde::serialize(@18, ref calldata); + Serde::serialize(@recipient, ref calldata); + Serde::serialize(@owner, ref calldata); + Serde::serialize(@factory, ref calldata); + + let (token_address, _) = deploy_syscall( + self.coin_class_hash.read(), contract_address_salt, calldata.span(), false + ) + .unwrap(); + // .unwrap_syscall(); + // println!("token address {:?}", token_address); + + let token = Token { + token_address: token_address, + owner: recipient, + creator: owner, + name: name.clone(), + symbol: symbol.clone(), + total_supply: initial_supply, + initial_supply: initial_supply, + created_at: get_block_timestamp(), + token_type: Option::None, + is_unruggable: is_unruggable + }; + + self.token_created.entry(token_address).write(token.clone()); + + let total_token = self.total_token.read(); + if total_token == 0 { + self.total_token.write(1); + self.array_coins.entry(0).write(token.clone()); + } else { + self.total_token.write(total_token + 1); + self.array_coins.entry(total_token).write(token.clone()); + } + + self + .emit( + CreateToken { + caller: get_caller_address(), + token_address: token_address, + symbol: symbol, + name: name, + initial_supply, + total_supply: initial_supply.clone(), + is_unruggable: is_unruggable + } + ); + token_address + } + + + fn _launch_token( + ref self: ContractState, + coin_address: ContractAddress, + caller: ContractAddress, + creator: ContractAddress, + is_unruggable: bool, + bonding_type: Option + ) { + let caller = get_caller_address(); + let token = self.token_created.read(coin_address); + + // TODO + // TEST edges cases + // Supply + // Threshold and supply correlation + + // TODO finish this and add tests + let is_paid_launch_enable = self.is_paid_launch_enable.read(); + if is_paid_launch_enable { + let admins_fees_params = self.admins_fees_params.read(); + let token_address_to_paid_launch = admins_fees_params.token_address_to_paid_launch; + let amount_to_paid_launch = admins_fees_params.amount_to_paid_launch; + + let erc20 = IERC20Dispatcher { contract_address: token_address_to_paid_launch }; + erc20 + .transfer_from( + caller, self.protocol_fee_destination.read(), amount_to_paid_launch + ); + } + + // TODO + // Maybe not needed because you can also create the coin everyhwhere (Unrug) and launch + let mut token_to_use = self.default_token.read(); + let mut quote_token_address = token_to_use.token_address.clone(); + // let mut bond_type = BondingType::Exponential; + // TODO fix unwrap match + + let mut bond_type = BondingType::Linear; + // let mut bond_type = Option::Some(BondingType::Linear); + if let Option::Some(v) = + bonding_type { // println!("The maximum is configured to be {}", v); + } else { + // Default Exponential because gas optimization + bond_type = BondingType::Exponential; + } + + // let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + let total_supply = memecoin.total_supply(); + let threshold_liquidity = self.threshold_liquidity.read(); + let protocol_fee_percent = self.protocol_fee_percent.read(); + let creator_fee_percent = self.creator_fee_percent.read(); + + // let threshold = pool.threshold_liquidity; + + // TODO calculate initial key price based on + // MC + // Threshold liquidity + // total supply + + // Total supply / 5 to get 20% of supply add after threshold + let liquidity_supply = total_supply / LIQUIDITY_RATIO; + let supply_distribution = total_supply - liquidity_supply; + let liquidity_available = total_supply - liquidity_supply; + + // TODO precompute maybe and saved + // Also start User params after + let starting_price = 1_u256; + let slope = 1_u256; + // let starting_price = threshold_liquidity / total_supply; + // // @TODO Deploy an ERC404 + // // Option for liquidity providing and Trading + let launch_token_pump = TokenLaunch { + owner: caller, + creator: caller, + token_address: coin_address, // CREATE 404 + total_supply: total_supply, + // available_supply: total_supply, + available_supply: supply_distribution, + initial_available_supply: supply_distribution, + initial_pool_supply: liquidity_supply, + // available_supply:liquidity_supply, + // Todo price by pricetype after fix Enum instantiate + bonding_curve_type: bond_type, + created_at: get_block_timestamp(), + token_quote: token_to_use.clone(), + starting_price: starting_price.clone(), + price: starting_price.clone(), + liquidity_raised: 0_u256, + total_token_holded: 0_u256, + is_liquidity_launch: false, + slope: slope, + threshold_liquidity: threshold_liquidity, + liquidity_type: Option::None, + protocol_fee_percent: protocol_fee_percent, + creator_fee_percent: creator_fee_percent + }; + // Send supply need to launch your coin + let amount_needed = total_supply.clone(); + // println!("amount_needed {:?}", amount_needed); + let allowance = memecoin.allowance(caller, get_contract_address()); + // println!("test allowance contract {:?}", allowance); + let balance_contract = memecoin.balance_of(get_contract_address()); + + let is_memecoin = is_unruggable; + // let is_memecoin = factory.is_memecoin(memecoin.contract_address); + // if balance_contract < total_supply && !is_memecoin { + if balance_contract < total_supply { + // && !is_memecoin + // assert(allowance >= amount_needed, 'no supply provided'); + // assert(allowance >= amount_needed, errors::NO_SUPPLY_PROVIDED); + assert(allowance >= amount_needed, errors::INSUFFICIENT_ALLOWANCE); + + if allowance >= amount_needed { + // println!("allowance > amount_needed{:?}", allowance > amount_needed); + memecoin + .transfer_from( + caller, get_contract_address(), total_supply - balance_contract + ); + } + } + + // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); + self.launched_coins.entry(coin_address).write(launch_token_pump.clone()); + + let total_launch = self.total_launch.read(); + if total_launch == 0 { + self.total_launch.write(1); + self.array_launched_coins.entry(0).write(launch_token_pump); + } else { + self.total_launch.write(total_launch + 1); + self.array_launched_coins.entry(total_launch).write(launch_token_pump); + } + self + .emit( + CreateLaunch { + caller: get_caller_address(), + token_address: coin_address, + amount: 0, + price: starting_price, + total_supply: total_supply, + slope: slope, + threshold_liquidity: threshold_liquidity, + quote_token_address: quote_token_address, + is_unruggable: is_unruggable, + bonding_type: bond_type + } + ); + } + + // Call the Unrug V2 to deposit Liquidity and locked it + fn _add_liquidity_ekubo( + ref self: ContractState, coin_address: ContractAddress, + // params: EkuboLaunchParameters + ) -> (u64, EkuboLP) { + let unrug_liquidity_address = self.unrug_liquidity_address.read(); + let unrug_liquidity = IUnrugLiquidityDispatcher { + contract_address: unrug_liquidity_address + }; + + let launch = self.launched_coins.read(coin_address); + + // assert(launch.liquidity_raised >= launch.threshold_liquidity, 'no threshold raised'); + assert(launch.is_liquidity_launch == false, errors::LIQUIDITY_ALREADY_LAUNCHED); + let threshold_liquidity = launch.threshold_liquidity.clone(); + let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; + let mut threshold = threshold_liquidity - slippage_threshold; + // WEIRD error to fix + // assert(launch.liquidity_raised >= threshold, errors::NO_THRESHOLD_RAISED); + + // TODO fix starting price + // Fix tick spacing + // Fix bounds + let starting_price: i129 = calculate_starting_price_launch( + launch.initial_pool_supply.clone(), launch.liquidity_raised.clone() + ); + // Uncomment this to used calculated starting price + let init_starting_price = i129 { sign: true, mag: 4600158 }; + + // TODO default tick space + // Add default for main coin like others + let mut tick_spacing = 5928; + + // Edge case to check and fix if fees enabled + let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); + // println!("tick_spacing {:?}", tick_spacing); + // let bound = calculate_aligned_bound_mag(starting_price, 2, tick_spacing); + // TODO verify condition EKUBO + // Verify conditions + // Add these debug prints + + // assert(bound % tick_spacing == 0, 'Bound not aligned'); + // assert(bound <= MAX_TICK.try_into().unwrap(), 'Tick magnitude too high'); + // println!("Starting Price: {}", starting_price.mag); + // println!("Calculated Bound: {}", bound); + // println!("Tick Spacing: {}", tick_spacing); + let bound_spacing = tick_spacing * 2; + let pool_params = EkuboPoolParameters { + fee: 0xc49ba5e353f7d00000000000000000, // TODO fee optional by user + tick_spacing: tick_spacing, // TODO tick_spacing optional by user + // tick_spacing: 5000, // TODO tick_spacing optional by user + starting_price: starting_price, // TODO verify if starting_price is correct + // starting_price: init_starting_price, // TODO verify if starting_price is correct + bound: bound_spacing, + // bound: bound, + }; + + // TODO fix issue when fees are enabled + let lp_supply = launch.initial_pool_supply.clone(); + let mut lp_quote_supply = launch.liquidity_raised.clone(); + + // Approve Quote + let quote_token = IERC20Dispatcher { + contract_address: launch.token_quote.token_address.clone() + }; + + // Assertion: Check if the contract has enough quote tokens to transfer to liquidity + // TODO fix this + // HIGH SECURITY + // Can drained funk if edge case approximation issue + // Edge case of approximation estimation amount and fees can cause it + let contract_quote_balance = quote_token.balance_of(get_contract_address()); + // println!("add liquidity contract_quote_balance final {:?}", contract_quote_balance); + // println!("initial_pool_supply : {}", lp_supply.clone()); + // println!("liquidity raised: {}", lp_quote_supply.clone()); + + if contract_quote_balance < lp_quote_supply + && contract_quote_balance < launch.threshold_liquidity { + // println!( + // "contract_quote_balance < lp_quote_supply + // && contract_quote_balance < launch.threshold_liquidity: {}", + // contract_quote_balance < lp_quote_supply + // && contract_quote_balance < launch.threshold_liquidity + // ); + // println!("launch.threshold_liquidity: {}", launch.threshold_liquidity.clone()); + // TODO just calculate the difference of round and substract it + + // let new_lp_quote_rounded= lp_quote_supply - contract_quote_balance; + // lp_quote_supply = new_lp_quote_rounded.clone(); + + lp_quote_supply = contract_quote_balance.clone(); + } + // println!("liquidity raised: {}", lp_quote_supply.clone()); + + let params = EkuboUnrugLaunchParameters { + // owner: launch.owner, // TODO add optional parameters to be select LIQ percent to + // be lock to Unrug at some point + owner: get_contract_address(), // TODO add optional parameters to be select LIQ percent to be lock to Unrug at some point + token_address: coin_address, + quote_address: launch.token_quote.token_address.clone(), + lp_supply: lp_supply.clone(), + lp_quote_supply: lp_quote_supply.clone(), + pool_params: pool_params, + caller: get_caller_address() + }; + + let position_ekubo_address = unrug_liquidity.get_position_ekubo_address(); + // quote_token.approve(position_ekubo_address, lp_quote_supply); + quote_token.approve(unrug_liquidity_address, lp_quote_supply); + + // Approve Memecoin + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + // memecoin.approve(position_ekubo_address, lp_supply); + memecoin.approve(unrug_liquidity_address, lp_supply); + + let (id, position) = unrug_liquidity.launch_on_ekubo(coin_address, params); + let id_cast: u256 = id.try_into().unwrap(); + + // Set token launched + let mut launch_to_update = self.launched_coins.read(coin_address); + launch_to_update.is_liquidity_launch = true; + self.launched_coins.entry(coin_address).write(launch_to_update.clone()); + + self + .emit( + LiquidityCreated { + id: id_cast, + pool: coin_address, + asset: coin_address, + quote_token_address: launch.token_quote.token_address, + owner: launch.owner, + exchange: SupportedExchanges::Ekubo, + is_unruggable: false + } + ); + + let token_state = self.token_created.read(coin_address); + // TODO set_launched + // let memecoin = IMemecoinDispatcher { contract_address: coin_address }; + // let factory_address = memecoin.get_factory_address(); + // if token_state.creator == get_contract_address() || factory_address == + // get_contract_address() { + // memecoin + // .set_launched( + // LiquidityType::EkuboNFT(id), + // LiquidityParameters::Ekubo( + // EkuboLiquidityParameters { + // quote_address, ekubo_pool_parameters: ekubo_pool_params + // } + // ), + // :transfer_restriction_delay, + // :max_percentage_buy_launch, + // :team_allocation, + // ); + // self + // .emit( + // MemecoinLaunched { + // memecoin_address, quote_token: quote_address, exchange_name: 'Ekubo' + // } + // ); + // } + (id, position) + } + + // TODO finish call Jediswap + // Change preparation of state for lp_supply, approve etc for the Unrug V2 + fn _add_liquidity_jediswap( + ref self: ContractState, coin_address: ContractAddress, owner: ContractAddress + // params: EkuboLaunchParameters + ) -> u256 { + let unrug_liquidity = IUnrugLiquidityDispatcher { + contract_address: self.unrug_liquidity_address.read() + }; + + let launch = self.launched_coins.read(coin_address); + + assert(launch.is_liquidity_launch == false, errors::LIQUIDITY_ALREADY_LAUNCHED); + let threshold_liquidity = launch.threshold_liquidity.clone(); + let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; + let mut threshold = threshold_liquidity - slippage_threshold; + + // assert(launch.liquidity_raised >= threshold, errors::NO_THRESHOLD_RAISED); + + let quote_address = launch.token_quote.token_address.clone(); + let lp_supply = launch.initial_pool_supply.clone(); + let quote_supply = launch.liquidity_raised.clone(); + let unlock_time = starknet::get_block_timestamp() + DEFAULT_MIN_LOCKTIME; + + // let (id, position) = unrug_liquidity.launch_on_jediswap(coin_address, params); + let id_cast = unrug_liquidity + .launch_on_jediswap( + coin_address, quote_address, lp_supply, quote_supply, unlock_time, owner + ); + + // TODO + // Add Locked position + // let (id, position) = unrug_liquidity.launch_on_jediswap(coin_address, quote_address, + // lp_supply, quote_supply, unlock_time); + // let id_cast: u256 = id.try_into().unwrap(); + + self + .emit( + LiquidityCreated { + id: id_cast, + pool: coin_address, + asset: coin_address, + quote_token_address: launch.token_quote.token_address, + owner: launch.owner, + exchange: SupportedExchanges::Jediswap, + is_unruggable: false + } + ); + // (id, position) + id_cast + } + } +} diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad_old.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad_old.cairo index 2e5005c50..d45316a99 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad_old.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad_old.cairo @@ -718,6 +718,10 @@ pub mod LaunchpadMarketplace { // fees threshold = threshold_liquidity - (slippage_threshold + amount_protocol_fee); // add slippage and fees + // threshold = threshold_liquidity + // - (slippage_threshold); // add slippage and fees + // threshold = threshold_liquidity + // - (amount_protocol_fee); // add slippage and fees erc20 .transfer_from( @@ -1178,7 +1182,7 @@ pub mod LaunchpadMarketplace { key_user: coin_address, amount: quote_amount, price: total_price, // Adjust if necessary - protocol_fee: amount_protocol_fee, + protocol_fee: quote_amount_protocol_fee, creator_fee: amount_creator_fee, timestamp: get_block_timestamp(), last_price: old_pool.price, @@ -1397,8 +1401,8 @@ pub mod LaunchpadMarketplace { } let mut calldata = array![]; - Serde::serialize(@name, ref calldata); - Serde::serialize(@symbol, ref calldata); + Serde::serialize(@name.clone(), ref calldata); + Serde::serialize(@symbol.clone(), ref calldata); Serde::serialize(@initial_supply, ref calldata); Serde::serialize(@18, ref calldata); Serde::serialize(@recipient, ref calldata); @@ -1416,8 +1420,8 @@ pub mod LaunchpadMarketplace { token_address: token_address, owner: recipient, creator: owner, - name, - symbol, + name: name.clone(), + symbol: symbol.clone(), total_supply: initial_supply, initial_supply: initial_supply, created_at: get_block_timestamp(), diff --git a/onchain/cairo/launchpad/src/launchpad/unrug.cairo b/onchain/cairo/launchpad/src/launchpad/unrug.cairo index 69843fc05..5bd88ac0b 100644 --- a/onchain/cairo/launchpad/src/launchpad/unrug.cairo +++ b/onchain/cairo/launchpad/src/launchpad/unrug.cairo @@ -2,21 +2,13 @@ pub mod UnrugLiquidity { use afk_launchpad::interfaces::jediswap::{ // V1 - // IJediswapRouter, IJediswapRouterDispatcher, IJediswapFactoryDispatcher, - // IJediswapFactoryV1Dispatcher, - // IJediswapFactoryV1DispatcherTrait, - + // IJediswapRouter, IJediswapRouterDispatcher, IJediswapFactoryDispatcher, // IJediswapFactoryV1Dispatcher, // IJediswapFactoryV1DispatcherTrait, // V2 Jediswap IJediswapFactoryV2, IJediswapFactoryV2Dispatcher, IJediswapFactoryV2DispatcherTrait, // Router // IJediswapRouterV2, IJediswapRouterV2Dispatcher, IJediswapRouterV2DispatcherTrait, // NFT router position IJediswapNFTRouterV2, IJediswapNFTRouterV2Dispatcher, IJediswapNFTRouterV2DispatcherTrait, - // IJediswapRouterV1Dispatcher, - // IJediswapRouterV1, - // IJediswapRouterV1DispatcherTrait, - // IJediswapFactoryV1Dispatcher, - // IJediswapFactoryV1DispatcherTrait }; use afk_launchpad::interfaces::unrug::{IUnrugLiquidity}; @@ -24,8 +16,6 @@ pub mod UnrugLiquidity { calculate_starting_price_launch, // calculate_slope, calculate_pricing, }; use afk_launchpad::launchpad::errors; - // use afk_launchpad::launchpad::helpers::{distribute_team_alloc, check_common_launch_parameters - // }; use afk_launchpad::launchpad::helpers::{distribute_team_alloc, check_common_launch_parameters}; use afk_launchpad::launchpad::locker::interface::{ ILockManagerDispatcher, ILockManagerDispatcherTrait @@ -34,7 +24,6 @@ pub mod UnrugLiquidity { }; use afk_launchpad::launchpad::utils::{ sort_tokens, get_initial_tick_from_starting_price, get_next_tick_bounds, unique_count, - // calculate_aligned_bound_mag }; use afk_launchpad::tokens::erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; use afk_launchpad::tokens::memecoin::{IMemecoinDispatcher, IMemecoinDispatcherTrait}; @@ -51,10 +40,8 @@ pub mod UnrugLiquidity { // MemecoinCreated, MemecoinLaunched }; use afk_launchpad::utils::{sqrt}; - use core::num::traits::Zero; use ekubo::components::clear::{IClearDispatcher, IClearDispatcherTrait}; - use ekubo::components::shared_locker::{call_core_with_callback, consume_callback_data}; use ekubo::interfaces::core::{ICoreDispatcher, ICoreDispatcherTrait, ILocker}; use ekubo::interfaces::erc20::{ @@ -611,8 +598,6 @@ pub mod UnrugLiquidity { // TODO check errors possible // BOUNDS_TICK_SPACING - // T - // TODO used it or full_bounds // Verify bound to use based on user params // Add single tick bound diff --git a/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo b/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo index 36a750c22..b3281579e 100644 --- a/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo +++ b/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo @@ -125,8 +125,12 @@ mod launchpad_tests { 0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067.try_into().unwrap() } + // fn EKUBO_REGISTRY() -> ContractAddress { + // 0x0013e25867b6eef62703735aa4cfa7754e72f4e94a56c9d3d9ad8ebe86cee4aa.try_into().unwrap() + // } + // v3 fn EKUBO_REGISTRY() -> ContractAddress { - 0x0013e25867b6eef62703735aa4cfa7754e72f4e94a56c9d3d9ad8ebe86cee4aa.try_into().unwrap() + 0x064bdb4094881140bc39340146c5fcc5a187a98aec5a53f448ac702e5de5067e.try_into().unwrap() } fn JEDISWAP_FACTORY() -> ContractAddress { diff --git a/onchain/cairo/launchpad/src/types/launchpad_types.cairo b/onchain/cairo/launchpad/src/types/launchpad_types.cairo index 1556344c5..b83e2a78a 100644 --- a/onchain/cairo/launchpad/src/types/launchpad_types.cairo +++ b/onchain/cairo/launchpad/src/types/launchpad_types.cairo @@ -31,17 +31,6 @@ pub enum BondingType { // Limited } -// #[derive(Serde, Copy, // Clone, -// Drop, starknet::Store, PartialEq // PartialEq -// )] -// pub enum BondingType { -// Linear, -// Exponential, -// // Limited, -// // Trapezoidal, -// // Scoring, // Nostr data with Appchain connected to a Relayer -// } - // Storage #[derive(Drop, Serde, Copy, starknet::Store, PartialEq)] pub struct AdminsFeesParams { @@ -55,20 +44,6 @@ pub struct AdminsFeesParams { pub is_paid_create_token_enable: bool, pub is_paid_launch_enable: bool, } -// #[derive(Drop, Serde, Copy, starknet::Store, PartialEq)] -// pub struct AdminsFeesParams { -// pub protocol_fee_percent: u256, -// pub creator_fee_percent: u256, -// pub is_fees_protocol_sell_enabled: bool, -// pub is_fees_protocol_buy_enabled: bool, -// pub is_fees_protocol_enabled: bool, -// pub is_fees_enabled: bool, -// pub is_custom_launch_enable: bool, -// pub is_custom_token_enable: bool, -// pub is_paid_create_token_enable: bool, -// pub is_paid_launch_enable: bool, -// pub is_create_token_paid: bool, -// } #[derive(Drop, Serde, Copy, starknet::Store, PartialEq)] pub struct TokenQuoteBuyCoin { @@ -80,14 +55,6 @@ pub struct TokenQuoteBuyCoin { } -#[derive(Serde, Copy, // Clone, - Drop, starknet::Store, // PartialEq -)] -pub enum TokenType { - ERC20, - ERC404, -} - #[derive(Drop, Serde, Clone, starknet::Store)] pub struct Token { pub owner: ContractAddress, @@ -112,7 +79,6 @@ pub struct TokenLaunch { pub initial_pool_supply: u256, // Liquidity token to add in the DEX pub initial_available_supply: u256, // Init available to buy pub total_supply: u256, // Total supply to buy - // pub bonding_curve_type: Option, pub bonding_curve_type: BondingType, pub created_at: u64, pub token_quote: TokenQuoteBuyCoin, // Token launched @@ -166,30 +132,128 @@ pub struct MetadataLaunch { pub nostr_event_id: u256, } -#[derive(Drop, Serde, Copy, starknet::Store)] -pub struct TokenLaunchFair { - // pub struct Keys { + +// Struct for Liquidity Ekubo and more + +#[derive(Copy, Drop, Serde)] +pub struct LaunchParameters { + pub memecoin_address: starknet::ContractAddress, + pub transfer_restriction_delay: u64, + pub max_percentage_buy_launch: u16, + pub quote_address: starknet::ContractAddress, + pub initial_holders: Span, + pub initial_holders_amounts: Span, +} + +#[derive(Copy, Drop, Serde)] +pub struct EkuboLaunchParameters { pub owner: ContractAddress, pub token_address: ContractAddress, - pub price: u256, - pub starting_price: u256, - pub total_supply: u256, - pub bonding_curve_type: Option, - pub created_at: u64, - pub token_quote: TokenQuoteBuyCoin, - pub final_time: u64, + pub quote_address: ContractAddress, + pub lp_supply: u256, + pub pool_params: EkuboPoolParameters } +#[derive(Copy, Drop, Serde)] +pub struct EkuboUnrugLaunchParameters { + pub owner: ContractAddress, + pub token_address: ContractAddress, + pub quote_address: ContractAddress, + pub lp_supply: u256, + pub lp_quote_supply: u256, + pub pool_params: EkuboPoolParameters, + pub caller: ContractAddress +} -// Events +#[derive(Copy, Drop, Serde)] +pub struct EkuboLP { + pub owner: ContractAddress, + pub quote_address: ContractAddress, + pub pool_key: PoolKey, + pub bounds: Bounds, +} -#[derive(Drop, starknet::Event)] -pub struct StoredName { - #[key] - pub user: ContractAddress, - pub name: felt252, +#[derive(Drop, Copy, starknet::Store, Serde)] +pub struct EkuboPoolParameters { + pub fee: u128, + pub tick_spacing: u128, + // the sign of the starting tick is positive (false) if quote/token < 1 and negative (true) + // otherwise + pub starting_price: i129, + // The LP providing bound, upper/lower determined by the address of the LPed tokens + pub bound: u128, } +#[derive(Copy, Drop, starknet::Store, Serde)] +pub enum LiquidityType { + JediERC20: ContractAddress, + StarkDeFiERC20: ContractAddress, + EkuboNFT: u64 +} + +#[derive(Copy, Drop, starknet::Store, Serde)] +pub struct EkuboLiquidityParameters { + pub ekubo_pool_parameters: EkuboPoolParameters, + pub quote_address: ContractAddress, +} + + +// #[derive(Copy, Drop, starknet::Store, Serde)] +// struct StarkDeFiLiquidityParameters { +// quote_address: ContractAddress, +// quote_amount: u256, +// } + +#[derive(Copy, Drop, starknet::Store, Serde)] +pub enum LiquidityParameters { + Ekubo: EkuboLiquidityParameters, + // pub Jediswap: (JediswapLiquidityParameters, ContractAddress), +// StarkDeFi: (StarkDeFiLiquidityParameters, ContractAddress), +} + +#[derive(Serde, Drop, Copy)] +pub struct LaunchCallback { + pub params: EkuboLaunchParameters, +} + +#[derive(Serde, Drop, Copy)] +pub struct UnrugLaunchCallback { + pub unrug_params: EkuboUnrugLaunchParameters, +} + +// #[derive(Serde, Drop, Copy)] +// struct WithdrawFeesCallback { +// id: u64, +// liquidity_type: EkuboLP, +// recipient: ContractAddress, +// } + +#[derive(Serde, Drop, Copy)] +pub enum CallbackData { + // WithdrawFeesCallback: WithdrawFeesCallback, + LaunchCallback: LaunchCallback, +} + +#[derive(Serde, Drop, Copy)] +pub enum UnrugCallbackData { + UnrugLaunchCallback: UnrugLaunchCallback, +} + +#[derive(Drop, Serde, Copy, starknet::Store, PartialEq)] +pub struct LockPosition { + pub id_position: u256, + pub asset_address: ContractAddress, + pub quote_address: ContractAddress, + pub exchange: SupportedExchanges, + pub created_at: u64, + pub unlock_time: u64, + pub owner: ContractAddress, + pub caller: ContractAddress, +} + +// Events of smart contract +// Emit by Launchpad and Unrug + #[derive(Drop, starknet::Event)] pub struct BuyToken { #[key] @@ -259,20 +323,6 @@ pub struct MemecoinCreated { } -#[derive(Drop, starknet::Event)] -pub struct MemecoinLaunched { - pub memecoin_address: ContractAddress, - pub quote_token: ContractAddress, - pub exchange_name: felt252, -} - -#[derive(Drop, starknet::Event)] -pub struct LaunchUpdated { - #[key] - user: ContractAddress, - supply: u256, - price: u256 -} #[derive(Drop, starknet::Event)] pub struct SetJediswapV2Factory { @@ -334,67 +384,6 @@ pub struct MetadataCoinAdded { pub timestamp: u64 } -#[derive(Copy, Drop, Serde)] -pub struct LaunchParameters { - pub memecoin_address: starknet::ContractAddress, - pub transfer_restriction_delay: u64, - pub max_percentage_buy_launch: u16, - pub quote_address: starknet::ContractAddress, - pub initial_holders: Span, - pub initial_holders_amounts: Span, -} - -#[derive(Copy, Drop, Serde)] -pub struct EkuboLaunchParameters { - pub owner: ContractAddress, - pub token_address: ContractAddress, - pub quote_address: ContractAddress, - pub lp_supply: u256, - pub pool_params: EkuboPoolParameters -} - -#[derive(Copy, Drop, Serde)] -pub struct EkuboUnrugLaunchParameters { - pub owner: ContractAddress, - pub token_address: ContractAddress, - pub quote_address: ContractAddress, - pub lp_supply: u256, - pub lp_quote_supply: u256, - pub pool_params: EkuboPoolParameters, - pub caller: ContractAddress -} - -#[derive(Copy, Drop, Serde)] -pub struct EkuboLP { - pub owner: ContractAddress, - pub quote_address: ContractAddress, - pub pool_key: PoolKey, - pub bounds: Bounds, -} - -#[derive(Drop, Copy, starknet::Store, Serde)] -pub struct EkuboPoolParameters { - pub fee: u128, - pub tick_spacing: u128, - // the sign of the starting tick is positive (false) if quote/token < 1 and negative (true) - // otherwise - pub starting_price: i129, - // The LP providing bound, upper/lower determined by the address of the LPed tokens - pub bound: u128, -} - -#[derive(Copy, Drop, starknet::Store, Serde)] -pub enum LiquidityType { - JediERC20: ContractAddress, - StarkDeFiERC20: ContractAddress, - EkuboNFT: u64 -} - -#[derive(Copy, Drop, starknet::Store, Serde)] -pub struct EkuboLiquidityParameters { - pub ekubo_pool_parameters: EkuboPoolParameters, - pub quote_address: ContractAddress, -} #[derive(Copy, Drop, starknet::Store, Serde)] pub struct JediswapLiquidityParameters { @@ -402,56 +391,50 @@ pub struct JediswapLiquidityParameters { pub quote_amount: u256, } -// #[derive(Copy, Drop, starknet::Store, Serde)] -// struct StarkDeFiLiquidityParameters { -// quote_address: ContractAddress, -// quote_amount: u256, -// } - -#[derive(Copy, Drop, starknet::Store, Serde)] -pub enum LiquidityParameters { - Ekubo: EkuboLiquidityParameters, - // pub Jediswap: (JediswapLiquidityParameters, ContractAddress), -// StarkDeFi: (StarkDeFiLiquidityParameters, ContractAddress), -} +// -#[derive(Serde, Drop, Copy)] -pub struct LaunchCallback { - pub params: EkuboLaunchParameters, +#[derive(Drop, starknet::Event)] +pub struct MemecoinLaunched { + pub memecoin_address: ContractAddress, + pub quote_token: ContractAddress, + pub exchange_name: felt252, } -#[derive(Serde, Drop, Copy)] -pub struct UnrugLaunchCallback { - pub unrug_params: EkuboUnrugLaunchParameters, +#[derive(Drop, starknet::Event)] +pub struct LaunchUpdated { + #[key] + user: ContractAddress, + supply: u256, + price: u256 } -// #[derive(Serde, Drop, Copy)] -// struct WithdrawFeesCallback { -// id: u64, -// liquidity_type: EkuboLP, -// recipient: ContractAddress, -// } - -#[derive(Serde, Drop, Copy)] -pub enum CallbackData { - // WithdrawFeesCallback: WithdrawFeesCallback, - LaunchCallback: LaunchCallback, -} -#[derive(Serde, Drop, Copy)] -pub enum UnrugCallbackData { - UnrugLaunchCallback: UnrugLaunchCallback, +#[derive(Serde, Copy, // Clone, + Drop, starknet::Store, // PartialEq + )] + pub enum TokenType { + ERC20, + ERC404, } - -#[derive(Drop, Serde, Copy, starknet::Store, PartialEq)] -pub struct LockPosition { - pub id_position: u256, - pub asset_address: ContractAddress, - pub quote_address: ContractAddress, - pub exchange: SupportedExchanges, - pub created_at: u64, - pub unlock_time: u64, + +#[derive(Drop, Serde, Copy, starknet::Store)] +pub struct TokenLaunchFair { + // pub struct Keys { pub owner: ContractAddress, - pub caller: ContractAddress, + pub token_address: ContractAddress, + pub price: u256, + pub starting_price: u256, + pub total_supply: u256, + pub bonding_curve_type: Option, + pub created_at: u64, + pub token_quote: TokenQuoteBuyCoin, + pub final_time: u64, } +// Events +#[derive(Drop, starknet::Event)] +pub struct StoredName { + #[key] + pub user: ContractAddress, + pub name: felt252, +} diff --git a/packages/common/src/contracts.ts b/packages/common/src/contracts.ts index d2982314b..99e50db7e 100644 --- a/packages/common/src/contracts.ts +++ b/packages/common/src/contracts.ts @@ -8,9 +8,10 @@ export const ESCROW_ADDRESSES = { // [constants.StarknetChainId.SN_SEPOLIA]: // '0x078a022e6906c83e049a30f7464b939b831ecbe47029480d7e89684f20c8d263', - [constants.StarknetChainId.SN_SEPOLIA]: - "0x7323351c9e497ef4cc59cfdacdc8ba7b07c6b4aaeb07e78dfda0988f6e8e3ee", - + // [constants.StarknetChainId.SN_SEPOLIA]: + // "0x7323351c9e497ef4cc59cfdacdc8ba7b07c6b4aaeb07e78dfda0988f6e8e3ee", + [constants.StarknetChainId.SN_SEPOLIA]:"0x1ed7f4d0afce7bd17acecae039f255724d194373f104ce04b3146d6461e09f", + // [constants.StarknetChainId.SN_SEPOLIA]: "0x854a13df46ddc497f610a5bf65097650b0883ce99b6bae614294ecbaf1000d" }; diff --git a/scripts/utils/escrow.ts b/scripts/utils/escrow.ts index 9964c00eb..34ac1ef33 100644 --- a/scripts/utils/escrow.ts +++ b/scripts/utils/escrow.ts @@ -8,11 +8,11 @@ import { finalizeEvent } from "nostr-tools"; dotenv.config(); const PATH_SOCIAL_ACCOUNT = path.resolve( __dirname, - "../../onchain/cairo/target/dev/afk_DepositEscrow.contract_class.json" + "../../onchain/cairo/afk/target/dev/afk_DepositEscrow.contract_class.json" ); const PATH_SOCIAL_ACCOUNT_COMPILED = path.resolve( __dirname, - "../../onchain/cairo/target/dev/afk_DepositEscrow.compiled_contract_class.json" + "../../onchain/cairo/afk/target/dev/afk_DepositEscrow.compiled_contract_class.json" ); /** @TODO spec need to be discuss. This function serve as an example */ From 6ec58216efc603097cc546af7376a8cd263f6f71 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Thu, 30 Jan 2025 15:08:43 +0100 Subject: [PATCH 02/11] first refacto clean part of intern function --- .../launchpad/src/interfaces/launchpad.cairo | 2 +- .../launchpad/src/launchpad/launchpad.cairo | 385 +++++------------- 2 files changed, 110 insertions(+), 277 deletions(-) diff --git a/onchain/cairo/launchpad/src/interfaces/launchpad.cairo b/onchain/cairo/launchpad/src/interfaces/launchpad.cairo index 3bb6a66ac..463da3924 100644 --- a/onchain/cairo/launchpad/src/interfaces/launchpad.cairo +++ b/onchain/cairo/launchpad/src/interfaces/launchpad.cairo @@ -41,7 +41,7 @@ pub trait ILaunchpadMarketplace { ref self: TContractState, coin_address: ContractAddress, quote_amount: u256, ); fn sell_coin(ref self: TContractState, coin_address: ContractAddress, coin_amount: u256); - fn claim_coin_buy(ref self: TContractState, coin_address: ContractAddress, amount: u256); + // fn claim_coin_buy(ref self: TContractState, coin_address: ContractAddress, amount: u256); fn claim_coin_all(ref self: TContractState, coin_address: ContractAddress); fn claim_coin_all_for_friend( ref self: TContractState, coin_address: ContractAddress, friend: ContractAddress diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index bc88ae8be..dc0685eeb 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -156,7 +156,6 @@ pub mod LaunchpadMarketplace { shares_by_users: Map::>, bonding_type: Map::, array_launched_coins: Map::, - array_coins: Map::, tokens_created: Map::, launch_created: Map::, // Admin params @@ -1143,45 +1142,7 @@ pub mod LaunchpadMarketplace { // TODO Finish this function // Claim coin if liquidity is sent // Check and modify the share of user - fn claim_coin_buy(ref self: ContractState, coin_address: ContractAddress, amount: u256) { - let caller = get_contract_address(); - // Verify if liquidity launch - let mut launch = self.launched_coins.read(coin_address); - assert(launch.is_liquidity_launch == true, errors::NOT_LAUNCHED_YET); - - let mut share_user = self - .shares_by_users - .entry(get_caller_address()) - .entry(coin_address) - .read(); - assert(share_user.is_claimable, errors::NOT_CLAIMABLE); - - let max_amount_claimable = share_user.amount_owned; - // assert(max_amount_claimable >= amount, 'share below'); - assert(max_amount_claimable >= amount, errors::SHARE_BELOW); - - // Transfer memecoin - let memecoin = IERC20Dispatcher { contract_address: coin_address }; - memecoin.transfer(caller, amount); - - // Update new share and emit event - share_user.amount_owned -= amount; - if share_user.amount_owned == 0 { - share_user.is_claimable = false; - } - self.shares_by_users.entry(get_caller_address()).entry(coin_address).write(share_user); - - self - .emit( - TokenClaimed { - token_address: coin_address, - owner: caller, - timestamp: get_block_timestamp(), - amount, - } - ); - } fn claim_coin_all(ref self: ContractState, coin_address: ContractAddress) { let caller = get_contract_address(); // Verify if liquidity launch @@ -1363,7 +1324,6 @@ pub mod LaunchpadMarketplace { ) .unwrap(); // .unwrap_syscall(); - // println!("token address {:?}", token_address); let token = Token { token_address: token_address, @@ -1379,15 +1339,8 @@ pub mod LaunchpadMarketplace { }; self.token_created.entry(token_address).write(token.clone()); - let total_token = self.total_token.read(); - if total_token == 0 { - self.total_token.write(1); - self.array_coins.entry(0).write(token.clone()); - } else { - self.total_token.write(total_token + 1); - self.array_coins.entry(total_token).write(token.clone()); - } + self.total_token.write(total_token + 1); self .emit( @@ -1416,226 +1369,140 @@ pub mod LaunchpadMarketplace { let caller = get_caller_address(); let token = self.token_created.read(coin_address); - // TODO - // TEST edges cases - // Supply - // Threshold and supply correlation - - // TODO finish this and add tests - let is_paid_launch_enable = self.is_paid_launch_enable.read(); - if is_paid_launch_enable { + // Handle paid launch if enabled + if self.is_paid_launch_enable.read() { let admins_fees_params = self.admins_fees_params.read(); - let token_address_to_paid_launch = admins_fees_params.token_address_to_paid_launch; - let amount_to_paid_launch = admins_fees_params.amount_to_paid_launch; - - let erc20 = IERC20Dispatcher { contract_address: token_address_to_paid_launch }; - erc20 - .transfer_from( - caller, self.protocol_fee_destination.read(), amount_to_paid_launch - ); + let erc20 = IERC20Dispatcher { + contract_address: admins_fees_params.token_address_to_paid_launch + }; + erc20.transfer_from( + caller, + self.protocol_fee_destination.read(), + admins_fees_params.amount_to_paid_launch + ); } - // TODO - // Maybe not needed because you can also create the coin everyhwhere (Unrug) and launch - let mut token_to_use = self.default_token.read(); - let mut quote_token_address = token_to_use.token_address.clone(); - // let mut bond_type = BondingType::Exponential; - // TODO fix unwrap match - - let mut bond_type = BondingType::Linear; - // let mut bond_type = Option::Some(BondingType::Linear); - if let Option::Some(v) = - bonding_type { // println!("The maximum is configured to be {}", v); - } else { - // Default Exponential because gas optimization - bond_type = BondingType::Exponential; - } + // Set up bonding curve type + let bond_type = match bonding_type { + Option::Some(curve_type) => curve_type, + Option::None => BondingType::Exponential + }; - // let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; + // Get token parameters + let token_to_use = self.default_token.read(); + let quote_token_address = token_to_use.token_address.clone(); let memecoin = IERC20Dispatcher { contract_address: coin_address }; let total_supply = memecoin.total_supply(); - let threshold_liquidity = self.threshold_liquidity.read(); - let protocol_fee_percent = self.protocol_fee_percent.read(); - let creator_fee_percent = self.creator_fee_percent.read(); - - // let threshold = pool.threshold_liquidity; - - // TODO calculate initial key price based on - // MC - // Threshold liquidity - // total supply - // Total supply / 5 to get 20% of supply add after threshold + // Calculate supply distribution let liquidity_supply = total_supply / LIQUIDITY_RATIO; let supply_distribution = total_supply - liquidity_supply; - let liquidity_available = total_supply - liquidity_supply; - - // TODO precompute maybe and saved - // Also start User params after - let starting_price = 1_u256; - let slope = 1_u256; - // let starting_price = threshold_liquidity / total_supply; - // // @TODO Deploy an ERC404 - // // Option for liquidity providing and Trading + + // Create launch parameters let launch_token_pump = TokenLaunch { owner: caller, creator: caller, - token_address: coin_address, // CREATE 404 - total_supply: total_supply, - // available_supply: total_supply, + token_address: coin_address, + total_supply, available_supply: supply_distribution, initial_available_supply: supply_distribution, initial_pool_supply: liquidity_supply, - // available_supply:liquidity_supply, - // Todo price by pricetype after fix Enum instantiate bonding_curve_type: bond_type, created_at: get_block_timestamp(), token_quote: token_to_use.clone(), - starting_price: starting_price.clone(), - price: starting_price.clone(), + starting_price: 1_u256, + price: 1_u256, liquidity_raised: 0_u256, total_token_holded: 0_u256, is_liquidity_launch: false, - slope: slope, - threshold_liquidity: threshold_liquidity, + slope: 1_u256, + threshold_liquidity: self.threshold_liquidity.read(), liquidity_type: Option::None, - protocol_fee_percent: protocol_fee_percent, - creator_fee_percent: creator_fee_percent + protocol_fee_percent: self.protocol_fee_percent.read(), + creator_fee_percent: self.creator_fee_percent.read() }; - // Send supply need to launch your coin - let amount_needed = total_supply.clone(); - // println!("amount_needed {:?}", amount_needed); - let allowance = memecoin.allowance(caller, get_contract_address()); - // println!("test allowance contract {:?}", allowance); - let balance_contract = memecoin.balance_of(get_contract_address()); - let is_memecoin = is_unruggable; - // let is_memecoin = factory.is_memecoin(memecoin.contract_address); - // if balance_contract < total_supply && !is_memecoin { + // Handle token transfer + let balance_contract = memecoin.balance_of(get_contract_address()); if balance_contract < total_supply { - // && !is_memecoin - // assert(allowance >= amount_needed, 'no supply provided'); - // assert(allowance >= amount_needed, errors::NO_SUPPLY_PROVIDED); - assert(allowance >= amount_needed, errors::INSUFFICIENT_ALLOWANCE); - - if allowance >= amount_needed { - memecoin - .transfer_from( - caller, get_contract_address(), total_supply - balance_contract - ); - // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); - - } + let allowance = memecoin.allowance(caller, get_contract_address()); + assert(allowance >= total_supply, errors::INSUFFICIENT_ALLOWANCE); + memecoin.transfer_from( + caller, + get_contract_address(), + total_supply - balance_contract + ); } + // Store launch data self.launched_coins.entry(coin_address).write(launch_token_pump.clone()); let total_launch = self.total_launch.read(); - if total_launch == 0 { - self.total_launch.write(1); - self.array_launched_coins.entry(0).write(launch_token_pump); - } else { - self.total_launch.write(total_launch + 1); - self.array_launched_coins.entry(total_launch).write(launch_token_pump); - } - self - .emit( - CreateLaunch { - caller: get_caller_address(), - token_address: coin_address, - amount: 0, - price: starting_price, - total_supply: total_supply, - slope: slope, - threshold_liquidity: threshold_liquidity, - quote_token_address: quote_token_address, - is_unruggable: is_unruggable, - bonding_type: bond_type - } - ); + self.total_launch.write(total_launch + 1); + self.array_launched_coins.entry(total_launch).write(launch_token_pump); + + // Emit launch event + self.emit( + CreateLaunch { + caller: get_caller_address(), + token_address: coin_address, + amount: 0, + price: 1_u256, + total_supply, + slope: 1_u256, + threshold_liquidity: self.threshold_liquidity.read(), + quote_token_address, + is_unruggable, + bonding_type: bond_type + } + ); } // Call the Unrug V2 to deposit Liquidity and locked it fn _add_liquidity_ekubo( - ref self: ContractState, coin_address: ContractAddress, - // params: EkuboLaunchParameters + ref self: ContractState, + coin_address: ContractAddress, ) -> (u64, EkuboLP) { + // Get unrug liquidity contract let unrug_liquidity_address = self.unrug_liquidity_address.read(); - let unrug_liquidity = IUnrugLiquidityDispatcher { - contract_address: unrug_liquidity_address - }; + let unrug_liquidity = IUnrugLiquidityDispatcher { contract_address: unrug_liquidity_address }; + // Get launch info and validate let launch = self.launched_coins.read(coin_address); - // assert(launch.liquidity_raised >= launch.threshold_liquidity, 'no threshold raised'); assert(launch.is_liquidity_launch == false, errors::LIQUIDITY_ALREADY_LAUNCHED); - let threshold_liquidity = launch.threshold_liquidity.clone(); - let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; - let mut threshold = threshold_liquidity - slippage_threshold; - // WEIRD error to fix - // assert(launch.liquidity_raised >= threshold, errors::NO_THRESHOLD_RAISED); - - // TODO fix starting price - // Fix tick spacing - // Fix bounds - let starting_price: i129 = calculate_starting_price_launch( - launch.initial_pool_supply.clone(), launch.liquidity_raised.clone() - ); - // Uncomment this to used calculated starting price - let init_starting_price = i129 { sign: true, mag: 4600158 }; - // TODO default tick space - // Add default for main coin like others - let mut tick_spacing = 5928; + // Calculate thresholds + let threshold_liquidity = launch.threshold_liquidity.clone(); + let slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; + let threshold = threshold_liquidity - slippage_threshold; - // Edge case to check and fix if fees enabled - let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); - // let bound = calculate_aligned_bound_mag(starting_price, 2, tick_spacing); - // TODO verify condition EKUBO - // Verify conditions - // Add these debug prints - // assert(bound % tick_spacing == 0, 'Bound not aligned'); - // assert(bound <= MAX_TICK.try_into().unwrap(), 'Tick magnitude too high'); + // Set pool parameters + let tick_spacing = 5928; let bound_spacing = tick_spacing * 2; let pool_params = EkuboPoolParameters { - fee: 0xc49ba5e353f7d00000000000000000, // TODO fee optional by user - tick_spacing: tick_spacing, // TODO tick_spacing optional by user - // tick_spacing: 5000, // TODO tick_spacing optional by user - starting_price: starting_price, // TODO verify if starting_price is correct - // starting_price: init_starting_price, // TODO verify if starting_price is correct + fee: 0xc49ba5e353f7d00000000000000000, + tick_spacing: tick_spacing, + starting_price: calculate_starting_price_launch( + launch.initial_pool_supply.clone(), + launch.liquidity_raised.clone() + ), bound: bound_spacing, - // bound: bound, }; - // TODO fix issue when fees are enabled + // Calculate liquidity amounts let lp_supply = launch.initial_pool_supply.clone(); let mut lp_quote_supply = launch.liquidity_raised.clone(); - // Approve Quote - let quote_token = IERC20Dispatcher { - contract_address: launch.token_quote.token_address.clone() - }; - - // TODO fix this - // HIGH SECURITY - // Can drained funk if edge case approximation issue - // Assertion: Check if the contract has enough quote tokens to transfer to liquidity - // Edge case of approximation estimation amount and fees can cause it + // Handle edge case where contract balance is insufficient + let quote_token = IERC20Dispatcher { contract_address: launch.token_quote.token_address.clone() }; let contract_quote_balance = quote_token.balance_of(get_contract_address()); - // println!("add liquidity contract_quote_balance final {:?}", contract_quote_balance); - // println!("initial_pool_supply : {}", lp_supply.clone()); - // println!("liquidity raised: {}", lp_quote_supply.clone()); - - if contract_quote_balance < lp_quote_supply - && contract_quote_balance < launch.threshold_liquidity { - // TODO just calculate the difference of round and substract it - // let new_lp_quote_rounded= lp_quote_supply - contract_quote_balance; - // lp_quote_supply = new_lp_quote_rounded.clone(); + if contract_quote_balance < lp_quote_supply && contract_quote_balance < launch.threshold_liquidity { lp_quote_supply = contract_quote_balance.clone(); } + + // Prepare launch parameters let params = EkuboUnrugLaunchParameters { - // owner: launch.owner, // TODO add optional parameters to be select LIQ percent to - // be lock to Unrug at some point - owner: get_contract_address(), // TODO add optional parameters to be select LIQ percent to be lock to Unrug at some point + owner: get_contract_address(), token_address: coin_address, quote_address: launch.token_quote.token_address.clone(), lp_supply: lp_supply.clone(), @@ -1644,56 +1511,32 @@ pub mod LaunchpadMarketplace { caller: get_caller_address() }; - let position_ekubo_address = unrug_liquidity.get_position_ekubo_address(); + // Approve tokens quote_token.approve(unrug_liquidity_address, lp_quote_supply); - // Approve Memecoin let memecoin = IERC20Dispatcher { contract_address: coin_address }; memecoin.approve(unrug_liquidity_address, lp_supply); + + // Launch on Ekubo let (id, position) = unrug_liquidity.launch_on_ekubo(coin_address, params); - let id_cast: u256 = id.try_into().unwrap(); - // Set token launched + + // Update launch state let mut launch_to_update = self.launched_coins.read(coin_address); launch_to_update.is_liquidity_launch = true; self.launched_coins.entry(coin_address).write(launch_to_update.clone()); - self - .emit( - LiquidityCreated { - id: id_cast, - pool: coin_address, - asset: coin_address, - quote_token_address: launch.token_quote.token_address, - owner: launch.owner, - exchange: SupportedExchanges::Ekubo, - is_unruggable: false - } - ); + // Emit event + self.emit( + LiquidityCreated { + id: id.try_into().unwrap(), + pool: coin_address, + asset: coin_address, + quote_token_address: launch.token_quote.token_address, + owner: launch.owner, + exchange: SupportedExchanges::Ekubo, + is_unruggable: false + } + ); - let token_state = self.token_created.read(coin_address); - // TODO set_launched - // let memecoin = IMemecoinDispatcher { contract_address: coin_address }; - // let factory_address = memecoin.get_factory_address(); - // if token_state.creator == get_contract_address() || factory_address == - // get_contract_address() { - // memecoin - // .set_launched( - // LiquidityType::EkuboNFT(id), - // LiquidityParameters::Ekubo( - // EkuboLiquidityParameters { - // quote_address, ekubo_pool_parameters: ekubo_pool_params - // } - // ), - // :transfer_restriction_delay, - // :max_percentage_buy_launch, - // :team_allocation, - // ); - // self - // .emit( - // MemecoinLaunched { - // memecoin_address, quote_token: quote_address, exchange_name: 'Ekubo' - // } - // ); - // } (id, position) } @@ -1701,48 +1544,38 @@ pub mod LaunchpadMarketplace { // Change preparation of state for lp_supply, approve etc for the Unrug V2 fn _add_liquidity_jediswap( ref self: ContractState, coin_address: ContractAddress, owner: ContractAddress - // params: EkuboLaunchParameters ) -> u256 { let unrug_liquidity = IUnrugLiquidityDispatcher { contract_address: self.unrug_liquidity_address.read() }; let launch = self.launched_coins.read(coin_address); - assert(launch.is_liquidity_launch == false, errors::LIQUIDITY_ALREADY_LAUNCHED); - let threshold_liquidity = launch.threshold_liquidity.clone(); - let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; - let mut threshold = threshold_liquidity - slippage_threshold; - // assert(launch.liquidity_raised >= threshold, errors::NO_THRESHOLD_RAISED); - let quote_address = launch.token_quote.token_address.clone(); - let lp_supply = launch.initial_pool_supply.clone(); - let quote_supply = launch.liquidity_raised.clone(); + + let quote_address = launch.token_quote.token_address; + let lp_supply = launch.initial_pool_supply; + let quote_supply = launch.liquidity_raised; let unlock_time = starknet::get_block_timestamp() + DEFAULT_MIN_LOCKTIME; - // let (id, position) = unrug_liquidity.launch_on_jediswap(coin_address, params); - let id_cast = unrug_liquidity + + let id = unrug_liquidity .launch_on_jediswap( coin_address, quote_address, lp_supply, quote_supply, unlock_time, owner ); - // TODO - // Add Locked position - // let (id, position) = unrug_liquidity.launch_on_jediswap(coin_address, quote_address, - // lp_supply, quote_supply, unlock_time); - // let id_cast: u256 = id.try_into().unwrap(); self .emit( LiquidityCreated { - id: id_cast, + id, pool: coin_address, asset: coin_address, - quote_token_address: launch.token_quote.token_address, + quote_token_address: quote_address, owner: launch.owner, exchange: SupportedExchanges::Jediswap, is_unruggable: false } ); - // (id, position) - id_cast + + id } } } From 8ff6357a92d1cb446ab11d96a0f0622f8237cb57 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Fri, 31 Jan 2025 09:10:33 +0100 Subject: [PATCH 03/11] refacto and clean3 --- .../.snfoundry_cache/.prev_tests_failed | 23 +- ...public_blastapi_io_rpc_v0_7_315925_v3.json | 1 + onchain/cairo/launchpad/Scarb copy.toml | 39 + .../launchpad/src/launchpad/launchpad.cairo | 667 ++++++------------ .../launchpad/src/tests/launchpad_tests.cairo | 10 +- 5 files changed, 258 insertions(+), 482 deletions(-) create mode 100644 onchain/cairo/launchpad/.snfoundry_cache/https___starknet_mainnet_public_blastapi_io_rpc_v0_7_315925_v3.json create mode 100644 onchain/cairo/launchpad/Scarb copy.toml diff --git a/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed b/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed index 27afc1f4a..3f006052f 100644 --- a/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed +++ b/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed @@ -1,19 +1,14 @@ +afk_launchpad::tests::launchpad_tests::launchpad_tests::test_buy_coin_exp_with_different_supply +afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all_exp_curve +afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_and_sell_exp +afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_end_to_end afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launch_token +afk_launchpad::tests::launchpad_tests::launchpad_tests::test_buy_coin_with_different_supply +afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all +afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all_and_claim +afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_and_sell afk_launchpad::tests::launchpad_tests::launchpad_tests::test_set_protocol_fee_percent_non_admin +afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps_exp afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launchpad_end_to_end_exponential -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_and_sell -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all_and_claim -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_end_to_end afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launchpad_end_to_end_linear -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_buy_coin_exp_with_different_supply -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_buy_coin_with_different_supply -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_and_sell_exp -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_integration_exp -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_integration_linear -afk_launchpad::tests::liquidity_tests::liquidity_tests::test_add_liquidity_ekubo -afk_launchpad::tests::unrug_tests::unrug_tests::test_add_liquidity_ekubo -afk_launchpad::tests::unrug_tests::unrug_tests::test_launch_on_jediswap -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all_exp_curve diff --git a/onchain/cairo/launchpad/.snfoundry_cache/https___starknet_mainnet_public_blastapi_io_rpc_v0_7_315925_v3.json b/onchain/cairo/launchpad/.snfoundry_cache/https___starknet_mainnet_public_blastapi_io_rpc_v0_7_315925_v3.json new file mode 100644 index 000000000..3a5c6028d --- /dev/null +++ b/onchain/cairo/launchpad/.snfoundry_cache/https___starknet_mainnet_public_blastapi_io_rpc_v0_7_315925_v3.json @@ -0,0 +1 @@ +{"cache_version":3,"storage_at":{},"nonce_at":{},"class_hash_at":{},"compiled_contract_class":{},"block_info":{"block_number":315925,"block_timestamp":1697102443,"sequencer_address":"0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8","gas_prices":{"eth_l1_gas_price":100000000000,"strk_l1_gas_price":100000000000,"eth_l1_data_gas_price":1000000,"strk_l1_data_gas_price":1000000000},"use_kzg_da":true}} \ No newline at end of file diff --git a/onchain/cairo/launchpad/Scarb copy.toml b/onchain/cairo/launchpad/Scarb copy.toml new file mode 100644 index 000000000..092753ff4 --- /dev/null +++ b/onchain/cairo/launchpad/Scarb copy.toml @@ -0,0 +1,39 @@ +[package] +name = "afk_launchpad" +version = "0.1.0" +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = ">=2.8.2" +openzeppelin = { git = "https://github.com/openzeppelin/cairo-contracts", tag="v0.18.0" } +ekubo = { git = "https://github.com/EkuboProtocol/abis", rev = "edb6de8c9baf515f1053bbab3d86825d54a63bc3"} +alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.30.0" } + +#[lib] + +[scripts] +test = "snforge test" + +[tool.fmt] +sort-module-level-items = true + +[[target.starknet-contract]] +casm = true +sierra = true +allowed-libfuncs-list.name = "experimental" + +[[tool.snforge.fork]] +name = "Mainnet" +url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_7" +block_id.number = "615925" + +[[tool.snforge.fork]] +name = "Sepolia" +url = "https://starknet-sepolia.public.blastapi.io/rpc/v0_7" +block_id.number = "615925" diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index dc0685eeb..aeee6af55 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -592,269 +592,136 @@ pub mod LaunchpadMarketplace { } // Buy coin by quote amount - // Get amount of coin receive based on token IN - // Calculate fees and protocol fees - // Transfer fees to the protocol fee destination - // Transfer quote to the user - // Update the state of the pool: Liquidity raised, available_supply, - // Update the share user: update amount_owned - // Emit the buy event + // Calculates amount of coin to receive based on quote token input + // Handles fees, updates pool state and user shares fn buy_coin_by_quote_amount( - ref self: ContractState, coin_address: ContractAddress, quote_amount: u256, - // ekubo_pool_params: Option + ref self: ContractState, + coin_address: ContractAddress, + quote_amount: u256, ) { + // Input validation assert(quote_amount > 0, errors::AMOUNT_ZERO); let caller = get_caller_address(); - let old_launch = self.launched_coins.read(coin_address); - assert(!old_launch.owner.is_zero(), errors::COIN_NOT_FOUND); - let memecoin = IERC20Dispatcher { contract_address: coin_address }; - let mut pool_coin = old_launch.clone(); - // let total_supply_memecoin = memecoin.total_supply(); - let threshold_liquidity = pool_coin.threshold_liquidity.clone(); - - // TODO erc20 token transfer - let token_quote = old_launch.token_quote.clone(); - let quote_token_address = token_quote.token_address.clone(); - let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; - let protocol_fee_percent = self.protocol_fee_percent.read(); - - // IF AMOUNT COIN TO HAVE => GET AMOUNT QUOTE TO PAID - let mut total_price = quote_amount.clone(); - let old_price = pool_coin.price.clone(); - - // TODO edge case remaining supply to buy easily - // In case the user want to buy more than the threshold - // Give the available supply - // if total_price + old_launch.liquidity_raised.clone() > threshold_liquidity { - // total_price = threshold_liquidity - old_launch.liquidity_raised.clone(); - // amount = pool_coin.available_supply; - // amount_protocol_fee = total_price * protocol_fee_percent / BPS; - // // remain_liquidity = total_price - amount_protocol_fee; - // remain_liquidity = total_price; - // } else { - // amount = self - // ._get_amount_by_type_of_coin_or_quote(coin_address, total_price, false, - // true); - // // remain_liquidity = total_price - amount_protocol_fee; - // erc20 - // .transfer_from( - // get_caller_address(), - // self.protocol_fee_destination.read(), - // amount_protocol_fee - // ); - // // println!("remain_liquidity {:?}", remain_liquidity); - // erc20.transfer_from(get_caller_address(), get_contract_address(), - // remain_liquidity); - // } - // TODO check if fees is enabled - let mut amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; - let mut remain_liquidity = total_price - amount_protocol_fee; - let mut remain_quote_to_liquidity = total_price; - // let mut remain_quote_to_liquidity = total_price - amount_protocol_fee; + // Get pool info + let mut pool = self.launched_coins.read(coin_address); + assert(!pool.owner.is_zero(), errors::COIN_NOT_FOUND); - let mut threshold_liquidity = pool_coin.threshold_liquidity.clone(); - // let mut amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + // Calculate fees and remaining quote amount + let protocol_fee_percent = self.protocol_fee_percent.read(); + let mut amount_protocol_fee = 0; + let mut remain_quote_to_liquidity = quote_amount; + let mut threshold_liquidity = pool.threshold_liquidity.clone(); + let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; - // let mut threshold = threshold_liquidity; + let mut threshold = threshold_liquidity - slippage_threshold; - threshold_liquidity = threshold_liquidity - slippage_threshold; - // TODO without fees if it's correct - let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); - let is_fees_protocol_buy_enabled = self.is_fees_protocol_buy_enabled.read(); - - // TODO edge cases - // Verify rounding of Fees - // Check fees send to protocol and liquidity and carefully verify - if is_fees_protocol_enabled && is_fees_protocol_buy_enabled { - // if pool_coin.liquidity_raised < quote_amount { - // total_price = pool_coin.liquidity_raised.clone(); - // amount_protocol_fee = total_price * protocol_fee_percent / BPS; - // remain_quote_to_liquidity = pool_coin.liquidity_raised.clone() - - // amount_protocol_fee; - // } else { - // remain_quote_to_liquidity = quote_amount - amount_protocol_fee; - // } - remain_liquidity = total_price - amount_protocol_fee; - - remain_quote_to_liquidity = total_price - amount_protocol_fee; - // remain_quote_to_liquidity = quote_amount - amount_protocol_fee; - // TODO check slippage and fees - // TODO check threshold min and Supply threshold - // threshold = threshold_liquidity - (slippage_threshold * 2); // add slippage and - // fees - threshold = threshold_liquidity - - (slippage_threshold + amount_protocol_fee); // add slippage and fees - // threshold = threshold_liquidity - // - (slippage_threshold); // add slippage and fees - // threshold = threshold_liquidity - // - (amount_protocol_fee); // add slippage and fees - erc20 - .transfer_from( - get_caller_address(), - self.protocol_fee_destination.read(), - amount_protocol_fee - ); + // Handle protocol fees if enabled + if self.is_fees_protocol_enabled.read() && self.is_fees_protocol_buy_enabled.read() { + amount_protocol_fee = quote_amount * protocol_fee_percent / BPS; + remain_quote_to_liquidity = quote_amount - amount_protocol_fee; + threshold -= amount_protocol_fee; + // Transfer protocol fee + let quote_token = IERC20Dispatcher { contract_address: pool.token_quote.token_address }; + quote_token.transfer_from( + caller, + self.protocol_fee_destination.read(), + amount_protocol_fee + ); } - //new liquidity after purchase - let new_liquidity = pool_coin.liquidity_raised + remain_quote_to_liquidity; - - // Verify pool has sufficient available supply - //assertion - // Add slippage threshold + let new_liquidity = pool.liquidity_raised + remain_quote_to_liquidity; // assert(new_liquidity <= threshold, errors::THRESHOLD_LIQUIDITY_EXCEEDED); - // Check if liquidity threshold raise - let threshold_liq = self.threshold_liquidity.read(); - let threshold_mc = self.threshold_market_cap.read(); - - // let mut amount = 0; - // Pay with quote token - // Transfer quote & coin - // TOdo fix issue price - let mut coin_amount = get_amount_by_type_of_coin_or_quote( - pool_coin.clone(), - coin_address.clone(), - remain_quote_to_liquidity.clone(), + // Calculate coin amount to receive + let coin_amount = get_amount_by_type_of_coin_or_quote( + pool.clone(), + coin_address, + remain_quote_to_liquidity, false, true ); - let mut amount_coin_received = coin_amount.clone(); - // TODO check available to buy - // TODO EDGES CASES - // Approximation amount - // TEST - // Can cause draining - // Todo check all memecoin amount possible to buy - // if pool_coin.total_token_holded < pool_coin.available_supply + amount { - // let amount = pool_coin.total_token_holded - pool_coin.available_supply; - // let amount_coin_received = pool_coin.total_token_holded - - // pool_coin.available_supply; - // // assert(amount < pool_coin.available_supply, - // errors::INSUFFICIENT_TOTAL_SUPPLY); - // // assert(pool_coin.total_token_holded < pool_coin.available_supply + amount, - // errors::INSUFFICIENT_TOTAL_SUPPLY); + // Verify sufficient supply + assert(pool.available_supply >= coin_amount, errors::INSUFFICIENT_SUPPLY); - // } - // if pool_coin.available_supply < amount { - // amount = pool_coin.available_supply; - // } - assert(pool_coin.available_supply >= coin_amount, errors::INSUFFICIENT_SUPPLY); - // println!("amount memecoin to receive {:?}", amount); - // TODO readd this check and check why it's broken - // println!("transfer protocol fees {:?}", amount_protocol_fee); - // println!("transfer remain_liquidity {:?}", remain_quote_to_liquidity); - erc20 - .transfer_from( - get_caller_address(), get_contract_address(), remain_quote_to_liquidity - ); + // Transfer quote tokens to contract + let quote_token = IERC20Dispatcher { contract_address: pool.token_quote.token_address }; + quote_token.transfer_from( + caller, + get_contract_address(), + remain_quote_to_liquidity + ); - // Update the Stats of pool: - // Liquidity raised - // Available supply - // Token holded - // pool_coin.liquidity_raised += remain_liquidity; - // Amount quote buy with fees deducted if enabled - // Optionally, re-calculate the quote amount based on the amount to ensure consistency - pool_coin.liquidity_raised += remain_quote_to_liquidity; - pool_coin.price = total_price; - // TODO TEST - // EDGE CASE - // HIGH RISK = CAN DRAINED ALL POOL VALUE - // TODO check approximation, rounding and edges cases - if coin_amount >= pool_coin.available_supply { - pool_coin.available_supply = 0; - pool_coin.total_token_holded += coin_amount; + // Update pool state + let old_price = pool.price; + pool.liquidity_raised += remain_quote_to_liquidity; + pool.price = quote_amount; + pool.total_token_holded += coin_amount; + + // pool.available_supply -= coin_amount; + if coin_amount >= pool.available_supply { + pool.available_supply = 0; + pool.total_token_holded += coin_amount; } else { - pool_coin.total_token_holded += coin_amount; - pool_coin.available_supply -= coin_amount; + pool.total_token_holded += coin_amount; + pool.available_supply -= coin_amount; } - // Update share and coin stats for an user - let mut old_share = self - .shares_by_users - .entry(get_caller_address()) - .entry(coin_address) - .read(); - - let mut share_user = old_share.clone(); - if share_user.owner.is_zero() { - share_user = - SharesTokenUser { - owner: get_caller_address(), - token_address: coin_address, - amount_owned: coin_amount, - amount_buy: coin_amount, - amount_sell: 0, - created_at: get_block_timestamp(), - total_paid: total_price, - is_claimable: true, - }; + // Update user shares + let mut share = self.shares_by_users.entry(caller).entry(coin_address).read(); + if share.owner.is_zero() { + share = SharesTokenUser { + owner: caller, + token_address: coin_address, + amount_owned: coin_amount, + amount_buy: coin_amount, + amount_sell: 0, + created_at: get_block_timestamp(), + total_paid: quote_amount, + is_claimable: true, + }; } else { - share_user.total_paid += total_price; - share_user.amount_owned += coin_amount; - share_user.amount_buy += coin_amount; + share.total_paid += quote_amount; + share.amount_owned += coin_amount; + share.amount_buy += coin_amount; } - // pool_coin.price = total_price / amount; - // let mc = (pool_coin.price * total_supply_memecoin); - // TODO add liquidity launch - // TOTAL_SUPPLY / 5 - // 20% go the liquidity - // 80% bought by others - - // TODO check reetrancy guard - // Update state - // self - // .shares_by_users - // .entry((get_caller_address(), coin_address)) - // .write(share_user.clone()); + self.shares_by_users.entry(caller).entry(coin_address).write(share); + + // Check if liquidity threshold reached + // let threshold = pool.threshold_liquidity - (pool.threshold_liquidity * SLIPPAGE_THRESHOLD / BPS); + self.launched_coins.entry(coin_address).write(pool); + + if pool.liquidity_raised >= threshold { + self.emit( + LiquidityCanBeAdded { + pool: pool.token_address, + asset: pool.token_address, + quote_token_address: pool.token_quote.token_address, + } + ); - self - .shares_by_users - .entry(get_caller_address()) - .entry(coin_address) - .write(share_user.clone()); - - // TODO finish test and fix - // Add slipage threshold - // Fix price of the last - self.launched_coins.entry(coin_address).write(pool_coin.clone()); - if pool_coin.liquidity_raised >= threshold { - self - .emit( - LiquidityCanBeAdded { - pool: pool_coin.token_address.clone(), - asset: pool_coin.token_address.clone(), - quote_token_address: pool_coin.token_quote.token_address.clone(), - } - ); - // TODO V2 Add Liquidity DEX selected by USER - // let launch_dex= pool_coin.dex_launch; - // self._add_liquidity(coin_address, SupportedExchanges::Ekubo); + // Add liquidity to DEX self._add_liquidity_ekubo(coin_address); - pool_coin.is_liquidity_launch = true; + pool.is_liquidity_launch = true; } - // Update the state of the pool - self.launched_coins.entry(coin_address).write(pool_coin.clone()); + // Update pool state + self.launched_coins.entry(coin_address).write(pool); - self - .emit( - BuyToken { - caller: get_caller_address(), - token_address: coin_address, - amount: coin_amount, - price: total_price, - protocol_fee: amount_protocol_fee, - // creator_fee: 0, - last_price: old_price, - timestamp: get_block_timestamp(), - quote_amount: remain_quote_to_liquidity - // quote_amount: quote_amount - } - ); + // Emit buy event + self.emit( + BuyToken { + caller, + token_address: coin_address, + amount: coin_amount, + price: quote_amount, + protocol_fee: amount_protocol_fee, + last_price: old_price, + timestamp: get_block_timestamp(), + quote_amount: remain_quote_to_liquidity + } + ); } // params @coin_address @coin_amount @@ -866,275 +733,149 @@ pub mod LaunchpadMarketplace { // Update the share user: update amount_owned // Emit the sell event fn sell_coin(ref self: ContractState, coin_address: ContractAddress, coin_amount: u256) { - let old_pool = self.launched_coins.read(coin_address); - assert(!old_pool.owner.is_zero(), errors::COIN_SHARE_NOT_FOUND); - assert(old_pool.is_liquidity_launch == false, errors::TOKEN_ALREADY_TRADEABLE); + // Validate pool exists and is not yet launched + let pool = self.launched_coins.read(coin_address); + assert(!pool.owner.is_zero(), errors::COIN_SHARE_NOT_FOUND); + assert(!pool.is_liquidity_launch, errors::TOKEN_ALREADY_TRADEABLE); + let caller = get_caller_address(); - let mut old_share = self + + // Get user's share and validate amount + let mut share = self .shares_by_users - .entry(get_caller_address()) + .entry(caller) .entry(coin_address) .read(); - // Verify Amount owned - let mut share_user = old_share.clone(); - // assert(share_user.amount_owned >= coin_amount, 'above supply'); - // TODO erc20 token transfer - let total_supply = old_pool.total_supply.clone(); - let token_quote = old_pool.token_quote.clone(); - let quote_token_address = token_quote.token_address.clone(); - - // Todo check user amount fee creator if needed - let creator_fee_percent = self.creator_fee_percent.read(); - let protocol_fee_percent = self.protocol_fee_percent.read(); - - // let amount_protocol_fee: u256 = coin_amount * protocol_fee_percent / BPS; - // let amount_creator_fee = coin_amount * creator_fee_percent / BPS; - - let mut remain_coin_amount = coin_amount.clone(); - // let mut remain_coin_amount = coin_amount; - // let remain_coin_amount = coin_amount - amount_protocol_fee; + // Adjust sell amount if needed + let mut sell_amount = if share.amount_owned < coin_amount { + share.amount_owned + } else { + coin_amount + }; - // TODO check - // Test edge case and calcul - // CAREFULLY CHECK AND TEST - let mut amount_owned = share_user.amount_owned.clone(); - // println!("sell share_user.amount_owned {:?}", share_user.amount_owned); + assert(share.amount_owned >= sell_amount, errors::ABOVE_SUPPLY); + // Calculate fees + let protocol_fee_percent = self.protocol_fee_percent.read(); + let creator_fee_percent = self.creator_fee_percent.read(); + assert( + protocol_fee_percent <= MAX_FEE_PROTOCOL && protocol_fee_percent >= MIN_FEE_PROTOCOL, + errors::PROTOCOL_FEE_OUT_OF_BOUNDS + ); assert( - share_user.amount_owned <= old_pool.total_token_holded, + share.amount_owned <= pool.total_token_holded, errors::SUPPLY_ABOVE_TOTAL_OWNED ); - // if share_user.amount_owned >= old_pool.total_token_holded { - // assert( - // share_user.amount_owned >= old_pool.total_token_holded, - // errors::SUPPLY_ABOVE_TOTAL_OWNED - // ); - // } - - // TODO CHECK error even if used amount_owned as an input in test - // Edge case calculation rounding - // Use max owned - // CAREFULLY CHECK AND TEST - - // println!("sell remain_coin_amount {:?}", remain_coin_amount); - if share_user.amount_owned < remain_coin_amount { - // Used max amount_owned - remain_coin_amount = share_user.amount_owned.clone(); - // remain_coin_amount = share_user.amount_owned; - } - - let amount_protocol_fee: u256 = remain_coin_amount.clone() * protocol_fee_percent / BPS; - let amount_creator_fee = remain_coin_amount.clone() * creator_fee_percent / BPS; - assert(share_user.amount_owned >= remain_coin_amount, errors::ABOVE_SUPPLY); - // println!("sell remain_coin_amount edge {:?}", remain_coin_amount); + + // Calculate quote token amounts let mut quote_amount_total = get_amount_by_type_of_coin_or_quote( - old_pool.clone(), coin_address.clone(), remain_coin_amount.clone(), true, false + pool.clone(), coin_address, sell_amount, true, false ); - // println!("sell quote_amount_total received {:?}", quote_amount_total); - // println!("sell amount quote to receive {:?}", quote_amount_total); + let mut quote_amount = quote_amount_total.clone(); - // println!("sell check quote_amount {:?}", quote_amount); - // println!("sell check liquidity_raised {:?}", old_pool.liquidity_raised); + let protocol_fee_amount = quote_amount * protocol_fee_percent / BPS; + let creator_fee_amount = quote_amount * creator_fee_percent / BPS; + + // let protocol_fee_amount = sell_amount * protocol_fee_percent / BPS; + // let creator_fee_amount = sell_amount * creator_fee_percent / BPS; - // TODO check fees - // TEST issue of Unrug + // Handle protocol fees if enabled + let is_fees_enabled = self.is_fees_protocol_enabled.read() + && self.is_fees_protocol_sell_enabled.read(); - let is_fees_protocol_enabled = self.is_fees_protocol_enabled.read(); - let is_fees_protocol_sell_enabled = self.is_fees_protocol_sell_enabled.read(); - let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; + let mut quote_fee_amount = 0_u256; + println!("check fees"); - let mut quote_amount_protocol_fee: u256 = quote_amount_total - * protocol_fee_percent - / BPS; - // let quote_amount = quote_amount_total - quote_amount_protocol_fee; - let mut quote_amount = quote_amount_total.clone(); - let mut total_quote_amount = quote_amount_total.clone(); - let mut quote_amount_received = quote_amount_total.clone(); - // CAREFULLY CHECK AND TEST - - if is_fees_protocol_enabled && is_fees_protocol_sell_enabled { - quote_amount = quote_amount_total - quote_amount_protocol_fee; - quote_amount_total = quote_amount_total - quote_amount_protocol_fee; - quote_amount_received = quote_amount_total - quote_amount_protocol_fee; - } - // println!("sell quote_amount received final {:?}", quote_amount); - // TODO - // Edge case calculation rounding - // HIGH SECURITY - // TODO due to estimation, approximation or rounding - // GET the approximation slippage tolerance too not drained liq if big error - // CAREFULLY CHECK AND TEST - // Can drained all fund - - if old_pool.liquidity_raised < quote_amount { - // TODO due to estimation, approximation or rounding - // maybe substract the difference between quote_amount and old_pool.liquidity_raised - // println!("old_pool.liquidity_raised < quote_amount"); - quote_amount = old_pool.liquidity_raised.clone(); - quote_amount_total = old_pool.liquidity_raised.clone(); - quote_amount_received = old_pool.liquidity_raised.clone(); + if is_fees_enabled { + quote_fee_amount = quote_amount * protocol_fee_percent / BPS; + quote_amount -= quote_fee_amount; } - // Overwrite protocol fees and quote amount after check liquidity raised and contract - // quote balance - quote_amount_protocol_fee = quote_amount * protocol_fee_percent / BPS; - - // quote_amount = quote_amount - quote_amount_protocol_fee; - // quote_amount_total = quote_amount - quote_amount_protocol_fee; - // quote_amount_received = quote_amount - quote_amount_protocol_fee; - if is_fees_protocol_enabled && is_fees_protocol_sell_enabled { - quote_amount = quote_amount_total - quote_amount_protocol_fee; - quote_amount_total = quote_amount_total - quote_amount_protocol_fee; - quote_amount_received = quote_amount_total - quote_amount_protocol_fee; - } + // Validate against liquidity and balance constraints + println!("check liq raised and quote amount"); - // TODO edge case approximation, rounding - // CAREFULLY TEST EDGE CASE AND FUZZING - // HIGH RISK = MONEY DRAINING - // CAN DRAINED ALL MONEY - // TODO fixed rounding and approximation - // HIGH RISK SECURITY - // // Assertion: Check if the contract has enough quote tokens to transfer - let contract_quote_balance = erc20.balance_of(get_contract_address()); - // println!("sell contract_quote_balance final {:?}", contract_quote_balance); - - // // if contract_quote_balance < quote_amount { - // if contract_quote_balance < quote_amount && contract_quote_balance < - // old_pool.threshold_liquidity.clone() { - // println!("contract quote above try edge case rounding"); - // if is_fees_protocol_sell_enabled { - // quote_amount = contract_quote_balance.clone() - quote_amount_protocol_fee; - // } else { - // quote_amount = contract_quote_balance.clone(); - // } - // } - // println!("sell quote_amount received final after check balance {:?}", quote_amount); + if pool.liquidity_raised < quote_amount { + println!("pool.liquidity_raised < quote_amount"); + quote_amount = pool.liquidity_raised; + if is_fees_enabled { + quote_fee_amount = quote_amount * protocol_fee_percent / BPS; + quote_amount -= quote_fee_amount; + } + } - // assert(old_pool.liquidity_raised >= quote_amount, 'liquidity <= amount'); - assert(old_pool.liquidity_raised >= quote_amount, errors::LIQUIDITY_BELOW_AMOUNT); + println!("quote_amount: {}", quote_amount.clone()); + assert(pool.liquidity_raised >= quote_amount, errors::LIQUIDITY_BELOW_AMOUNT); - // TODO fix this function - // let mut total_price = amount; - // println!("amount {:?}", amount); - // println!("coin_amount {:?}", coin_amount); - // println!("total_price {:?}", total_price); + // Process transfers + let quote_token = IERC20Dispatcher { contract_address: pool.token_quote.token_address }; + println!("transfer fees: {}", quote_fee_amount.clone()); - // Ensure fee percentages are within valid bounds - assert( - protocol_fee_percent <= MAX_FEE_PROTOCOL - && protocol_fee_percent >= MIN_FEE_PROTOCOL, - errors::PROTOCOL_FEE_OUT_OF_BOUNDS - // 'protocol fee out' - ); - // assert( - // creator_fee_percent <= MAX_FEE_CREATOR && creator_fee_percent >= MIN_FEE_CREATOR, - // 'creator_fee out' - // ); - - // assert!(old_share.amount_owned >= amount, "share to sell > supply"); - // println!("amount{:?}", amount); - // assert!(total_supply >= quote_amount, "share to sell > supply"); - // assert( old_pool.liquidity_raised >= quote_amount, 'liquidity_raised <= amount'); - - // let old_price = old_pool.price.clone(); - let total_price = old_pool.price.clone(); - // Update keys with new values - let mut pool_update = old_pool.clone(); - - // let remain_coin_amount = total_price ; - - // Ensure fee calculations are correct - // assert( - // amount_to_user + amount_protocol_fee + amount_creator_fee == quote_amount, - // 'fee calculation mismatch' - // ); - - // Transfer protocol fee to the designated destination - // println!("sell transfer fees protocol"); - - // TODO edge case fees threshold - // CAREFULLY TEST - // HIGH RISK = BLOCKED SELL - // println!("sell quote_amount_protocol_fee {:?}", quote_amount_protocol_fee); - // // TODO fixed rounding before - - if is_fees_protocol_enabled && is_fees_protocol_sell_enabled { - // TODO edgecase - // println!("sell transfer FEES {:?}", quote_amount_protocol_fee); - erc20.transfer(self.protocol_fee_destination.read(), quote_amount_protocol_fee); + if is_fees_enabled && quote_fee_amount > 0 { + quote_token.transfer(self.protocol_fee_destination.read(), quote_fee_amount); } + println!("transfer quote amount: {}", quote_amount.clone()); + let balance_contract = quote_token.balance_of(get_contract_address()); + println!("balance_contract: {}", balance_contract.clone()); - // println!("sell transfer quote amount"); - // Transfer the remaining quote amount to the user - if quote_amount > 0 { - // println!("sell transfer quote amount {:?}", quote_amount); - erc20.transfer(caller, quote_amount); - } + // assert(balance_contract >= quote_amount, errors::BALANCE_CONTRACT_BELOW_AMOUNT); - // Assertion: Ensure the user receives the correct amount - // let user_received = erc20.balance_of(caller); - // assert(user_received >= , 'user not receive amount'); - // TODO sell coin if it's already sendable and transferable - // ENABLE if direct launch coin + // TODO audit + // Security check to do. + // Rounding issue and approximation of the quote amount caused overflow + if balance_contract > quote_amount { + quote_token.transfer(caller, quote_amount); + } else { + let amount_paid= quote_amount - balance_contract; + println!("amount_paid: {}", amount_paid.clone()); + quote_token.transfer(caller, amount_paid); + } - // TODO fix amount owned and sellable. - // Update share user coin - // println!("sell update amount owned"); + // if quote_amount > 0 { + // quote_token.transfer(caller, quote_amount); + // } - share_user.amount_owned -= remain_coin_amount; - share_user.amount_sell += remain_coin_amount; + // Update state + println!("update share"); + + share.amount_owned -= sell_amount; + share.amount_sell += sell_amount; - // TODO check reetrancy guard + let mut updated_pool = pool.clone(); + println!("update pool"); - // Assertion: Ensure pool liquidity remains consistent - assert( - old_pool.liquidity_raised >= quote_amount, - errors::POOL_LIQUIDITY_INCONSISTENCY_AFTER_SALE - ); - // TODO finish update state - // pool_update.price = total_price; - // println!("sell update pool"); - - // println!("sell pool update liq raised"); - // println!("remain_coin_amount {:?}", remain_coin_amount); - - // TODO check - // HIGH SECURITY - if pool_update.liquidity_raised >= quote_amount { - // println!( - // "pool_update.liquidity_raised > quote_amount {:?}", - // pool_update.liquidity_raised > quote_amount - // ); - pool_update.liquidity_raised -= quote_amount; + updated_pool.liquidity_raised = if updated_pool.liquidity_raised >= quote_amount { + updated_pool.liquidity_raised - quote_amount } else { - pool_update.liquidity_raised = 0_u256; - } - // println!("try update total_token_holded {:?}", pool_update.total_token_holded); - pool_update.total_token_holded -= remain_coin_amount; - // println!("try update available supply {:?}", pool_update.available_supply); - pool_update.available_supply += remain_coin_amount; + 0_u256 + }; + updated_pool.total_token_holded -= sell_amount; + updated_pool.available_supply += sell_amount; + + // Save updated state self .shares_by_users - .entry(get_caller_address()) - .entry(coin_address.clone()) - .write(share_user.clone()); + .entry(caller) + .entry(coin_address) + .write(share); - self.launched_coins.entry(coin_address.clone()).write(pool_update.clone()); + self.launched_coins.entry(coin_address).write(updated_pool.clone()); + + // Emit event self .emit( SellToken { - caller: caller, + caller, key_user: coin_address, amount: quote_amount, - price: total_price, // Adjust if necessary - protocol_fee: quote_amount_protocol_fee, - creator_fee: amount_creator_fee, + price: updated_pool.price, + protocol_fee: quote_fee_amount, + creator_fee: creator_fee_amount, timestamp: get_block_timestamp(), - last_price: old_pool.price, - coin_amount: remain_coin_amount, + last_price: pool.price, + coin_amount: sell_amount, } ); } diff --git a/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo b/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo index b3281579e..73f9221c0 100644 --- a/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo +++ b/onchain/cairo/launchpad/src/tests/launchpad_tests.cairo @@ -125,13 +125,13 @@ mod launchpad_tests { 0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067.try_into().unwrap() } - // fn EKUBO_REGISTRY() -> ContractAddress { - // 0x0013e25867b6eef62703735aa4cfa7754e72f4e94a56c9d3d9ad8ebe86cee4aa.try_into().unwrap() - // } - // v3 fn EKUBO_REGISTRY() -> ContractAddress { - 0x064bdb4094881140bc39340146c5fcc5a187a98aec5a53f448ac702e5de5067e.try_into().unwrap() + 0x0013e25867b6eef62703735aa4cfa7754e72f4e94a56c9d3d9ad8ebe86cee4aa.try_into().unwrap() } + // v3 + // fn EKUBO_REGISTRY() -> ContractAddress { + // 0x064bdb4094881140bc39340146c5fcc5a187a98aec5a53f448ac702e5de5067e.try_into().unwrap() + // } fn JEDISWAP_FACTORY() -> ContractAddress { 0x01aa950c9b974294787de8df8880ecf668840a6ab8fa8290bf2952212b375148.try_into().unwrap() From d2d2e955d1480c6c828f414251fa76a5f046b8ea Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sat, 1 Feb 2025 09:48:42 +0100 Subject: [PATCH 04/11] print to check rounding in sell --- .../.snfoundry_cache/.prev_tests_failed | 12 +----- .../launchpad/src/launchpad/launchpad.cairo | 42 ++++++++++++++----- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed b/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed index 3f006052f..7ed5eafbf 100644 --- a/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed +++ b/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed @@ -1,14 +1,4 @@ -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_buy_coin_exp_with_different_supply -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all_exp_curve -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_and_sell_exp -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_end_to_end -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launch_token -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_buy_coin_with_different_supply -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_all_and_claim -afk_launchpad::tests::launchpad_tests::launchpad_tests::launchpad_buy_and_sell afk_launchpad::tests::launchpad_tests::launchpad_tests::test_set_protocol_fee_percent_non_admin +afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launch_token afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps_exp -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launchpad_end_to_end_exponential -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launchpad_end_to_end_linear diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index aeee6af55..09f820f65 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -616,8 +616,9 @@ pub mod LaunchpadMarketplace { let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; let mut threshold = threshold_liquidity - slippage_threshold; - // Handle protocol fees if enabled + // HIGH SECURITY ISSUE + // Security check to do if self.is_fees_protocol_enabled.read() && self.is_fees_protocol_buy_enabled.read() { amount_protocol_fee = quote_amount * protocol_fee_percent / BPS; remain_quote_to_liquidity = quote_amount - amount_protocol_fee; @@ -634,6 +635,9 @@ pub mod LaunchpadMarketplace { // assert(new_liquidity <= threshold, errors::THRESHOLD_LIQUIDITY_EXCEEDED); // Calculate coin amount to receive + // AUDIT + // High security check to do. + // Verify rounding issue and approximation of the quote amount caused overflow let coin_amount = get_amount_by_type_of_coin_or_quote( pool.clone(), coin_address, @@ -763,18 +767,16 @@ pub mod LaunchpadMarketplace { protocol_fee_percent <= MAX_FEE_PROTOCOL && protocol_fee_percent >= MIN_FEE_PROTOCOL, errors::PROTOCOL_FEE_OUT_OF_BOUNDS ); - assert( - share.amount_owned <= pool.total_token_holded, - errors::SUPPLY_ABOVE_TOTAL_OWNED - ); - - - + // assert( + // share.amount_owned <= pool.total_token_holded, + // errors::SUPPLY_ABOVE_TOTAL_OWNED + // ); // Calculate quote token amounts let mut quote_amount_total = get_amount_by_type_of_coin_or_quote( pool.clone(), coin_address, sell_amount, true, false ); let mut quote_amount = quote_amount_total.clone(); + println!("quote_amount_total first: {}", quote_amount_total.clone()); let protocol_fee_amount = quote_amount * protocol_fee_percent / BPS; let creator_fee_amount = quote_amount * creator_fee_percent / BPS; @@ -789,12 +791,17 @@ pub mod LaunchpadMarketplace { let mut quote_fee_amount = 0_u256; println!("check fees"); + // Substract fees protocol from quote amount + // AUDIT + // High security check to do: rounding, approximation, balance of contract if is_fees_enabled { quote_fee_amount = quote_amount * protocol_fee_percent / BPS; quote_amount -= quote_fee_amount; } // Validate against liquidity and balance constraints + // AUDIT + // High security check to do. println!("check liq raised and quote amount"); if pool.liquidity_raised < quote_amount { @@ -822,16 +829,29 @@ pub mod LaunchpadMarketplace { // assert(balance_contract >= quote_amount, errors::BALANCE_CONTRACT_BELOW_AMOUNT); + let quote_amount_paid = quote_amount.clone(); + // let quote_amount_paid = quote_amount - quote_fee_amount; + println!("quote_amount_paid: {}", quote_amount_paid.clone()); + // TODO audit + // HIGH SECURITY ISSUE // Security check to do. // Rounding issue and approximation of the quote amount caused overflow - if balance_contract > quote_amount { - quote_token.transfer(caller, quote_amount); + if balance_contract > quote_amount_paid { + quote_token.transfer(caller, quote_amount_paid); } else { - let amount_paid= quote_amount - balance_contract; + // let quote_amount_paid = quote_amount - quote_fee_amount; + let amount_paid= quote_amount_paid - balance_contract; println!("amount_paid: {}", amount_paid.clone()); quote_token.transfer(caller, amount_paid); } + // if balance_contract > quote_amount { + // quote_token.transfer(caller, quote_amount); + // } else { + // let amount_paid= quote_amount - balance_contract; + // println!("amount_paid: {}", amount_paid.clone()); + // quote_token.transfer(caller, amount_paid); + // } // if quote_amount > 0 { // quote_token.transfer(caller, quote_amount); From 9add3b2a153983e84e402f74f214a70a8f117b26 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sat, 1 Feb 2025 11:28:45 +0100 Subject: [PATCH 05/11] start fix perdersen test --- onchain/cairo/afk/src/lib.cairo | 6 +- onchain/cairo/afk/src/pedersen.cairo | 71 +++++++++++++++++++ .../.snfoundry_cache/.prev_tests_failed | 4 +- .../launchpad/src/launchpad/launchpad.cairo | 3 +- onchain/cairo/utils/src/pedersen.cairo | 9 +-- 5 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 onchain/cairo/afk/src/pedersen.cairo diff --git a/onchain/cairo/afk/src/lib.cairo b/onchain/cairo/afk/src/lib.cairo index 7b639bb66..d73183bc8 100644 --- a/onchain/cairo/afk/src/lib.cairo +++ b/onchain/cairo/afk/src/lib.cairo @@ -1,17 +1,13 @@ pub mod bip340; +pub mod pedersen; pub mod errors; -// pub mod interfaces; - pub mod math; pub mod sha256; pub mod social; - pub mod staking; - pub mod utils; - pub mod interfaces { pub mod erc20; pub mod erc20_mintable; diff --git a/onchain/cairo/afk/src/pedersen.cairo b/onchain/cairo/afk/src/pedersen.cairo new file mode 100644 index 000000000..69afce35a --- /dev/null +++ b/onchain/cairo/afk/src/pedersen.cairo @@ -0,0 +1,71 @@ +use core::ec::stark_curve::GEN_X; +use core::ec::stark_curve::GEN_Y; +use core::fmt::{Display, Formatter, Error}; +use core::ec::{EcPoint, EcPointTrait, ec_point_unwrap, NonZeroEcPoint}; +use core::poseidon::PoseidonTrait; +use core::hash::{HashStateTrait, HashStateExTrait,}; + +pub impl EcPointDisplay of Display { + fn fmt(self: @EcPoint, ref f: Formatter) -> Result<(), Error> { + let non_zero: NonZeroEcPoint = (*self).try_into().unwrap(); + let (x, y): (felt252, felt252) = ec_point_unwrap(non_zero); + writeln!(f, "Point ({x}, {y})") + } +} +pub fn pedersen_commit(value: felt252, salt: felt252, H: EcPoint) -> EcPoint { + let generator: EcPoint = EcPointTrait::new(GEN_X, GEN_Y).unwrap(); + let c_1 = generator.mul(value); + let c_2 = H.mul(salt); + + c_1 + c_2 //Elliptic curve point addition +} + +pub fn verify_commitment(commitment: EcPoint, value: felt252, salt: felt252, H: EcPoint) -> bool { + // Recompute commitment using revealed value and salt + let computed_commitment = pedersen_commit(value, salt, H); + + // Compare the original commitment with the recomputed one + let non_zero1: NonZeroEcPoint = commitment.try_into().unwrap(); + let non_zero2: NonZeroEcPoint = computed_commitment.try_into().unwrap(); + let (x1, y1) = ec_point_unwrap(non_zero1); + let (x2, y2) = ec_point_unwrap(non_zero2); + x1 == x2 && y1 == y2 +} + +pub fn hash_to_curve() -> Option { + let g_hash = PoseidonTrait::new().update(GEN_X); + let mut counter = 0; + loop { + // 2^16 is the maximum number of attempts we allow to find a valid point + if counter == 65536_u32 { + break Option::None; + } + let hash: felt252 = g_hash.update_with(counter).finalize(); + println!("Hash_to_curve counter: {}", counter); + // Check if the point is on the curve + match EcPointTrait::new_from_x(hash) { + // If the point is on the curve, return it + Option::Some(point) => { break Option::Some(point); }, + // If the point is not on the curve, try again + Option::None(_) => { counter += 1; } + } + } +} + +#[cfg(test)] +mod tests { + // use super::*; + use super::{pedersen_commit, verify_commitment, hash_to_curve}; + + #[test] + fn test_pedersen_commit() { + let H: EcPoint = hash_to_curve().unwrap(); + let value: felt252 = 77777; + let salt: felt252 = 228282189421094; + let commitment = pedersen_commit(value, salt, H); + + let is_valid = verify_commitment(commitment, value, salt, H); + assert(is_valid, 'The commitment is not valid'); + } + +} \ No newline at end of file diff --git a/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed b/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed index 7ed5eafbf..c09dbc160 100644 --- a/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed +++ b/onchain/cairo/launchpad/.snfoundry_cache/.prev_tests_failed @@ -1,4 +1,4 @@ -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_set_protocol_fee_percent_non_admin -afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launch_token afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps afk_launchpad::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps_exp +afk_launchpad::tests::launchpad_tests::launchpad_tests::test_launch_token +afk_launchpad::tests::launchpad_tests::launchpad_tests::test_set_protocol_fee_percent_non_admin diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index 09f820f65..eb6a2b938 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -841,7 +841,8 @@ pub mod LaunchpadMarketplace { quote_token.transfer(caller, quote_amount_paid); } else { // let quote_amount_paid = quote_amount - quote_fee_amount; - let amount_paid= quote_amount_paid - balance_contract; + let difference_amount= quote_amount_paid - balance_contract; + let amount_paid= quote_amount_paid - difference_amount; println!("amount_paid: {}", amount_paid.clone()); quote_token.transfer(caller, amount_paid); } diff --git a/onchain/cairo/utils/src/pedersen.cairo b/onchain/cairo/utils/src/pedersen.cairo index d90ea73d4..3ac4f9ebc 100644 --- a/onchain/cairo/utils/src/pedersen.cairo +++ b/onchain/cairo/utils/src/pedersen.cairo @@ -13,7 +13,7 @@ pub impl EcPointDisplay of Display { } } -fn pedersen_commit(value: felt252, salt: felt252, H: EcPoint) -> EcPoint { +pub fn pedersen_commit(value: felt252, salt: felt252, H: EcPoint) -> EcPoint { let generator: EcPoint = EcPointTrait::new(GEN_X, GEN_Y).unwrap(); let c_1 = generator.mul(value); let c_2 = H.mul(salt); @@ -21,7 +21,7 @@ fn pedersen_commit(value: felt252, salt: felt252, H: EcPoint) -> EcPoint { c_1 + c_2 //Elliptic curve point addition } -fn verify_commitment(commitment: EcPoint, value: felt252, salt: felt252, H: EcPoint) -> bool { +pub fn verify_commitment(commitment: EcPoint, value: felt252, salt: felt252, H: EcPoint) -> bool { // Recompute commitment using revealed value and salt let computed_commitment = pedersen_commit(value, salt, H); @@ -33,7 +33,7 @@ fn verify_commitment(commitment: EcPoint, value: felt252, salt: felt252, H: EcPo x1 == x2 && y1 == y2 } -fn hash_to_curve() -> Option { +pub fn hash_to_curve() -> Option { let g_hash = PoseidonTrait::new().update(GEN_X); let mut counter = 0; loop { @@ -55,7 +55,8 @@ fn hash_to_curve() -> Option { #[cfg(test)] mod tests { - use super::*; + // use super::*; + use afk::pedersen::{pedersen_commit, verify_commitment, hash_to_curve}; #[test] fn test_pedersen_commit() { From 2fb15ec0d7a97f0e270a47079635d64e257c2ec5 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sat, 1 Feb 2025 12:50:05 +0100 Subject: [PATCH 06/11] fix pedersen import --- onchain/cairo/afk/src/pedersen.cairo | 6 ++++++ onchain/cairo/utils/src/pedersen.cairo | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/onchain/cairo/afk/src/pedersen.cairo b/onchain/cairo/afk/src/pedersen.cairo index 69afce35a..ba84f619c 100644 --- a/onchain/cairo/afk/src/pedersen.cairo +++ b/onchain/cairo/afk/src/pedersen.cairo @@ -56,6 +56,12 @@ pub fn hash_to_curve() -> Option { mod tests { // use super::*; use super::{pedersen_commit, verify_commitment, hash_to_curve}; + use core::ec::stark_curve::GEN_X; + use core::ec::stark_curve::GEN_Y; + use core::fmt::{Display, Formatter, Error}; + use core::ec::{EcPoint, EcPointTrait, ec_point_unwrap, NonZeroEcPoint}; + use core::poseidon::PoseidonTrait; + use core::hash::{HashStateTrait, HashStateExTrait,}; #[test] fn test_pedersen_commit() { diff --git a/onchain/cairo/utils/src/pedersen.cairo b/onchain/cairo/utils/src/pedersen.cairo index 3ac4f9ebc..0e1edcde8 100644 --- a/onchain/cairo/utils/src/pedersen.cairo +++ b/onchain/cairo/utils/src/pedersen.cairo @@ -56,7 +56,14 @@ pub fn hash_to_curve() -> Option { #[cfg(test)] mod tests { // use super::*; - use afk::pedersen::{pedersen_commit, verify_commitment, hash_to_curve}; + use super::{pedersen_commit, verify_commitment, hash_to_curve}; + use core::ec::stark_curve::GEN_X; + use core::ec::stark_curve::GEN_Y; + use core::fmt::{Display, Formatter, Error}; + use core::ec::{EcPoint, EcPointTrait, ec_point_unwrap, NonZeroEcPoint}; + use core::poseidon::PoseidonTrait; + use core::hash::{HashStateTrait, HashStateExTrait,}; + #[test] fn test_pedersen_commit() { From 7a9edce61ab7a1127bae00bebf852ab1e7cf56f9 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sat, 1 Feb 2025 13:43:26 +0100 Subject: [PATCH 07/11] add try catch indexer --- .../src/indexer/buy-token.indexer.ts | 206 ++++++++--------- .../src/indexer/liquidity-added.indexer.ts | 207 +++++++++--------- .../launchpad/src/launchpad/launchpad.cairo | 24 +- 3 files changed, 223 insertions(+), 214 deletions(-) diff --git a/apps/nestjs-indexer/src/indexer/buy-token.indexer.ts b/apps/nestjs-indexer/src/indexer/buy-token.indexer.ts index e680f4a18..79569f288 100644 --- a/apps/nestjs-indexer/src/indexer/buy-token.indexer.ts +++ b/apps/nestjs-indexer/src/indexer/buy-token.indexer.ts @@ -54,106 +54,110 @@ export class BuyTokenIndexer { event: starknet.IEvent, transaction: starknet.ITransaction, ) { - const { - blockNumber, - blockHash: blockHashFelt, - timestamp: blockTimestamp, - } = header; - - const blockHash = validateAndParseAddress( - `0x${FieldElement.toBigInt(blockHashFelt).toString(16)}`, - ) as ContractAddress; - - const transactionHashFelt = transaction.meta.hash; - const transactionHash = validateAndParseAddress( - `0x${FieldElement.toBigInt(transactionHashFelt).toString(16)}`, - ) as ContractAddress; - - const transferId = `${transactionHash}_${event.index}`; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [_, callerFelt, tokenAddressFelt] = event.keys; - - const ownerAddress = validateAndParseAddress( - `0x${FieldElement.toBigInt(callerFelt).toString(16)}`, - ) as ContractAddress; - - const tokenAddress = validateAndParseAddress( - `0x${FieldElement.toBigInt(tokenAddressFelt).toString(16)}`, - ) as ContractAddress; - - const [ - amountLow, - amountHigh, - priceLow, - priceHigh, - protocolFeeLow, - protocolFeeHigh, - lastPriceLow, - lastPriceHigh, - timestampFelt, - quoteAmountLow, - quoteAmountHigh, - ] = event.data; - - const amountRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(amountLow), - high: FieldElement.toBigInt(amountHigh), - }); - const amount = formatUnits(amountRaw, constants.DECIMALS).toString(); - - const priceRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(priceLow), - high: FieldElement.toBigInt(priceHigh), - }); - const price = formatUnits(priceRaw, constants.DECIMALS); - - const protocolFeeRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(protocolFeeLow), - high: FieldElement.toBigInt(protocolFeeHigh), - }); - const protocolFee = formatUnits( - protocolFeeRaw, - constants.DECIMALS, - ).toString(); - - const lastPriceRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(lastPriceLow), - high: FieldElement.toBigInt(lastPriceHigh), - }); - const lastPrice = formatUnits(lastPriceRaw, constants.DECIMALS).toString(); - - const quoteAmountRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(quoteAmountLow), - high: FieldElement.toBigInt(quoteAmountHigh), - }); - const quoteAmount = formatUnits( - quoteAmountRaw, - constants.DECIMALS, - ).toString(); - - const timestamp = new Date( - Number(FieldElement.toBigInt(timestampFelt)) * 1000, - ); - - const data = { - transferId, - network: 'starknet-sepolia', - transactionHash, - blockNumber: Number(blockNumber), - blockHash, - blockTimestamp: new Date(Number(blockTimestamp.seconds) * 1000), - ownerAddress, - memecoinAddress: tokenAddress, - amount: Number(amount), - price, - protocolFee, - lastPrice, - quoteAmount, - timestamp, - transactionType: 'buy', - }; - - await this.buyTokenService.create(data); + try { + const { + blockNumber, + blockHash: blockHashFelt, + timestamp: blockTimestamp, + } = header; + + const blockHash = validateAndParseAddress( + `0x${FieldElement.toBigInt(blockHashFelt).toString(16)}`, + ) as ContractAddress; + + const transactionHashFelt = transaction.meta.hash; + const transactionHash = validateAndParseAddress( + `0x${FieldElement.toBigInt(transactionHashFelt).toString(16)}`, + ) as ContractAddress; + + const transferId = `${transactionHash}_${event.index}`; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, callerFelt, tokenAddressFelt] = event.keys; + + const ownerAddress = validateAndParseAddress( + `0x${FieldElement.toBigInt(callerFelt).toString(16)}`, + ) as ContractAddress; + + const tokenAddress = validateAndParseAddress( + `0x${FieldElement.toBigInt(tokenAddressFelt).toString(16)}`, + ) as ContractAddress; + + const [ + amountLow, + amountHigh, + priceLow, + priceHigh, + protocolFeeLow, + protocolFeeHigh, + lastPriceLow, + lastPriceHigh, + timestampFelt, + quoteAmountLow, + quoteAmountHigh, + ] = event.data; + + const amountRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(amountLow), + high: FieldElement.toBigInt(amountHigh), + }); + const amount = formatUnits(amountRaw, constants.DECIMALS).toString(); + + const priceRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(priceLow), + high: FieldElement.toBigInt(priceHigh), + }); + const price = formatUnits(priceRaw, constants.DECIMALS); + + const protocolFeeRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(protocolFeeLow), + high: FieldElement.toBigInt(protocolFeeHigh), + }); + const protocolFee = formatUnits( + protocolFeeRaw, + constants.DECIMALS, + ).toString(); + + const lastPriceRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(lastPriceLow), + high: FieldElement.toBigInt(lastPriceHigh), + }); + const lastPrice = formatUnits(lastPriceRaw, constants.DECIMALS).toString(); + + const quoteAmountRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(quoteAmountLow), + high: FieldElement.toBigInt(quoteAmountHigh), + }); + const quoteAmount = formatUnits( + quoteAmountRaw, + constants.DECIMALS, + ).toString(); + + const timestamp = new Date( + Number(FieldElement.toBigInt(timestampFelt)) * 1000, + ); + + const data = { + transferId, + network: 'starknet-sepolia', + transactionHash, + blockNumber: Number(blockNumber), + blockHash, + blockTimestamp: new Date(Number(blockTimestamp.seconds) * 1000), + ownerAddress, + memecoinAddress: tokenAddress, + amount: Number(amount), + price, + protocolFee, + lastPrice, + quoteAmount, + timestamp, + transactionType: 'buy', + }; + + await this.buyTokenService.create(data); + } catch (error) { + console.log("Error BuyTokenIndexer", error) + } } } diff --git a/apps/nestjs-indexer/src/indexer/liquidity-added.indexer.ts b/apps/nestjs-indexer/src/indexer/liquidity-added.indexer.ts index 828146412..239ac29f5 100644 --- a/apps/nestjs-indexer/src/indexer/liquidity-added.indexer.ts +++ b/apps/nestjs-indexer/src/indexer/liquidity-added.indexer.ts @@ -54,106 +54,111 @@ export class LiquidityAddedIndexer { event: starknet.IEvent, transaction: starknet.ITransaction, ) { - const { - blockNumber, - blockHash: blockHashFelt, - timestamp: blockTimestamp, - } = header; - - const blockHash = validateAndParseAddress( - `0x${FieldElement.toBigInt(blockHashFelt).toString(16)}`, - ) as ContractAddress; - - const transactionHashFelt = transaction.meta.hash; - const transactionHash = validateAndParseAddress( - `0x${FieldElement.toBigInt(transactionHashFelt).toString(16)}`, - ) as ContractAddress; - - const transferId = `${transactionHash}_${event.index}`; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [_, callerFelt, tokenAddressFelt] = event.keys; - - const ownerAddress = validateAndParseAddress( - `0x${FieldElement.toBigInt(callerFelt).toString(16)}`, - ) as ContractAddress; - - const tokenAddress = validateAndParseAddress( - `0x${FieldElement.toBigInt(tokenAddressFelt).toString(16)}`, - ) as ContractAddress; - - const [ - amountLow, - amountHigh, - priceLow, - priceHigh, - protocolFeeLow, - protocolFeeHigh, - lastPriceLow, - lastPriceHigh, - timestampFelt, - quoteAmountLow, - quoteAmountHigh, - ] = event.data; - - const amountRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(amountLow), - high: FieldElement.toBigInt(amountHigh), - }); - const amount = formatUnits(amountRaw, constants.DECIMALS).toString(); - - const priceRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(priceLow), - high: FieldElement.toBigInt(priceHigh), - }); - const price = formatUnits(priceRaw, constants.DECIMALS); - - const protocolFeeRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(protocolFeeLow), - high: FieldElement.toBigInt(protocolFeeHigh), - }); - const protocolFee = formatUnits( - protocolFeeRaw, - constants.DECIMALS, - ).toString(); - - const lastPriceRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(lastPriceLow), - high: FieldElement.toBigInt(lastPriceHigh), - }); - const lastPrice = formatUnits(lastPriceRaw, constants.DECIMALS).toString(); - - const quoteAmountRaw = uint256.uint256ToBN({ - low: FieldElement.toBigInt(quoteAmountLow), - high: FieldElement.toBigInt(quoteAmountHigh), - }); - const quoteAmount = formatUnits( - quoteAmountRaw, - constants.DECIMALS, - ).toString(); - - const timestamp = new Date( - Number(FieldElement.toBigInt(timestampFelt)) * 1000, - ); - - const data = { - transferId, - network: 'starknet-sepolia', - transactionHash, - blockNumber: Number(blockNumber), - blockHash, - blockTimestamp: new Date(Number(blockTimestamp.seconds) * 1000), - ownerAddress, - memecoinAddress: tokenAddress, - amount: Number(amount), - price, - protocolFee, - lastPrice, - quoteAmount, - timestamp, - transactionType: 'buy', - }; - - await this.liquidityAddedService.create(data); + try { + const { + blockNumber, + blockHash: blockHashFelt, + timestamp: blockTimestamp, + } = header; + + const blockHash = validateAndParseAddress( + `0x${FieldElement.toBigInt(blockHashFelt).toString(16)}`, + ) as ContractAddress; + + const transactionHashFelt = transaction.meta.hash; + const transactionHash = validateAndParseAddress( + `0x${FieldElement.toBigInt(transactionHashFelt).toString(16)}`, + ) as ContractAddress; + + const transferId = `${transactionHash}_${event.index}`; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, callerFelt, tokenAddressFelt] = event.keys; + + const ownerAddress = validateAndParseAddress( + `0x${FieldElement.toBigInt(callerFelt).toString(16)}`, + ) as ContractAddress; + + const tokenAddress = validateAndParseAddress( + `0x${FieldElement.toBigInt(tokenAddressFelt).toString(16)}`, + ) as ContractAddress; + + const [ + amountLow, + amountHigh, + priceLow, + priceHigh, + protocolFeeLow, + protocolFeeHigh, + lastPriceLow, + lastPriceHigh, + timestampFelt, + quoteAmountLow, + quoteAmountHigh, + ] = event.data; + + const amountRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(amountLow), + high: FieldElement.toBigInt(amountHigh), + }); + const amount = formatUnits(amountRaw, constants.DECIMALS).toString(); + + const priceRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(priceLow), + high: FieldElement.toBigInt(priceHigh), + }); + const price = formatUnits(priceRaw, constants.DECIMALS); + + const protocolFeeRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(protocolFeeLow), + high: FieldElement.toBigInt(protocolFeeHigh), + }); + const protocolFee = formatUnits( + protocolFeeRaw, + constants.DECIMALS, + ).toString(); + + const lastPriceRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(lastPriceLow), + high: FieldElement.toBigInt(lastPriceHigh), + }); + const lastPrice = formatUnits(lastPriceRaw, constants.DECIMALS).toString(); + + const quoteAmountRaw = uint256.uint256ToBN({ + low: FieldElement.toBigInt(quoteAmountLow ?? amountLow), + high: FieldElement.toBigInt(quoteAmountHigh ?? amountHigh), + }); + const quoteAmount = formatUnits( + quoteAmountRaw, + constants.DECIMALS, + ).toString(); + + const timestamp = new Date( + Number(FieldElement.toBigInt(timestampFelt)) * 1000, + ); + + const data = { + transferId, + network: 'starknet-sepolia', + transactionHash, + blockNumber: Number(blockNumber), + blockHash, + blockTimestamp: new Date(Number(blockTimestamp.seconds) * 1000), + ownerAddress, + memecoinAddress: tokenAddress, + amount: Number(amount), + price, + protocolFee, + lastPrice, + quoteAmount, + timestamp, + transactionType: 'buy', + }; + + await this.liquidityAddedService.create(data); + } catch (error) { + console.log("Error LiquidityAddedIndexer", error) + + } } } diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index eb6a2b938..cff51ec02 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -776,7 +776,7 @@ pub mod LaunchpadMarketplace { pool.clone(), coin_address, sell_amount, true, false ); let mut quote_amount = quote_amount_total.clone(); - println!("quote_amount_total first: {}", quote_amount_total.clone()); + // println!("quote_amount_total first: {}", quote_amount_total.clone()); let protocol_fee_amount = quote_amount * protocol_fee_percent / BPS; let creator_fee_amount = quote_amount * creator_fee_percent / BPS; @@ -789,7 +789,7 @@ pub mod LaunchpadMarketplace { && self.is_fees_protocol_sell_enabled.read(); let mut quote_fee_amount = 0_u256; - println!("check fees"); + // println!("check fees"); // Substract fees protocol from quote amount // AUDIT @@ -802,10 +802,10 @@ pub mod LaunchpadMarketplace { // Validate against liquidity and balance constraints // AUDIT // High security check to do. - println!("check liq raised and quote amount"); + // println!("check liq raised and quote amount"); if pool.liquidity_raised < quote_amount { - println!("pool.liquidity_raised < quote_amount"); + // println!("pool.liquidity_raised < quote_amount"); quote_amount = pool.liquidity_raised; if is_fees_enabled { quote_fee_amount = quote_amount * protocol_fee_percent / BPS; @@ -813,25 +813,25 @@ pub mod LaunchpadMarketplace { } } - println!("quote_amount: {}", quote_amount.clone()); + // println!("quote_amount: {}", quote_amount.clone()); assert(pool.liquidity_raised >= quote_amount, errors::LIQUIDITY_BELOW_AMOUNT); // Process transfers let quote_token = IERC20Dispatcher { contract_address: pool.token_quote.token_address }; - println!("transfer fees: {}", quote_fee_amount.clone()); + // println!("transfer fees: {}", quote_fee_amount.clone()); if is_fees_enabled && quote_fee_amount > 0 { quote_token.transfer(self.protocol_fee_destination.read(), quote_fee_amount); } - println!("transfer quote amount: {}", quote_amount.clone()); + // println!("transfer quote amount: {}", quote_amount.clone()); let balance_contract = quote_token.balance_of(get_contract_address()); - println!("balance_contract: {}", balance_contract.clone()); + // println!("balance_contract: {}", balance_contract.clone()); // assert(balance_contract >= quote_amount, errors::BALANCE_CONTRACT_BELOW_AMOUNT); let quote_amount_paid = quote_amount.clone(); // let quote_amount_paid = quote_amount - quote_fee_amount; - println!("quote_amount_paid: {}", quote_amount_paid.clone()); + // println!("quote_amount_paid: {}", quote_amount_paid.clone()); // TODO audit // HIGH SECURITY ISSUE @@ -843,7 +843,7 @@ pub mod LaunchpadMarketplace { // let quote_amount_paid = quote_amount - quote_fee_amount; let difference_amount= quote_amount_paid - balance_contract; let amount_paid= quote_amount_paid - difference_amount; - println!("amount_paid: {}", amount_paid.clone()); + // println!("amount_paid: {}", amount_paid.clone()); quote_token.transfer(caller, amount_paid); } // if balance_contract > quote_amount { @@ -859,13 +859,13 @@ pub mod LaunchpadMarketplace { // } // Update state - println!("update share"); + // println!("update share"); share.amount_owned -= sell_amount; share.amount_sell += sell_amount; let mut updated_pool = pool.clone(); - println!("update pool"); + // println!("update pool"); updated_pool.liquidity_raised = if updated_pool.liquidity_raised >= quote_amount { updated_pool.liquidity_raised - quote_amount From 86ea099396ebc44daea5c663d12d7a9ed37b66f1 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sun, 2 Feb 2025 07:23:21 +0100 Subject: [PATCH 08/11] change block start manual atm --- apps/nestjs-indexer/src/common/constants.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/nestjs-indexer/src/common/constants.ts b/apps/nestjs-indexer/src/common/constants.ts index 4c5170f20..4334e9858 100644 --- a/apps/nestjs-indexer/src/common/constants.ts +++ b/apps/nestjs-indexer/src/common/constants.ts @@ -1,7 +1,8 @@ export default { DECIMALS: 18, - STARTING_BLOCK: 140_000, + STARTING_BLOCK: 450_000, STARTING_BLOCK_UNRUG: 615_556, + STARTING_BLOCK_MAINNET: 615_556, contracts: { mainnet: { FACTORY_ADDRESS: From d2fa8c8a17b487a50e472c408d938e0a4c15134d Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sun, 2 Feb 2025 08:21:25 +0100 Subject: [PATCH 09/11] deploy launch refacto and fix --- apps/nestjs-indexer/src/common/constants.ts | 8 +++----- onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed | 4 ++-- onchain/cairo/afk/src/lib.cairo | 6 +++--- packages/common/src/contracts.ts | 11 +++-------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/apps/nestjs-indexer/src/common/constants.ts b/apps/nestjs-indexer/src/common/constants.ts index 4334e9858..17146e006 100644 --- a/apps/nestjs-indexer/src/common/constants.ts +++ b/apps/nestjs-indexer/src/common/constants.ts @@ -11,12 +11,10 @@ export default { '0x01a46467a9246f45c8c340f1f155266a26a71c07bd55d36e8d1c7d0d438a2dbc', }, sepolia: { - // LAUNCHPAD_ADDRESS:"0x525d47343caa4c56cd28eeeff7d503f1b80872c5a7f4a9f8ac8130de3513f70", - - // LAUNCHPAD_ADDRESS:"0x6fbcf2a0df5716d83d9653985f9c9bde6fb73130f0804e18bb18ef6f6ae7ad2", // LAUNCHPAD_ADDRESS:"0x6579503122e7564117f2192d6a66ec81b51dfd551b39a4fa26046044cd39a35", - LAUNCHPAD_ADDRESS: - '0x4cefb7ab4c3fda72df52f288e141ff1dc6956a497949bf7b0031f012f3d3afc', + // LAUNCHPAD_ADDRESS: + // '0x4cefb7ab4c3fda72df52f288e141ff1dc6956a497949bf7b0031f012f3d3afc', + LAUNCHPAD_ADDRESS:"0x4a5d414ec6085ef5d3779b6e72015076b1a1ecdb6065fe1123d794ff41aea4d", NAMESERVICE_ADDRESS: '0x15dcd3c28c07846fa98d3a40d29446de21b5e6cd8d49a43773da0f237d5ea7f', ESCROW_DEPOSIT_ADDRESS: diff --git a/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed b/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed index 86c22aaaf..94718de52 100644 --- a/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed +++ b/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed @@ -1,6 +1,6 @@ afk::bip340::tests::test_20 -afk::bip340::tests::test_generate_sign_and_verify afk::social::deposit::tests::claim_incorrect_gas_amount +afk::social::deposit::tests::deposit_claim_gas_fee afk::social::deposit::tests::deposit_claim +afk::bip340::tests::test_generate_sign_and_verify afk::social::deposit::tests::deposit_with_known_starknet_recipient -afk::social::deposit::tests::deposit_claim_gas_fee diff --git a/onchain/cairo/afk/src/lib.cairo b/onchain/cairo/afk/src/lib.cairo index d73183bc8..289fa0037 100644 --- a/onchain/cairo/afk/src/lib.cairo +++ b/onchain/cairo/afk/src/lib.cairo @@ -54,9 +54,9 @@ pub mod tokens { #[cfg(test)] pub mod tests { - pub mod dn404_presets_test; - pub mod dn404_tests; - pub mod nameservice_tests; + // pub mod dn404_presets_test; + // pub mod dn404_tests; + // pub mod nameservice_tests; pub mod staking_tests; pub mod utils; pub mod vault_tests; diff --git a/packages/common/src/contracts.ts b/packages/common/src/contracts.ts index 99e50db7e..a1d125df5 100644 --- a/packages/common/src/contracts.ts +++ b/packages/common/src/contracts.ts @@ -45,19 +45,14 @@ export const NAMESPACE_ADDRESS = { export const LAUNCHPAD_ADDRESS = { [constants.StarknetChainId.SN_MAIN]: "", - // [constants.StarknetChainId.SN_SEPOLIA]: - // "0x3ed3046bf62bfcfba32e9282d64f432de3f0c383755b1aee8514c3d67109573" - // [constants.StarknetChainId.SN_SEPOLIA]: - // "0x1488623bcbd76a0d078cc4c4743630c2650800626ce20774b8f3706a42591e9" - // [constants.StarknetChainId.SN_SEPOLIA]: - // "0x58b6601ce6010306e47fbc44d5489ede3f841414f2440329e841982e0d5e911" // [constants.StarknetChainId.SN_SEPOLIA]: // "0x525d47343caa4c56cd28eeeff7d503f1b80872c5a7f4a9f8ac8130de3513f70", // [constants.StarknetChainId.SN_SEPOLIA]: // "0x6fbcf2a0df5716d83d9653985f9c9bde6fb73130f0804e18bb18ef6f6ae7ad2" +// [constants.StarknetChainId.SN_SEPOLIA]: +// "0x4cefb7ab4c3fda72df52f288e141ff1dc6956a497949bf7b0031f012f3d3afc", [constants.StarknetChainId.SN_SEPOLIA]: - "0x4cefb7ab4c3fda72df52f288e141ff1dc6956a497949bf7b0031f012f3d3afc" - // "0x6579503122e7564117f2192d6a66ec81b51dfd551b39a4fa26046044cd39a35" + "0x4a5d414ec6085ef5d3779b6e72015076b1a1ecdb6065fe1123d794ff41aea4d" }; From ced03647b061b5aaac5e19e6b7ed0dea644bb1f8 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sun, 2 Feb 2025 08:22:35 +0100 Subject: [PATCH 10/11] fmt afk --- onchain/cairo/afk/src/bip340.cairo | 21 +++-- onchain/cairo/afk/src/lib.cairo | 2 +- onchain/cairo/afk/src/pedersen.cairo | 15 ++-- onchain/cairo/afk/src/social/deposit.cairo | 6 +- onchain/cairo/afk/src/social/namespace.cairo | 2 +- onchain/cairo/afk/src/social/transfer.cairo | 90 +++++++++---------- .../afk/src/tests/nameservice_tests.cairo | 4 +- onchain/cairo/afk/src/types/keys_types.cairo | 2 +- 8 files changed, 71 insertions(+), 71 deletions(-) diff --git a/onchain/cairo/afk/src/bip340.cairo b/onchain/cairo/afk/src/bip340.cairo index 54580d09b..f328bb8f7 100644 --- a/onchain/cairo/afk/src/bip340.cairo +++ b/onchain/cairo/afk/src/bip340.cairo @@ -268,11 +268,11 @@ fn encodeSocialRequest, impl CDrop: Drop>( } /// Generates a key pair (private key, public key) for Schnorr signatures -fn generate_keypair(vrf_contract_address:ContractAddress) -> (core::felt252, core::starknet::secp256k1::Secp256k1Point) { +fn generate_keypair( + vrf_contract_address: ContractAddress +) -> (core::felt252, core::starknet::secp256k1::Secp256k1Point) { // vrf address - let vrf_provider = IVrfProviderDispatcher { - contract_address: vrf_contract_address - }; + let vrf_provider = IVrfProviderDispatcher { contract_address: vrf_contract_address }; // Generate source for randomness let caller = get_caller_address(); @@ -356,11 +356,11 @@ mod tests { use core::option::OptionTrait; use core::traits::Into; use starknet::SyscallResultTrait; + use starknet::{secp256k1::{Secp256k1Point}, secp256_trait::{Secp256Trait}}; use super::Secp256PointTrait; + use super::{IVrfProvider, IVrfProviderDispatcher}; // use super::*; use super::{verify, verify_sig, sign, generate_keypair}; - use super::{ IVrfProvider,IVrfProviderDispatcher}; - use starknet::{secp256k1::{Secp256k1Point}, secp256_trait::{Secp256Trait}}; impl U256IntoByteArray of Into { fn into(self: u256) -> ByteArray { let mut ba = Default::default(); @@ -370,8 +370,8 @@ mod tests { } } const CONTRACT_ADDRESS: felt252 = - 0x00be3edf412dd5982aa102524c0b8a0bcee584c5a627ed1db6a7c36922047257; - + 0x00be3edf412dd5982aa102524c0b8a0bcee584c5a627ed1db6a7c36922047257; + // test data adapted from: https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv #[test] @@ -594,11 +594,10 @@ mod tests { } #[test] fn test_20() { - let vrf_provider = IVrfProviderDispatcher { contract_address: CONTRACT_ADDRESS.try_into().unwrap() }; - + let (private_key, public_key) = generate_keypair(vrf_provider.contract_address); // Message to sign @@ -635,4 +634,4 @@ mod tests { assert!(is_valid); } -} \ No newline at end of file +} diff --git a/onchain/cairo/afk/src/lib.cairo b/onchain/cairo/afk/src/lib.cairo index 289fa0037..186656418 100644 --- a/onchain/cairo/afk/src/lib.cairo +++ b/onchain/cairo/afk/src/lib.cairo @@ -1,8 +1,8 @@ pub mod bip340; -pub mod pedersen; pub mod errors; pub mod math; +pub mod pedersen; pub mod sha256; pub mod social; pub mod staking; diff --git a/onchain/cairo/afk/src/pedersen.cairo b/onchain/cairo/afk/src/pedersen.cairo index ba84f619c..a87e224c8 100644 --- a/onchain/cairo/afk/src/pedersen.cairo +++ b/onchain/cairo/afk/src/pedersen.cairo @@ -1,9 +1,9 @@ use core::ec::stark_curve::GEN_X; use core::ec::stark_curve::GEN_Y; -use core::fmt::{Display, Formatter, Error}; use core::ec::{EcPoint, EcPointTrait, ec_point_unwrap, NonZeroEcPoint}; -use core::poseidon::PoseidonTrait; +use core::fmt::{Display, Formatter, Error}; use core::hash::{HashStateTrait, HashStateExTrait,}; +use core::poseidon::PoseidonTrait; pub impl EcPointDisplay of Display { fn fmt(self: @EcPoint, ref f: Formatter) -> Result<(), Error> { @@ -55,13 +55,13 @@ pub fn hash_to_curve() -> Option { #[cfg(test)] mod tests { // use super::*; - use super::{pedersen_commit, verify_commitment, hash_to_curve}; use core::ec::stark_curve::GEN_X; use core::ec::stark_curve::GEN_Y; - use core::fmt::{Display, Formatter, Error}; use core::ec::{EcPoint, EcPointTrait, ec_point_unwrap, NonZeroEcPoint}; - use core::poseidon::PoseidonTrait; + use core::fmt::{Display, Formatter, Error}; use core::hash::{HashStateTrait, HashStateExTrait,}; + use core::poseidon::PoseidonTrait; + use super::{pedersen_commit, verify_commitment, hash_to_curve}; #[test] fn test_pedersen_commit() { @@ -69,9 +69,8 @@ mod tests { let value: felt252 = 77777; let salt: felt252 = 228282189421094; let commitment = pedersen_commit(value, salt, H); - + let is_valid = verify_commitment(commitment, value, salt, H); assert(is_valid, 'The commitment is not valid'); } - -} \ No newline at end of file +} diff --git a/onchain/cairo/afk/src/social/deposit.cairo b/onchain/cairo/afk/src/social/deposit.cairo index 68dce3317..3e0bb7c55 100644 --- a/onchain/cairo/afk/src/social/deposit.cairo +++ b/onchain/cairo/afk/src/social/deposit.cairo @@ -1,7 +1,7 @@ use core::traits::Into; use starknet::{ContractAddress}; -use super::request::{SocialRequest, SocialRequestImpl, Encode}; use super::request::ConvertToBytes; +use super::request::{SocialRequest, SocialRequestImpl, Encode}; pub type DepositId = felt252; #[derive(Clone, Debug, Drop, Serde)] @@ -367,8 +367,8 @@ mod tests { use starknet::{ContractAddress, get_block_timestamp}; use super::super::request::{SocialRequest, Signature}; - use super::{DepositResult, NostrPublicKey, Claim}; use super::{DepositEscrow, IDepositEscrowDispatcher, IDepositEscrowDispatcherTrait}; + use super::{DepositResult, NostrPublicKey, Claim}; fn declare_escrow() -> @ContractClass { declare("DepositEscrow").unwrap().contract_class() @@ -546,7 +546,7 @@ mod tests { let mut spy = spy_events(); // Deposit by sender to recipient escrow.deposit(amount, erc20.contract_address, recipient_nostr_key, 0_u64); - + // Recipient user claim deposit start_cheat_caller_address(escrow.contract_address, recipient_address); escrow.claim_with_gas(request, 0_u256); diff --git a/onchain/cairo/afk/src/social/namespace.cairo b/onchain/cairo/afk/src/social/namespace.cairo index 15b8d4014..4f53c6bc3 100644 --- a/onchain/cairo/afk/src/social/namespace.cairo +++ b/onchain/cairo/afk/src/social/namespace.cairo @@ -1,8 +1,8 @@ use core::fmt::Display; use core::to_byte_array::FormatAsByteArray; use starknet::{get_caller_address, get_contract_address, get_tx_info, ContractAddress}; -use super::request::{SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature}; use super::request::ConvertToBytes; +use super::request::{SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature}; // Add this ROLE on a constants file pub const OPERATOR_ROLE: felt252 = selector!("OPERATOR_ROLE"); diff --git a/onchain/cairo/afk/src/social/transfer.cairo b/onchain/cairo/afk/src/social/transfer.cairo index a84c8532e..5a021c074 100644 --- a/onchain/cairo/afk/src/social/transfer.cairo +++ b/onchain/cairo/afk/src/social/transfer.cairo @@ -1,7 +1,7 @@ use starknet::ContractAddress; use super::profile::{NostrProfile, NostrProfileTrait}; -use super::request::Encode; use super::request::ConvertToBytes; +use super::request::Encode; #[derive(Clone, Debug, Drop, Serde)] pub struct Transfer { pub amount: u256, @@ -45,54 +45,54 @@ fn count_digits(mut num: u256) -> (u32, felt252) { impl TransferImpl of ConvertToBytes { fn convert_to_bytes(self: @Transfer) -> ByteArray { let mut ba: ByteArray = ""; - // Encode amount (u256 to felt252 conversion) - let (amount_count, amount_count_felt252) = count_digits(*self.amount); - let amount_u256 = *self.amount; - let amount_felt252: felt252 = amount_u256.try_into().unwrap(); - ba.append_word(amount_count_felt252, 1_u32); - ba.append_word(amount_felt252, amount_count); - - // Encode token - ba.append_word(*self.token, 1_u32); - - // Encode token_address + // Encode amount (u256 to felt252 conversion) + let (amount_count, amount_count_felt252) = count_digits(*self.amount); + let amount_u256 = *self.amount; + let amount_felt252: felt252 = amount_u256.try_into().unwrap(); + ba.append_word(amount_count_felt252, 1_u32); + ba.append_word(amount_felt252, amount_count); + + // Encode token + ba.append_word(*self.token, 1_u32); + + // Encode token_address // let addr:felt252 = self.token_address.into(); // ba.append_word(addr, 1_u32); let token_add = *self.token_address; let token_addr = token_add.try_into().unwrap(); - ba.append_word(token_addr, 1_u32); - // Encode joyboy (NostrProfile encoding) - let (joyboy_count, joyboy_count_felt252) = count_digits((*self.joyboy.public_key).into()); - let joyboy_u256 = *self.joyboy.public_key; - let joyboy_felt252: felt252 = joyboy_u256.try_into().unwrap(); - ba.append_word(joyboy_count_felt252, 1_u32); - ba.append_word(joyboy_felt252, joyboy_count); - - // Encode joyboy relays - let joyboy_relays = self.joyboy.relays.span(); - for relay in joyboy_relays { - ba.append(relay); - }; - - // Encode recipient (NostrProfile encoding) - let (recipient_count, recipient_count_felt252) = count_digits(*self.recipient.public_key); - let receipient_u256 = *self.recipient.public_key; - let recipient_felt252: felt252 = receipient_u256.try_into().unwrap(); - ba.append_word(recipient_count_felt252, 1_u32); - ba.append_word(recipient_felt252, recipient_count); - - // Encode recipient relays - let recipient_relays = self.recipient.relays.span(); - for relay in recipient_relays { - ba.append(relay); - }; - - // Encode recipient_address - let receipient_add = *self.recipient_address; - let receipient_addr = receipient_add.try_into().unwrap(); - ba.append_word(receipient_addr, 1_u32); - - ba + ba.append_word(token_addr, 1_u32); + // Encode joyboy (NostrProfile encoding) + let (joyboy_count, joyboy_count_felt252) = count_digits((*self.joyboy.public_key).into()); + let joyboy_u256 = *self.joyboy.public_key; + let joyboy_felt252: felt252 = joyboy_u256.try_into().unwrap(); + ba.append_word(joyboy_count_felt252, 1_u32); + ba.append_word(joyboy_felt252, joyboy_count); + + // Encode joyboy relays + let joyboy_relays = self.joyboy.relays.span(); + for relay in joyboy_relays { + ba.append(relay); + }; + + // Encode recipient (NostrProfile encoding) + let (recipient_count, recipient_count_felt252) = count_digits(*self.recipient.public_key); + let receipient_u256 = *self.recipient.public_key; + let recipient_felt252: felt252 = receipient_u256.try_into().unwrap(); + ba.append_word(recipient_count_felt252, 1_u32); + ba.append_word(recipient_felt252, recipient_count); + + // Encode recipient relays + let recipient_relays = self.recipient.relays.span(); + for relay in recipient_relays { + ba.append(relay); + }; + + // Encode recipient_address + let receipient_add = *self.recipient_address; + let receipient_addr = receipient_add.try_into().unwrap(); + ba.append_word(receipient_addr, 1_u32); + + ba } } #[cfg(test)] diff --git a/onchain/cairo/afk/src/tests/nameservice_tests.cairo b/onchain/cairo/afk/src/tests/nameservice_tests.cairo index a8bf6740d..2c006d515 100644 --- a/onchain/cairo/afk/src/tests/nameservice_tests.cairo +++ b/onchain/cairo/afk/src/tests/nameservice_tests.cairo @@ -234,7 +234,9 @@ mod nameservice_tests { stop_cheat_caller_address(nameservice_dispatcher.contract_address); let admin_balance = payment_token_dispatcher.balance_of(ADMIN()); - assert(admin_balance == 60_u256, 'Admin did not receive fees'); // 50 initial + 10 withdrawn + assert( + admin_balance == 60_u256, 'Admin did not receive fees' + ); // 50 initial + 10 withdrawn let contract_balance = payment_token_dispatcher .balance_of(nameservice_dispatcher.contract_address); diff --git a/onchain/cairo/afk/src/types/keys_types.cairo b/onchain/cairo/afk/src/types/keys_types.cairo index d36ef89e4..700531835 100644 --- a/onchain/cairo/afk/src/types/keys_types.cairo +++ b/onchain/cairo/afk/src/types/keys_types.cairo @@ -118,7 +118,7 @@ pub fn get_current_price(key: @Keys, supply: u256, amount_to_buy: u256) -> u256 pub fn get_linear_price( // key: @Keys, -key: Keys, supply: u256, // amount_to_buy: u256 + key: Keys, supply: u256, // amount_to_buy: u256 ) -> u256 { let step_increase_linear = key.token_quote.step_increase_linear.clone(); let initial_key_price = key.token_quote.initial_key_price.clone(); From f76b75277348166392f81248cdd98e7fb7e6e3d2 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Sun, 2 Feb 2025 08:23:00 +0100 Subject: [PATCH 11/11] fmt launchpad --- .../launchpad/src/launchpad/launchpad.cairo | 256 +++++++++--------- .../src/launchpad/launchpad_not_clean.cairo | 29 +- .../cairo/launchpad/src/launchpad/unrug.cairo | 4 +- .../launchpad/src/types/keys_types.cairo | 2 +- .../launchpad/src/types/launchpad_types.cairo | 15 +- 5 files changed, 148 insertions(+), 158 deletions(-) diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo index cff51ec02..63eed397b 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad.cairo @@ -1,12 +1,12 @@ use afk_launchpad::interfaces::launchpad::{ILaunchpadMarketplace}; use afk_launchpad::types::jediswap_types::{MintParams}; use afk_launchpad::types::launchpad_types::{ - MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, - TokenQuoteBuyCoin, TokenLaunch, SharesTokenUser, BondingType, Token, CreateLaunch, - SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, LiquidityCreated, - LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, - LaunchParameters, EkuboLP, CallbackData, EkuboLaunchParameters, LaunchCallback, LiquidityType, - EkuboLiquidityParameters, LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams + MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, TokenQuoteBuyCoin, + TokenLaunch, SharesTokenUser, BondingType, Token, CreateLaunch, SetJediswapNFTRouterV2, + SetJediswapV2Factory, SupportedExchanges, LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, + TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, LaunchParameters, EkuboLP, CallbackData, + EkuboLaunchParameters, LaunchCallback, LiquidityType, EkuboLiquidityParameters, + LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams // MemecoinCreated, MemecoinLaunched }; use starknet::ClassHash; @@ -64,13 +64,12 @@ pub mod LaunchpadMarketplace { contract_address_const, get_block_timestamp, get_contract_address, ClassHash }; use super::{ - StoredName, BuyToken, SellToken, CreateToken, SharesTokenUser, MINTER_ROLE, - ADMIN_ROLE, BondingType, Token, TokenLaunch, TokenQuoteBuyCoin, CreateLaunch, - SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, MintParams, - LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, - EkuboPoolParameters, LaunchParameters, EkuboLP, LiquidityType, CallbackData, - EkuboLaunchParameters, LaunchCallback, EkuboLiquidityParameters, LiquidityParameters, - EkuboUnrugLaunchParameters, AdminsFeesParams + StoredName, BuyToken, SellToken, CreateToken, SharesTokenUser, MINTER_ROLE, ADMIN_ROLE, + BondingType, Token, TokenLaunch, TokenQuoteBuyCoin, CreateLaunch, SetJediswapNFTRouterV2, + SetJediswapV2Factory, SupportedExchanges, MintParams, LiquidityCreated, LiquidityCanBeAdded, + MetadataLaunch, TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, LaunchParameters, + EkuboLP, LiquidityType, CallbackData, EkuboLaunchParameters, LaunchCallback, + EkuboLiquidityParameters, LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams // MemecoinCreated, MemecoinLaunched }; component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); @@ -535,7 +534,7 @@ pub mod LaunchpadMarketplace { contract_address_salt, is_unruggable, recipient, // Send supply to this address - caller, // Owner of the address, Ownable access + caller, // Owner of the address, Ownable access contract_address, // Factory address to set_launched and others stuff ); @@ -562,7 +561,7 @@ pub mod LaunchpadMarketplace { contract_address_salt, is_unruggable, contract_address, // Send supply to this address - caller, // Owner of the address, Ownable access + caller, // Owner of the address, Ownable access contract_address, // Factory address to set_launched and others stuff ); self @@ -595,9 +594,7 @@ pub mod LaunchpadMarketplace { // Calculates amount of coin to receive based on quote token input // Handles fees, updates pool state and user shares fn buy_coin_by_quote_amount( - ref self: ContractState, - coin_address: ContractAddress, - quote_amount: u256, + ref self: ContractState, coin_address: ContractAddress, quote_amount: u256, ) { // Input validation assert(quote_amount > 0, errors::AMOUNT_ZERO); @@ -612,9 +609,9 @@ pub mod LaunchpadMarketplace { let mut amount_protocol_fee = 0; let mut remain_quote_to_liquidity = quote_amount; let mut threshold_liquidity = pool.threshold_liquidity.clone(); - + let mut slippage_threshold: u256 = threshold_liquidity * SLIPPAGE_THRESHOLD / BPS; - + let mut threshold = threshold_liquidity - slippage_threshold; // Handle protocol fees if enabled // HIGH SECURITY ISSUE @@ -624,12 +621,13 @@ pub mod LaunchpadMarketplace { remain_quote_to_liquidity = quote_amount - amount_protocol_fee; threshold -= amount_protocol_fee; // Transfer protocol fee - let quote_token = IERC20Dispatcher { contract_address: pool.token_quote.token_address }; - quote_token.transfer_from( - caller, - self.protocol_fee_destination.read(), - amount_protocol_fee - ); + let quote_token = IERC20Dispatcher { + contract_address: pool.token_quote.token_address + }; + quote_token + .transfer_from( + caller, self.protocol_fee_destination.read(), amount_protocol_fee + ); } let new_liquidity = pool.liquidity_raised + remain_quote_to_liquidity; // assert(new_liquidity <= threshold, errors::THRESHOLD_LIQUIDITY_EXCEEDED); @@ -639,11 +637,7 @@ pub mod LaunchpadMarketplace { // High security check to do. // Verify rounding issue and approximation of the quote amount caused overflow let coin_amount = get_amount_by_type_of_coin_or_quote( - pool.clone(), - coin_address, - remain_quote_to_liquidity, - false, - true + pool.clone(), coin_address, remain_quote_to_liquidity, false, true ); // Verify sufficient supply @@ -651,11 +645,7 @@ pub mod LaunchpadMarketplace { // Transfer quote tokens to contract let quote_token = IERC20Dispatcher { contract_address: pool.token_quote.token_address }; - quote_token.transfer_from( - caller, - get_contract_address(), - remain_quote_to_liquidity - ); + quote_token.transfer_from(caller, get_contract_address(), remain_quote_to_liquidity); // Update pool state let old_price = pool.price; @@ -675,16 +665,17 @@ pub mod LaunchpadMarketplace { // Update user shares let mut share = self.shares_by_users.entry(caller).entry(coin_address).read(); if share.owner.is_zero() { - share = SharesTokenUser { - owner: caller, - token_address: coin_address, - amount_owned: coin_amount, - amount_buy: coin_amount, - amount_sell: 0, - created_at: get_block_timestamp(), - total_paid: quote_amount, - is_claimable: true, - }; + share = + SharesTokenUser { + owner: caller, + token_address: coin_address, + amount_owned: coin_amount, + amount_buy: coin_amount, + amount_sell: 0, + created_at: get_block_timestamp(), + total_paid: quote_amount, + is_claimable: true, + }; } else { share.total_paid += quote_amount; share.amount_owned += coin_amount; @@ -693,17 +684,19 @@ pub mod LaunchpadMarketplace { self.shares_by_users.entry(caller).entry(coin_address).write(share); // Check if liquidity threshold reached - // let threshold = pool.threshold_liquidity - (pool.threshold_liquidity * SLIPPAGE_THRESHOLD / BPS); + // let threshold = pool.threshold_liquidity - (pool.threshold_liquidity * + // SLIPPAGE_THRESHOLD / BPS); self.launched_coins.entry(coin_address).write(pool); if pool.liquidity_raised >= threshold { - self.emit( - LiquidityCanBeAdded { - pool: pool.token_address, - asset: pool.token_address, - quote_token_address: pool.token_quote.token_address, - } - ); + self + .emit( + LiquidityCanBeAdded { + pool: pool.token_address, + asset: pool.token_address, + quote_token_address: pool.token_quote.token_address, + } + ); // Add liquidity to DEX self._add_liquidity_ekubo(coin_address); @@ -714,18 +707,19 @@ pub mod LaunchpadMarketplace { self.launched_coins.entry(coin_address).write(pool); // Emit buy event - self.emit( - BuyToken { - caller, - token_address: coin_address, - amount: coin_amount, - price: quote_amount, - protocol_fee: amount_protocol_fee, - last_price: old_price, - timestamp: get_block_timestamp(), - quote_amount: remain_quote_to_liquidity - } - ); + self + .emit( + BuyToken { + caller, + token_address: coin_address, + amount: coin_amount, + price: quote_amount, + protocol_fee: amount_protocol_fee, + last_price: old_price, + timestamp: get_block_timestamp(), + quote_amount: remain_quote_to_liquidity + } + ); } // params @coin_address @coin_amount @@ -745,11 +739,7 @@ pub mod LaunchpadMarketplace { let caller = get_caller_address(); // Get user's share and validate amount - let mut share = self - .shares_by_users - .entry(caller) - .entry(coin_address) - .read(); + let mut share = self.shares_by_users.entry(caller).entry(coin_address).read(); // Adjust sell amount if needed let mut sell_amount = if share.amount_owned < coin_amount { @@ -764,7 +754,8 @@ pub mod LaunchpadMarketplace { let protocol_fee_percent = self.protocol_fee_percent.read(); let creator_fee_percent = self.creator_fee_percent.read(); assert( - protocol_fee_percent <= MAX_FEE_PROTOCOL && protocol_fee_percent >= MIN_FEE_PROTOCOL, + protocol_fee_percent <= MAX_FEE_PROTOCOL + && protocol_fee_percent >= MIN_FEE_PROTOCOL, errors::PROTOCOL_FEE_OUT_OF_BOUNDS ); // assert( @@ -780,19 +771,19 @@ pub mod LaunchpadMarketplace { let protocol_fee_amount = quote_amount * protocol_fee_percent / BPS; let creator_fee_amount = quote_amount * creator_fee_percent / BPS; - + // let protocol_fee_amount = sell_amount * protocol_fee_percent / BPS; // let creator_fee_amount = sell_amount * creator_fee_percent / BPS; // Handle protocol fees if enabled - let is_fees_enabled = self.is_fees_protocol_enabled.read() + let is_fees_enabled = self.is_fees_protocol_enabled.read() && self.is_fees_protocol_sell_enabled.read(); let mut quote_fee_amount = 0_u256; // println!("check fees"); // Substract fees protocol from quote amount - // AUDIT + // AUDIT // High security check to do: rounding, approximation, balance of contract if is_fees_enabled { quote_fee_amount = quote_amount * protocol_fee_percent / BPS; @@ -841,8 +832,8 @@ pub mod LaunchpadMarketplace { quote_token.transfer(caller, quote_amount_paid); } else { // let quote_amount_paid = quote_amount - quote_fee_amount; - let difference_amount= quote_amount_paid - balance_contract; - let amount_paid= quote_amount_paid - difference_amount; + let difference_amount = quote_amount_paid - balance_contract; + let amount_paid = quote_amount_paid - difference_amount; // println!("amount_paid: {}", amount_paid.clone()); quote_token.transfer(caller, amount_paid); } @@ -860,27 +851,25 @@ pub mod LaunchpadMarketplace { // Update state // println!("update share"); - + share.amount_owned -= sell_amount; share.amount_sell += sell_amount; let mut updated_pool = pool.clone(); // println!("update pool"); - updated_pool.liquidity_raised = if updated_pool.liquidity_raised >= quote_amount { - updated_pool.liquidity_raised - quote_amount - } else { - 0_u256 - }; + updated_pool + .liquidity_raised = + if updated_pool.liquidity_raised >= quote_amount { + updated_pool.liquidity_raised - quote_amount + } else { + 0_u256 + }; updated_pool.total_token_holded -= sell_amount; updated_pool.available_supply += sell_amount; // Save updated state - self - .shares_by_users - .entry(caller) - .entry(coin_address) - .write(share); + self.shares_by_users.entry(caller).entry(coin_address).write(share); self.launched_coins.entry(coin_address).write(updated_pool.clone()); @@ -1134,14 +1123,15 @@ pub mod LaunchpadMarketplace { // Handle paid launch if enabled if self.is_paid_launch_enable.read() { let admins_fees_params = self.admins_fees_params.read(); - let erc20 = IERC20Dispatcher { - contract_address: admins_fees_params.token_address_to_paid_launch + let erc20 = IERC20Dispatcher { + contract_address: admins_fees_params.token_address_to_paid_launch }; - erc20.transfer_from( - caller, - self.protocol_fee_destination.read(), - admins_fees_params.amount_to_paid_launch - ); + erc20 + .transfer_from( + caller, + self.protocol_fee_destination.read(), + admins_fees_params.amount_to_paid_launch + ); } // Set up bonding curve type @@ -1189,11 +1179,8 @@ pub mod LaunchpadMarketplace { if balance_contract < total_supply { let allowance = memecoin.allowance(caller, get_contract_address()); assert(allowance >= total_supply, errors::INSUFFICIENT_ALLOWANCE); - memecoin.transfer_from( - caller, - get_contract_address(), - total_supply - balance_contract - ); + memecoin + .transfer_from(caller, get_contract_address(), total_supply - balance_contract); } // Store launch data @@ -1204,30 +1191,32 @@ pub mod LaunchpadMarketplace { self.array_launched_coins.entry(total_launch).write(launch_token_pump); // Emit launch event - self.emit( - CreateLaunch { - caller: get_caller_address(), - token_address: coin_address, - amount: 0, - price: 1_u256, - total_supply, - slope: 1_u256, - threshold_liquidity: self.threshold_liquidity.read(), - quote_token_address, - is_unruggable, - bonding_type: bond_type - } - ); + self + .emit( + CreateLaunch { + caller: get_caller_address(), + token_address: coin_address, + amount: 0, + price: 1_u256, + total_supply, + slope: 1_u256, + threshold_liquidity: self.threshold_liquidity.read(), + quote_token_address, + is_unruggable, + bonding_type: bond_type + } + ); } // Call the Unrug V2 to deposit Liquidity and locked it fn _add_liquidity_ekubo( - ref self: ContractState, - coin_address: ContractAddress, + ref self: ContractState, coin_address: ContractAddress, ) -> (u64, EkuboLP) { // Get unrug liquidity contract let unrug_liquidity_address = self.unrug_liquidity_address.read(); - let unrug_liquidity = IUnrugLiquidityDispatcher { contract_address: unrug_liquidity_address }; + let unrug_liquidity = IUnrugLiquidityDispatcher { + contract_address: unrug_liquidity_address + }; // Get launch info and validate let launch = self.launched_coins.read(coin_address); @@ -1245,8 +1234,7 @@ pub mod LaunchpadMarketplace { fee: 0xc49ba5e353f7d00000000000000000, tick_spacing: tick_spacing, starting_price: calculate_starting_price_launch( - launch.initial_pool_supply.clone(), - launch.liquidity_raised.clone() + launch.initial_pool_supply.clone(), launch.liquidity_raised.clone() ), bound: bound_spacing, }; @@ -1256,9 +1244,12 @@ pub mod LaunchpadMarketplace { let mut lp_quote_supply = launch.liquidity_raised.clone(); // Handle edge case where contract balance is insufficient - let quote_token = IERC20Dispatcher { contract_address: launch.token_quote.token_address.clone() }; + let quote_token = IERC20Dispatcher { + contract_address: launch.token_quote.token_address.clone() + }; let contract_quote_balance = quote_token.balance_of(get_contract_address()); - if contract_quote_balance < lp_quote_supply && contract_quote_balance < launch.threshold_liquidity { + if contract_quote_balance < lp_quote_supply + && contract_quote_balance < launch.threshold_liquidity { lp_quote_supply = contract_quote_balance.clone(); } @@ -1287,17 +1278,18 @@ pub mod LaunchpadMarketplace { self.launched_coins.entry(coin_address).write(launch_to_update.clone()); // Emit event - self.emit( - LiquidityCreated { - id: id.try_into().unwrap(), - pool: coin_address, - asset: coin_address, - quote_token_address: launch.token_quote.token_address, - owner: launch.owner, - exchange: SupportedExchanges::Ekubo, - is_unruggable: false - } - ); + self + .emit( + LiquidityCreated { + id: id.try_into().unwrap(), + pool: coin_address, + asset: coin_address, + quote_token_address: launch.token_quote.token_address, + owner: launch.owner, + exchange: SupportedExchanges::Ekubo, + is_unruggable: false + } + ); (id, position) } diff --git a/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo b/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo index 68ae3a6d9..72f1ef30f 100644 --- a/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo +++ b/onchain/cairo/launchpad/src/launchpad/launchpad_not_clean.cairo @@ -1,12 +1,12 @@ use afk_launchpad::interfaces::launchpad::{ILaunchpadMarketplace}; use afk_launchpad::types::jediswap_types::{MintParams}; use afk_launchpad::types::launchpad_types::{ - MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, - TokenQuoteBuyCoin, TokenLaunch, SharesTokenUser, BondingType, Token, CreateLaunch, - SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, LiquidityCreated, - LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, - LaunchParameters, EkuboLP, CallbackData, EkuboLaunchParameters, LaunchCallback, LiquidityType, - EkuboLiquidityParameters, LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams + MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, TokenQuoteBuyCoin, + TokenLaunch, SharesTokenUser, BondingType, Token, CreateLaunch, SetJediswapNFTRouterV2, + SetJediswapV2Factory, SupportedExchanges, LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, + TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, LaunchParameters, EkuboLP, CallbackData, + EkuboLaunchParameters, LaunchCallback, LiquidityType, EkuboLiquidityParameters, + LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams // MemecoinCreated, MemecoinLaunched }; use starknet::ClassHash; @@ -64,13 +64,12 @@ pub mod LaunchpadMarketplace { contract_address_const, get_block_timestamp, get_contract_address, ClassHash }; use super::{ - StoredName, BuyToken, SellToken, CreateToken, SharesTokenUser, MINTER_ROLE, - ADMIN_ROLE, BondingType, Token, TokenLaunch, TokenQuoteBuyCoin, CreateLaunch, - SetJediswapNFTRouterV2, SetJediswapV2Factory, SupportedExchanges, MintParams, - LiquidityCreated, LiquidityCanBeAdded, MetadataLaunch, TokenClaimed, MetadataCoinAdded, - EkuboPoolParameters, LaunchParameters, EkuboLP, LiquidityType, CallbackData, - EkuboLaunchParameters, LaunchCallback, EkuboLiquidityParameters, LiquidityParameters, - EkuboUnrugLaunchParameters, AdminsFeesParams + StoredName, BuyToken, SellToken, CreateToken, SharesTokenUser, MINTER_ROLE, ADMIN_ROLE, + BondingType, Token, TokenLaunch, TokenQuoteBuyCoin, CreateLaunch, SetJediswapNFTRouterV2, + SetJediswapV2Factory, SupportedExchanges, MintParams, LiquidityCreated, LiquidityCanBeAdded, + MetadataLaunch, TokenClaimed, MetadataCoinAdded, EkuboPoolParameters, LaunchParameters, + EkuboLP, LiquidityType, CallbackData, EkuboLaunchParameters, LaunchCallback, + EkuboLiquidityParameters, LiquidityParameters, EkuboUnrugLaunchParameters, AdminsFeesParams // MemecoinCreated, MemecoinLaunched }; component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); @@ -536,7 +535,7 @@ pub mod LaunchpadMarketplace { contract_address_salt, is_unruggable, recipient, // Send supply to this address - caller, // Owner of the address, Ownable access + caller, // Owner of the address, Ownable access contract_address, // Factory address to set_launched and others stuff ); @@ -563,7 +562,7 @@ pub mod LaunchpadMarketplace { contract_address_salt, is_unruggable, contract_address, // Send supply to this address - caller, // Owner of the address, Ownable access + caller, // Owner of the address, Ownable access contract_address, // Factory address to set_launched and others stuff ); self diff --git a/onchain/cairo/launchpad/src/launchpad/unrug.cairo b/onchain/cairo/launchpad/src/launchpad/unrug.cairo index 5bd88ac0b..f9a94fdac 100644 --- a/onchain/cairo/launchpad/src/launchpad/unrug.cairo +++ b/onchain/cairo/launchpad/src/launchpad/unrug.cairo @@ -2,8 +2,8 @@ pub mod UnrugLiquidity { use afk_launchpad::interfaces::jediswap::{ // V1 - // IJediswapRouter, IJediswapRouterDispatcher, IJediswapFactoryDispatcher, // IJediswapFactoryV1Dispatcher, // IJediswapFactoryV1DispatcherTrait, - // V2 Jediswap + // IJediswapRouter, IJediswapRouterDispatcher, IJediswapFactoryDispatcher, // + // IJediswapFactoryV1Dispatcher, // IJediswapFactoryV1DispatcherTrait, V2 Jediswap IJediswapFactoryV2, IJediswapFactoryV2Dispatcher, IJediswapFactoryV2DispatcherTrait, // Router // IJediswapRouterV2, diff --git a/onchain/cairo/launchpad/src/types/keys_types.cairo b/onchain/cairo/launchpad/src/types/keys_types.cairo index d36ef89e4..700531835 100644 --- a/onchain/cairo/launchpad/src/types/keys_types.cairo +++ b/onchain/cairo/launchpad/src/types/keys_types.cairo @@ -118,7 +118,7 @@ pub fn get_current_price(key: @Keys, supply: u256, amount_to_buy: u256) -> u256 pub fn get_linear_price( // key: @Keys, -key: Keys, supply: u256, // amount_to_buy: u256 + key: Keys, supply: u256, // amount_to_buy: u256 ) -> u256 { let step_increase_linear = key.token_quote.step_increase_linear.clone(); let initial_key_price = key.token_quote.initial_key_price.clone(); diff --git a/onchain/cairo/launchpad/src/types/launchpad_types.cairo b/onchain/cairo/launchpad/src/types/launchpad_types.cairo index b83e2a78a..44765874c 100644 --- a/onchain/cairo/launchpad/src/types/launchpad_types.cairo +++ b/onchain/cairo/launchpad/src/types/launchpad_types.cairo @@ -323,7 +323,6 @@ pub struct MemecoinCreated { } - #[derive(Drop, starknet::Event)] pub struct SetJediswapV2Factory { pub address_jediswap_factory_v2: ContractAddress, @@ -391,7 +390,7 @@ pub struct JediswapLiquidityParameters { pub quote_amount: u256, } -// +// #[derive(Drop, starknet::Event)] pub struct MemecoinLaunched { @@ -410,13 +409,13 @@ pub struct LaunchUpdated { #[derive(Serde, Copy, // Clone, - Drop, starknet::Store, // PartialEq - )] - pub enum TokenType { - ERC20, - ERC404, + Drop, starknet::Store, // PartialEq +)] +pub enum TokenType { + ERC20, + ERC404, } - + #[derive(Drop, Serde, Copy, starknet::Store)] pub struct TokenLaunchFair { // pub struct Keys {