diff --git a/contracts/src/amm/amm.cairo b/contracts/src/amm/amm.cairo index e747e8d5..85645b2e 100644 --- a/contracts/src/amm/amm.cairo +++ b/contracts/src/amm/amm.cairo @@ -9,13 +9,15 @@ struct AMM { #[derive(Drop, Copy, Serde)] enum AMMV2 { JediSwap, + Ekubo // Unsupported } #[generate_trait] impl AMMImpl of AMMTrait { fn to_string(self: AMMV2) -> felt252 { match self { - AMMV2::JediSwap => 'JediSwap' + AMMV2::JediSwap => 'JediSwap', + AMMV2::Ekubo => 'Ekubo' } } } diff --git a/contracts/src/tests/test_factory.cairo b/contracts/src/tests/test_factory.cairo index da1c90ad..edff19cc 100644 --- a/contracts/src/tests/test_factory.cairo +++ b/contracts/src/tests/test_factory.cairo @@ -97,11 +97,18 @@ fn test_create_memecoin() { assert(memecoin.name() == NAME(), 'wrong memecoin name'); assert(memecoin.symbol() == SYMBOL(), 'wrong memecoin symbol'); // initial supply - initial holder balance + let holders_sum = *INITIAL_HOLDERS_AMOUNTS()[0] + *INITIAL_HOLDERS_AMOUNTS()[1]; assert( - memecoin.balanceOf(memecoin_address) == DEFAULT_INITIAL_SUPPLY() - 100, + memecoin.balanceOf(memecoin_address) == DEFAULT_INITIAL_SUPPLY() - holders_sum, 'wrong initial supply' ); - assert(memecoin.balanceOf(*INITIAL_HOLDERS()[0]) == 50, 'wrong initial_holder_1 balance'); - assert(memecoin.balanceOf(*INITIAL_HOLDERS()[1]) == 50, 'wrong initial_holder_2 balance'); + assert( + memecoin.balanceOf(*INITIAL_HOLDERS()[0]) == *INITIAL_HOLDERS_AMOUNTS()[0], + 'wrong initial_holder_1 balance' + ); + assert( + memecoin.balanceOf(*INITIAL_HOLDERS()[1]) == *INITIAL_HOLDERS_AMOUNTS()[1], + 'wrong initial_holder_2 balance' + ); } diff --git a/contracts/src/tests/test_memecoin_erc20.cairo b/contracts/src/tests/test_memecoin_erc20.cairo index 0a543fe1..9b7abcd4 100644 --- a/contracts/src/tests/test_memecoin_erc20.cairo +++ b/contracts/src/tests/test_memecoin_erc20.cairo @@ -7,174 +7,44 @@ use snforge_std::{ }; use starknet::{ContractAddress, contract_address_const}; use unruggable::amm::amm::{AMM, AMMV2}; -use unruggable::tests::utils::{TxInfoMockTrait}; +use unruggable::tests::utils::{ + OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, RECIPIENT, SPENDER, deploy_locker, INITIAL_HOLDERS, + INITIAL_HOLDERS_AMOUNTS, TRANSFER_LIMIT_DELAY, DefaultTxInfoMock, deploy_standalone_memecoin +}; use unruggable::tokens::interface::{ IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; -// -// Constants -// - -fn RECIPIENT() -> ContractAddress { - return contract_address_const::<'RECIPIENT'>(); -} - -fn SPENDER() -> ContractAddress { - return contract_address_const::<'RECIPIENT'>(); -} - -const ETH_UNIT_DECIMALS: u256 = 1000000000000000000; - -// -// Setup -// - -fn deploy_contract( - owner: ContractAddress, - name: felt252, - symbol: felt252, - initial_supply: u256, - initial_holders: Span, - initial_holders_amounts: Span, -) -> Result { - let contract = declare('UnruggableMemecoin'); - let mut constructor_calldata = array![ - owner.into(), - 'locker', - 1000.into(), - name, - symbol, - initial_supply.low.into(), - initial_supply.high.into() - ]; - - Serde::serialize(@initial_holders.into(), ref constructor_calldata); - Serde::serialize(@initial_holders_amounts.into(), ref constructor_calldata); - contract.deploy(@constructor_calldata) -} - -fn instantiate_params() -> ( - ContractAddress, - felt252, - felt252, - u256, - ContractAddress, - ContractAddress, - Span, - Span, -) { - let owner = contract_address_const::<42>(); - let name = 'UnruggableMemecoin'; - let symbol = 'UM'; - let initial_supply = 1000; - let initial_holder_1 = contract_address_const::<44>(); - let initial_holder_2 = contract_address_const::<45>(); - let initial_holders = array![initial_holder_1, initial_holder_2].span(); - let initial_holders_amounts = array![50, 50].span(); - ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) -} mod erc20_metadata { use core::debug::PrintTrait; use openzeppelin::token::erc20::interface::IERC20; use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; use starknet::{ContractAddress, contract_address_const}; - use super::{deploy_contract, instantiate_params}; + use super::{ + deploy_standalone_memecoin, OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, RECIPIENT, SPENDER, + deploy_locker, TRANSFER_LIMIT_DELAY + }; use unruggable::tokens::interface::{ IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; #[test] fn test_name() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check name. Should be equal to 'UnruggableMemecoin'. - let name = memecoin.name(); - assert(name == 'UnruggableMemecoin', 'Invalid name'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + assert(memecoin.name() == NAME(), 'Invalid name'); } #[test] fn test_decimals() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check decimals. Should be equal to 18. - let decimals = memecoin.decimals(); - assert(decimals == 18, 'Invalid decimals'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + assert(memecoin.decimals() == 18, 'Invalid decimals'); } #[test] fn test_symbol() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check symbol. Should be equal to 'UM'. - let symbol = memecoin.symbol(); - assert(symbol == symbol, 'Invalid symbol'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + assert(memecoin.symbol() == SYMBOL(), 'Invalid symbol'); } } @@ -182,16 +52,15 @@ mod erc20_entrypoints { use core::array::SpanTrait; use core::debug::PrintTrait; use core::traits::Into; - use openzeppelin::token::erc20::interface::IERC20; use snforge_std::{ declare, ContractClassTrait, start_prank, stop_prank, start_warp, CheatTarget, TxInfoMock }; use starknet::{ContractAddress, contract_address_const}; - use super::{deploy_contract, instantiate_params}; - use unruggable::tests::utils::DeployerHelper::{ - deploy_contracts, deploy_unruggable_memecoin_contract, deploy_memecoin_factory, create_eth + use super::{ + deploy_standalone_memecoin, OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, RECIPIENT, SPENDER, + deploy_locker, TRANSFER_LIMIT_DELAY, INITIAL_HOLDERS, DefaultTxInfoMock, + INITIAL_HOLDERS_AMOUNTS }; - use unruggable::tests::utils::{DefaultTxInfoMock}; use unruggable::tokens::interface::{ IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; @@ -200,316 +69,142 @@ mod erc20_entrypoints { #[test] fn test_total_supply() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check total supply. Should be equal to initial supply. - let total_supply = memecoin.total_supply(); - assert(total_supply == initial_supply, 'Invalid total supply'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + assert(memecoin.total_supply() == DEFAULT_INITIAL_SUPPLY(), 'Invalid total supply'); } #[test] fn test_balance_of() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check initial contract balance. Should be equal to 900. - let balance = memecoin.balance_of(contract_address); - assert(balance == 900, 'Invalid balance'); - // Check initial holder 1 balance. Should be equal to 50. - let balance = memecoin.balance_of(initial_holder_1); - assert(balance == 50, 'Invalid balance'); - // Check initial holder 2 balance. Should be equal to 50. - let balance = memecoin.balance_of(initial_holder_1); - assert(balance == 50, 'Invalid balance'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + let holders_sum = *INITIAL_HOLDERS_AMOUNTS()[0] + *INITIAL_HOLDERS_AMOUNTS()[1]; + + // Check initial contract balance and initial holders balances. + assert( + memecoin.balance_of(memecoin_address) == DEFAULT_INITIAL_SUPPLY() - holders_sum, + 'Invalid balance memecoin' + ); + assert( + memecoin.balance_of(*INITIAL_HOLDERS()[0]) == *INITIAL_HOLDERS_AMOUNTS()[0], + 'Invalid balance holder1' + ); + assert( + memecoin.balance_of(*INITIAL_HOLDERS()[1]) == *INITIAL_HOLDERS_AMOUNTS()[1], + 'Invalid balance holder2' + ); } #[test] fn test_approve_allowance() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let spender = super::SPENDER(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); // Check initial allowance. Should be equal to 0. - let allowance = memecoin.allowance(owner, spender); + let allowance = memecoin.allowance(OWNER(), SPENDER()); assert(allowance == 0, 'Invalid allowance before'); // Approve initial supply tokens. - start_prank(CheatTarget::One(memecoin.contract_address), owner); - memecoin.approve(spender, initial_supply); + start_prank(CheatTarget::One(memecoin.contract_address), OWNER()); + memecoin.approve(SPENDER(), DEFAULT_INITIAL_SUPPLY()); // Check allowance. Should be equal to initial supply. - let allowance = memecoin.allowance(owner, spender); - assert(allowance == initial_supply, 'Invalid allowance after'); + let allowance = memecoin.allowance(OWNER(), SPENDER()); + assert(allowance == DEFAULT_INITIAL_SUPPLY(), 'Invalid allowance after'); } #[test] fn test_transfer() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let recipient = super::RECIPIENT(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); // Transfer 20 tokens to recipient. - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - memecoin.transfer(recipient, 20); + let pre_sender_balance = memecoin.balance_of(*INITIAL_HOLDERS()[0]); + start_prank(CheatTarget::One(memecoin.contract_address), *INITIAL_HOLDERS()[0]); + memecoin.transfer(RECIPIENT(), 20); // Check balance. Should be equal to initial balance - 20. - let initial_holder_1_balance = memecoin.balance_of(initial_holder_1); - assert(initial_holder_1_balance == 50 - 20, 'Invalid balance holder 1'); + let post_sender_balance = memecoin.balance_of(*INITIAL_HOLDERS()[0]); + assert(post_sender_balance == pre_sender_balance - 20, 'Invalid sender balance update'); // Check recipient balance. Should be equal to 20. - let recipient_balance = memecoin.balance_of(recipient); + let recipient_balance = memecoin.balance_of(RECIPIENT()); assert(recipient_balance == 20, 'Invalid balance recipient'); } #[test] fn test_transfer_from() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let spender = super::SPENDER(); - let recipient = super::RECIPIENT(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check initial balance. Should be equal to 50. - let balance = memecoin.balance_of(initial_holder_1); - assert(balance == 50, 'Invalid balance'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + let pre_sender_balance = memecoin.balance_of(*INITIAL_HOLDERS()[0]); // Approve initial supply tokens. - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - memecoin.approve(spender, initial_supply); - - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); + start_prank(CheatTarget::One(memecoin.contract_address), *INITIAL_HOLDERS()[0]); + memecoin.approve(SPENDER(), DEFAULT_INITIAL_SUPPLY()); // Transfer 20 tokens to recipient. - start_prank(CheatTarget::One(memecoin.contract_address), spender); - memecoin.transfer_from(initial_holder_1, recipient, 20); + start_prank(CheatTarget::One(memecoin.contract_address), SPENDER()); + memecoin.transfer_from(*INITIAL_HOLDERS()[0], RECIPIENT(), 20); // Check balance. Should be equal to initial balance - 20. - let initial_holder_1_balance = memecoin.balance_of(initial_holder_1); - assert(initial_holder_1_balance == 50 - 20, 'Invalid balance holder 1'); + let post_sender_balance = memecoin.balance_of(*INITIAL_HOLDERS()[0]); + assert(post_sender_balance == pre_sender_balance - 20, 'Invalid sender balance update'); // Check recipient balance. Should be equal to 20. - let recipient_balance = memecoin.balance_of(recipient); + let recipient_balance = memecoin.balanceOf(RECIPIENT()); assert(recipient_balance == 20, 'Invalid balance recipient'); // Check allowance. Should be equal to initial supply - transfered amount. - let allowance = memecoin.allowance(initial_holder_1, spender); - assert(allowance == (initial_supply - 20), 'Invalid allowance'); + let allowance = memecoin.allowance(*INITIAL_HOLDERS()[0], SPENDER()); + assert(allowance == (DEFAULT_INITIAL_SUPPLY() - 20), 'Invalid allowance'); } // Test ERC20 Camel entrypoints #[test] fn test_totalSupply() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check total supply. Should be equal to initial supply. - let total_supply = memecoin.totalSupply(); - assert(total_supply == initial_supply, 'Invalid total supply'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + assert(memecoin.totalSupply() == DEFAULT_INITIAL_SUPPLY(), 'Invalid total supply'); } #[test] fn test_balanceOf() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check initial contract balance. Should be equal to 900. - let balance = memecoin.balanceOf(contract_address); - assert(balance == 900, 'Invalid balance'); - // Check initial holder 1 balance. Should be equal to 50. - let balance = memecoin.balanceOf(initial_holder_1); - assert(balance == 50, 'Invalid balance'); - // Check initial holder 2 balance. Should be equal to 50. - let balance = memecoin.balanceOf(initial_holder_1); - assert(balance == 50, 'Invalid balance'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + let holders_sum = *INITIAL_HOLDERS_AMOUNTS()[0] + *INITIAL_HOLDERS_AMOUNTS()[1]; + + // Check initial contract balance and initial holders balances. + assert( + memecoin.balanceOf(memecoin_address) == DEFAULT_INITIAL_SUPPLY() - holders_sum, + 'Invalid balance memecoin' + ); + assert( + memecoin.balance_of(*INITIAL_HOLDERS()[0]) == *INITIAL_HOLDERS_AMOUNTS()[0], + 'Invalid balance holder1' + ); + assert( + memecoin.balance_of(*INITIAL_HOLDERS()[1]) == *INITIAL_HOLDERS_AMOUNTS()[1], + 'Invalid balance holder2' + ); } #[test] fn test_transferFrom() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let spender = super::SPENDER(); - let recipient = super::RECIPIENT(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // Check initial balance. Should be equal to 50. - let balance = memecoin.balance_of(initial_holder_1); - assert(balance == 50, 'Invalid balance'); + let (memecoin, memecoin_address) = deploy_standalone_memecoin(); + let pre_sender_balance = memecoin.balance_of(*INITIAL_HOLDERS()[0]); // Approve initial supply tokens. - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - memecoin.approve(spender, initial_supply); - - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); + start_prank(CheatTarget::One(memecoin.contract_address), *INITIAL_HOLDERS()[0]); + memecoin.approve(SPENDER(), DEFAULT_INITIAL_SUPPLY()); // Transfer 20 tokens to recipient. - start_prank(CheatTarget::One(memecoin.contract_address), spender); - memecoin.transferFrom(initial_holder_1, recipient, 20); + start_prank(CheatTarget::One(memecoin.contract_address), SPENDER()); + memecoin.transferFrom(*INITIAL_HOLDERS()[0], RECIPIENT(), 20); // Check balance. Should be equal to initial balance - 20. - let initial_holder_1_balance = memecoin.balance_of(initial_holder_1); - assert(initial_holder_1_balance == 50 - 20, 'Invalid balance holder 1'); + let post_sender_balance = memecoin.balance_of(*INITIAL_HOLDERS()[0]); + assert(post_sender_balance == pre_sender_balance - 20, 'Invalid sender balance update'); // Check recipient balance. Should be equal to 20. - let recipient_balance = memecoin.balance_of(recipient); + let recipient_balance = memecoin.balanceOf(RECIPIENT()); assert(recipient_balance == 20, 'Invalid balance recipient'); // Check allowance. Should be equal to initial supply - transfered amount. - let allowance = memecoin.allowance(initial_holder_1, spender); - assert(allowance == (initial_supply - 20), 'Invalid allowance'); + let allowance = memecoin.allowance(*INITIAL_HOLDERS()[0], SPENDER()); + assert(allowance == (DEFAULT_INITIAL_SUPPLY() - 20), 'Invalid allowance'); } } diff --git a/contracts/src/tests/test_unruggable_memecoin.cairo b/contracts/src/tests/test_unruggable_memecoin.cairo index e90e3bad..3d667d5e 100644 --- a/contracts/src/tests/test_unruggable_memecoin.cairo +++ b/contracts/src/tests/test_unruggable_memecoin.cairo @@ -7,86 +7,156 @@ use snforge_std::{ }; use starknet::{ContractAddress, contract_address_const}; use unruggable::amm::amm::{AMM, AMMV2, AMMTrait}; -use unruggable::tests::utils::DefaultTxInfoMock; +use unruggable::tests::utils::{ + OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, RECIPIENT, SPENDER, deploy_locker, INITIAL_HOLDERS, + INITIAL_HOLDERS_AMOUNTS, TRANSFER_LIMIT_DELAY, DefaultTxInfoMock, + deploy_memecoin_through_factory +}; use unruggable::tokens::interface::{ IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; +mod test_constructor { + use UnruggableMemecoin::{ + pre_launch_holders_countContractMemberStateTrait, + transfer_limit_delayContractMemberStateTrait, team_allocationContractMemberStateTrait, + IUnruggableAdditional, IUnruggableMemecoinCamel, IUnruggableMemecoinSnake + }; + use core::debug::PrintTrait; + use core::traits::TryInto; + use openzeppelin::token::erc20::interface::IERC20; + use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; + use starknet::{ContractAddress, contract_address_const}; + use unruggable::tests::utils::{ + deploy_amm_factory_and_router, deploy_meme_factory_with_owner, deploy_locker, + deploy_eth_with_owner, OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, INITIAL_HOLDERS, + INITIAL_HOLDER_1, INITIAL_HOLDER_2, INITIAL_HOLDERS_AMOUNTS, SALT, DefaultTxInfoMock, + deploy_memecoin_through_factory, ETH_ADDRESS, deploy_memecoin_through_factory_with_owner, + JEDI_ROUTER_ADDRESS, MEMEFACTORY_ADDRESS, ALICE, BOB, TRANSFER_LIMIT_DELAY, pow_256, + LOCKER_ADDRESS, JEDI_FACTORY_ADDRESS + }; + use unruggable::tokens::UnruggableMemecoin; + use unruggable::tokens::interface::{ + IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait + }; -// -// Constants -// -fn RECIPIENT() -> ContractAddress { - return contract_address_const::<'RECIPIENT'>(); -} -fn SPENDER() -> ContractAddress { - return contract_address_const::<'RECIPIENT'>(); -} + #[test] + fn test_constructor_happy_path() { + let mut memecoin = UnruggableMemecoin::contract_state_for_testing(); + + // Deployer must be the meme factory + start_prank(CheatTarget::One(snforge_std::test_address()), MEMEFACTORY_ADDRESS()); + UnruggableMemecoin::constructor( + ref memecoin, + OWNER(), + LOCKER_ADDRESS(), + TRANSFER_LIMIT_DELAY, + NAME(), + SYMBOL(), + DEFAULT_INITIAL_SUPPLY(), + INITIAL_HOLDERS(), + INITIAL_HOLDERS_AMOUNTS() + ); -const ETH_UNIT_DECIMALS: u256 = 1000000000000000000; - -// -// Setup -// - -fn deploy_contract( - owner: ContractAddress, - name: felt252, - symbol: felt252, - initial_supply: u256, - initial_holders: Span, - initial_holders_amounts: Span, -) -> Result { - let contract = declare('UnruggableMemecoin'); - let mut constructor_calldata = array![ - owner.into(), - 'locker', - 1000.into(), - name, - symbol, - initial_supply.low.into(), - initial_supply.high.into() - ]; - - Serde::serialize(@initial_holders.into(), ref constructor_calldata); - Serde::serialize(@initial_holders_amounts.into(), ref constructor_calldata); - contract.deploy(@constructor_calldata) -} + // External entrypoints + assert(memecoin.locker_address() == LOCKER_ADDRESS(), 'wrong locker'); + assert( + memecoin.memecoin_factory_address() == MEMEFACTORY_ADDRESS(), 'wrong factory address' + ); + + // Check internals that must be set upon deployment + assert( + memecoin.transfer_limit_delay.read() == TRANSFER_LIMIT_DELAY, + 'wrong transfer limit delay' + ); + assert( + memecoin.team_allocation.read() == 2_100_000 * pow_256(10, 18), 'wrong team allocation' + ); // 10% of supply + } -fn instantiate_params() -> ( - ContractAddress, - felt252, - felt252, - u256, - ContractAddress, - ContractAddress, - Span, - Span, -) { - let owner = contract_address_const::<42>(); - let name = 'UnruggableMemecoin'; - let symbol = 'UM'; - let initial_supply = 1000; - let initial_holder_1 = contract_address_const::<44>(); - let initial_holder_2 = contract_address_const::<45>(); - let initial_holders = array![initial_holder_1, initial_holder_2].span(); - let initial_holders_amounts = array![50, 50].span(); - ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) + #[test] + #[should_panic(expected: ('Unruggable: arrays len dif',))] + fn test_constructor_initial_holders_arrays_len_mismatch() { + let initial_holders: Array = array![ + INITIAL_HOLDER_1(), + INITIAL_HOLDER_2(), + contract_address_const::<'holder 3'>(), + contract_address_const::<'holder 4'>() + ]; + let initial_holders_amounts: Array = array![50, 40, 10]; + let mut state = UnruggableMemecoin::contract_state_for_testing(); + UnruggableMemecoin::constructor( + ref state, + OWNER(), + LOCKER_ADDRESS(), + TRANSFER_LIMIT_DELAY, + NAME(), + SYMBOL(), + DEFAULT_INITIAL_SUPPLY(), + initial_holders.span(), + initial_holders_amounts.span() + ); + } + + #[test] + #[should_panic(expected: ('Unruggable: max holders reached',))] + fn test_constructor_max_holders_reached() { + // 11 holders > 10 holders max + let initial_holders = array![ + INITIAL_HOLDER_1(), + INITIAL_HOLDER_2(), + contract_address_const::<52>(), + contract_address_const::<53>(), + contract_address_const::<54>(), + contract_address_const::<55>(), + contract_address_const::<56>(), + contract_address_const::<57>(), + contract_address_const::<58>(), + contract_address_const::<59>(), + contract_address_const::<60>(), + ]; + let initial_holders_amounts: Array = array![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; + let mut state = UnruggableMemecoin::contract_state_for_testing(); + UnruggableMemecoin::constructor( + ref state, + OWNER(), + LOCKER_ADDRESS(), + TRANSFER_LIMIT_DELAY, + NAME(), + SYMBOL(), + DEFAULT_INITIAL_SUPPLY(), + initial_holders.span(), + initial_holders_amounts.span() + ); + } + + #[test] + #[should_panic(expected: ('Unruggable: max team allocation',))] + fn test_constructor_too_much_team_alloc_should_fail() { + let mut calldata = array![ + OWNER().into(), 'locker', TRANSFER_LIMIT_DELAY.into(), NAME().into(), SYMBOL().into() + ]; + // Allocation over 10% (over 2.1M) + let alloc_holder_1 = 1_050_000 * pow_256(10, 18); + let alloc_holder_2 = 1_050_001 * pow_256(10, 18); + let mut state = UnruggableMemecoin::contract_state_for_testing(); + UnruggableMemecoin::constructor( + ref state, + OWNER(), + LOCKER_ADDRESS(), + TRANSFER_LIMIT_DELAY, + NAME(), + SYMBOL(), + DEFAULT_INITIAL_SUPPLY(), + INITIAL_HOLDERS(), + array![alloc_holder_1, alloc_holder_2].span() + ); + } } mod memecoin_entrypoints { use debug::PrintTrait; - use openzeppelin::token::erc20::interface::{ IERC20, ERC20ABIDispatcher, ERC20ABIDispatcherTrait }; @@ -94,131 +164,40 @@ mod memecoin_entrypoints { declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, start_warp, TxInfoMock }; use starknet::{ContractAddress, contract_address_const}; - use super::{deploy_contract, instantiate_params, ETH_UNIT_DECIMALS}; use unruggable::amm::amm::{AMM, AMMV2, AMMTrait}; use unruggable::amm::jediswap_interface::{ IFactoryC1, IFactoryC1Dispatcher, IFactoryC1DispatcherTrait, IRouterC1, IRouterC1Dispatcher, IRouterC1DispatcherTrait, IPairDispatcher, IPairDispatcherTrait }; - use unruggable::factory::{IFactory, IFactoryDispatcher, IFactoryDispatcherTrait}; - use unruggable::tests::utils::DeployerHelper::{ - deploy_contracts, deploy_unruggable_memecoin_contract, deploy_memecoin_factory, create_eth, - }; use unruggable::tests::utils::{ deploy_amm_factory_and_router, deploy_meme_factory_with_owner, deploy_locker, deploy_eth_with_owner, OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, INITIAL_HOLDERS, - INITIAL_HOLDERS_AMOUNTS, SALT, DefaultTxInfoMock + INITIAL_HOLDER_1, INITIAL_HOLDER_2, INITIAL_HOLDERS_AMOUNTS, SALT, DefaultTxInfoMock, + deploy_memecoin_through_factory, ETH_ADDRESS, deploy_memecoin_through_factory_with_owner, + JEDI_ROUTER_ADDRESS, MEMEFACTORY_ADDRESS, ALICE, BOB, pow_256, LOCKER_ADDRESS }; use unruggable::tokens::interface::{ IUnruggableMemecoin, IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; use unruggable::tokens::memecoin::UnruggableMemecoin; - #[test] - #[should_panic(expected: ('Caller is not the owner',))] - fn test_launch_memecoin_not_owner() { - // Setup - let (_, router_address) = deploy_contracts(); - let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; - - let (owner, name, symbol, _, _, initial_holder_2, _, _) = instantiate_params(); - let contract_address_salt = 'salty'; - let initial_holders = array![owner].span(); - let initial_holders_amounts = array![1 * ETH_UNIT_DECIMALS].span(); - - // Declare availables AMMs for this factory - let mut amms = array![AMM { name: AMMV2::JediSwap.to_string(), router_address }]; - - // Declare UnruggableMemecoin and use ClassHash for the Factory - let declare_memecoin = declare('UnruggableMemecoin'); - let memecoin_factory_address = deploy_memecoin_factory( - owner, declare_memecoin.class_hash, amms - ); - let unruggable_meme_factory = IFactoryDispatcher { - contract_address: memecoin_factory_address - }; - - let locker_calldata = array![200]; - let locker_contract = declare('TokenLocker'); - let locker_address = locker_contract.deploy(@locker_calldata).unwrap(); - - let initial_supply: u256 = 100 * ETH_UNIT_DECIMALS; - - let eth = create_eth(initial_supply, owner, unruggable_meme_factory.contract_address); - - start_prank(CheatTarget::One(unruggable_meme_factory.contract_address), owner); - // Create a MemeCoin - let memecoin_address = unruggable_meme_factory - .create_memecoin( - owner, - locker_address, - name, - symbol, - initial_supply, - initial_holders, - initial_holders_amounts, - 1000, - eth, - contract_address_salt - ); - stop_prank(CheatTarget::One(unruggable_meme_factory.contract_address)); - let unruggable_memecoin = IUnruggableMemecoinDispatcher { - contract_address: memecoin_address - }; - unruggable_memecoin.launch_memecoin(AMMV2::JediSwap, eth.contract_address,); - } #[test] fn test_launch_memecoin_happy_path() { - // Setup - let (_, router_address) = deploy_amm_factory_and_router(); - let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; - - // NOTE: - // 1. The initial call to `memecoin_address` should be made by the owner. - // 2. Subsequently, the router needs to call memecoin to transfer tokens to the pool. - // 3. The second call to `memecoin_address` should be made by the router. - // However, note that the prank still designates owner as the caller. - // Since we can't switch the mock caller target inside a function call, we cannot rely on - // starknet foundry's `start_prank` to test this. - // However, if we make the test contract the owner of the memecoin, - // then we can simply call `launch_memecoin` and all caller contexts will be correct. let owner = starknet::get_contract_address(); + let (memecoin, memecoin_address) = deploy_memecoin_through_factory_with_owner(owner); + let eth = ERC20ABIDispatcher { contract_address: ETH_ADDRESS() }; - let memecoin_factory_address = deploy_meme_factory_with_owner(owner, router_address); - let memecoin_factory = IFactoryDispatcher { contract_address: memecoin_factory_address }; - - let locker = deploy_locker(); - - let (eth, eth_address) = deploy_eth_with_owner(owner); - start_prank(CheatTarget::One(eth.contract_address), owner); - eth.approve(spender: memecoin_factory_address, amount: 1 * ETH_UNIT_DECIMALS); - stop_prank(CheatTarget::One(eth.contract_address)); - - start_prank(CheatTarget::One(memecoin_factory_address), owner); - // Create a MemeCoin - let memecoin_address = memecoin_factory - .create_memecoin( - owner: owner, - locker_address: locker, - name: NAME(), - symbol: SYMBOL(), - initial_supply: DEFAULT_INITIAL_SUPPLY(), - initial_holders: INITIAL_HOLDERS(), - initial_holders_amounts: INITIAL_HOLDERS_AMOUNTS(), - transfer_limit_delay: 1000, - counterparty_token: eth, - contract_address_salt: SALT(), - ); - stop_prank(CheatTarget::One(memecoin_factory_address)); - let memecoin = IUnruggableMemecoinDispatcher { contract_address: memecoin_address }; - + // The amount supplied as liquidity are the amount + // held by the memecoin contract pre-launch let memecoin_bal_meme = memecoin.balanceOf(memecoin_address); let memecoin_bal_eth = eth.balanceOf(memecoin_address); - start_prank(CheatTarget::One(router_address), memecoin_address); + start_prank(CheatTarget::One(JEDI_ROUTER_ADDRESS()), memecoin_address); let pool_address = memecoin.launch_memecoin(AMMV2::JediSwap, eth.contract_address); - stop_prank(CheatTarget::One(memecoin_factory_address)); + stop_prank(CheatTarget::One(MEMEFACTORY_ADDRESS())); + + assert(memecoin.launched(), 'should be launched'); let pool_dispatcher = IPairDispatcher { contract_address: pool_address }; let (token_0_reserves, token_1_reserves, _) = pool_dispatcher.get_reserves(); assert(pool_dispatcher.token0() == memecoin_address, 'wrong token 0 address'); @@ -230,407 +209,98 @@ mod memecoin_entrypoints { } #[test] - fn test_get_team_allocation() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - let team_alloc = memecoin.get_team_allocation(); - // theorical team allocation is 10%, so initial_supply * MAX_TEAM_ALLOC / 100 - // 1000 * 100 / 100 = 100 - assert(team_alloc == 100, 'Invalid team allocation'); + #[should_panic(expected: ('Caller is not the owner',))] + fn test_launch_memecoin_not_owner() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); + memecoin.launch_memecoin(AMMV2::JediSwap, ETH_ADDRESS(),); } #[test] - #[should_panic(expected: ('Max buy cap reached',))] - fn test_transfer_max_percentage() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let alice = contract_address_const::<53>(); - - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + #[should_panic(expected: ('Unruggable: AMM not supported',))] + fn test_launch_memecoin_amm_not_supported() { + let owner = starknet::get_contract_address(); + let (memecoin, memecoin_address) = deploy_memecoin_through_factory_with_owner(owner); + let eth = ERC20ABIDispatcher { contract_address: ETH_ADDRESS() }; - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); + // The amount supplied as liquidity are the amount + // held by the memecoin contract pre-launch + let memecoin_bal_meme = memecoin.balanceOf(memecoin_address); + let memecoin_bal_eth = eth.balanceOf(memecoin_address); - // Transfer 21 token from owner to alice. - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - let send_amount = memecoin.transfer(alice, 21); + start_prank(CheatTarget::One(JEDI_ROUTER_ADDRESS()), memecoin_address); + let pool_address = memecoin.launch_memecoin(AMMV2::Ekubo, eth.contract_address); } #[test] - #[should_panic(expected: ('Max buy cap reached',))] - fn test_transfer_from_max_percentage() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let alice = contract_address_const::<53>(); - - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); + fn test_get_team_allocation() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - // Transfer 21 token from owner to alice. - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - let send_amount = memecoin.transfer_from(initial_holder_1, alice, 500); + let team_alloc = memecoin.get_team_allocation(); + // Team alloc is set to 10% in test utils + assert(team_alloc == 2_100_000 * pow_256(10, 18), 'Invalid team allocation'); } #[test] - #[should_panic(expected: ('Multi calls not allowed',))] - fn test_transfer_from_multi_call() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let alice = contract_address_const::<53>(); - let bob = contract_address_const::<54>(); - - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; + fn test_memecoin_factory_address() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - - // Transfer token from owner to alice twice - should fail - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - let send_amount = memecoin.transfer_from(initial_holder_1, alice, 0); - let send_amount = memecoin.transfer_from(initial_holder_1, alice, 0); + assert( + memecoin.memecoin_factory_address() == MEMEFACTORY_ADDRESS(), 'wrong factory address' + ); } #[test] - fn test_classic_max_percentage() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - let alice = contract_address_const::<53>(); - - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + fn test_locker_address() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - // setting tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(1234); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); - - // Transfer 1 token from owner to alice. - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - let send_amount = memecoin.transfer(alice, 20); - assert(memecoin.balanceOf(alice) == 20, 'Invalid balance'); + assert(memecoin.locker_address() == LOCKER_ADDRESS(), 'wrong locker address'); } -} -mod custom_constructor { - use core::debug::PrintTrait; - use openzeppelin::token::erc20::interface::IERC20; - use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; - use starknet::{ContractAddress, contract_address_const}; - use super::{deploy_contract, instantiate_params}; - use unruggable::tokens::interface::{ - IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait - }; #[test] - #[should_panic(expected: ('Unruggable: arrays len dif',))] - fn test_constructor_initial_holders_arrays_len_mismatch() { - let (owner, name, symbol, initial_supply, initial_holder_1, initial_holder_2, _, _) = - instantiate_params(); - let initial_holder_3 = contract_address_const::<52>(); - let initial_holder_4 = contract_address_const::<53>(); - let initial_holders = array![ - initial_holder_1, initial_holder_2, initial_holder_3, initial_holder_4 - ] - .span(); - let initial_holders_amounts = array![50, 40, 10].span(); - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address.print(), - Result::Err(msg) => panic(msg.panic_data), - } - } + #[should_panic(expected: ('Max buy cap reached',))] + fn test_transfer_max_percentage() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - #[test] - fn test_constructor_initial_holders_arrays_len_is_equal() { - let (owner, name, symbol, initial_supply, initial_holder_1, initial_holder_2, _, _) = - instantiate_params(); - let initial_holder_3 = contract_address_const::<52>(); - // array_len is 4 - let initial_holders = array![initial_holder_1, initial_holder_2, initial_holder_3].span(); - // array_len is 4 - let initial_holders_amounts = array![50, 40, 10].span(); - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => {}, - Result::Err(msg) => panic(msg.panic_data), - } + // Transfer slightly more than 2% of 21M stokens from owner to ALICE(). + let amount = 420_001 * pow_256(10, 18); + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_1()); + let send_amount = memecoin.transfer(ALICE(), amount); } #[test] - #[should_panic(expected: ('Unruggable: max holders reached',))] - fn test_max_holders_reached() { - let (owner, name, symbol, initial_supply, initial_holder_1, initial_holder_2, _, _) = - instantiate_params(); - let initial_holder_3 = contract_address_const::<52>(); - let initial_holder_4 = contract_address_const::<53>(); - let initial_holder_5 = contract_address_const::<54>(); - let initial_holder_6 = contract_address_const::<55>(); - let initial_holder_7 = contract_address_const::<56>(); - let initial_holder_8 = contract_address_const::<57>(); - let initial_holder_9 = contract_address_const::<58>(); - let initial_holder_10 = contract_address_const::<59>(); - let initial_holder_11 = contract_address_const::<60>(); - // 11 holders - let initial_holders = array![ - initial_holder_1, - initial_holder_2, - initial_holder_3, - initial_holder_4, - initial_holder_5, - initial_holder_6, - initial_holder_7, - initial_holder_8, - initial_holder_9, - initial_holder_10, - initial_holder_11, - ] - .span(); - let initial_holders_amounts = array![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,].span(); - - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address.print(), - Result::Err(msg) => panic(msg.panic_data), - } - } - - #[test] - fn test_max_holders_not_reached() { - let (owner, name, symbol, initial_supply, initial_holder_1, initial_holder_2, _, _) = - instantiate_params(); - let initial_holder_3 = contract_address_const::<52>(); - let initial_holder_4 = contract_address_const::<53>(); - let initial_holder_5 = contract_address_const::<54>(); - // 6 holders - let initial_holders = array![ - initial_holder_1, - initial_holder_2, - initial_holder_3, - initial_holder_4, - initial_holder_5, - ] - .span(); - let initial_holders_amounts = array![50, 47, 1, 1, 1,].span(); - - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => {}, - Result::Err(msg) => panic(msg.panic_data), - } - } + #[should_panic(expected: ('Max buy cap reached',))] + fn test_transfer_from_max_percentage() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - #[test] - fn test_initial_recipient_ok() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts - ) = - instantiate_params(); - - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => {}, - Result::Err(msg) => panic(msg.panic_data), - } + let amount = 420_001 * pow_256(10, 18); + start_prank(CheatTarget::One(memecoin.contract_address), OWNER()); + let send_amount = memecoin.transfer_from(OWNER(), ALICE(), amount); } #[test] - #[should_panic(expected: ('Unruggable: max team allocation',))] - fn test_max_team_allocation_fail() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _ - ) = - instantiate_params(); - // team should have less than 100 tokens - let initial_holders_amounts = array![100, 50,].span(); - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address.print(), - Result::Err(msg) => panic(msg.panic_data), - } - } + #[should_panic(expected: ('Multi calls not allowed',))] + fn test_transfer_from_multi_call() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - #[test] - fn test_max_team_allocation_ok() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _ - ) = - instantiate_params(); - // team should have less than 100 tokens - let initial_holders_amounts = array![50, 50,].span(); - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => {}, - Result::Err(msg) => panic(msg.panic_data), - } + // Transfer token from owner to ALICE() twice - should fail because + // the tx_hash is the same for both calls + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_1()); + let send_amount = memecoin.transfer_from(INITIAL_HOLDER_1(), ALICE(), 0); + let send_amount = memecoin.transfer_from(INITIAL_HOLDER_1(), ALICE(), 0); } #[test] - fn test_max_team_allocation_ok2() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _ - ) = - instantiate_params(); - // team should have less than 100 tokens - let initial_holders_amounts = array![50, 40,].span(); - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => {}, - Result::Err(msg) => panic(msg.panic_data), - } - } + fn test_classic_max_percentage() { + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - #[test] - fn test_max_supply_reached_ok() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _ - ) = - instantiate_params(); - // team should have less than 101 tokens - let initial_holders_amounts = array![50, 50].span(); - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => {}, - Result::Err(msg) => panic(msg.panic_data), - } + // Transfer 1 token from owner to ALICE(). + start_prank(CheatTarget::One(memecoin_address), INITIAL_HOLDER_1()); + let send_amount = memecoin.transfer(ALICE(), 20); + assert(memecoin.balanceOf(ALICE()) == 20, 'Invalid balance'); } } + mod memecoin_internals { use UnruggableMemecoin::{ UnruggableMemecoinInternalImpl, SnakeEntrypoints, UnruggableEntrypoints, @@ -640,8 +310,14 @@ mod memecoin_internals { use openzeppelin::token::erc20::interface::IERC20; use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; use starknet::{ContractAddress, contract_address_const}; - use super::{TxInfoMock, DefaultTxInfoMock}; - use super::{deploy_contract, instantiate_params}; + use super::{TxInfoMock}; + use unruggable::tests::utils::{ + deploy_amm_factory_and_router, deploy_meme_factory_with_owner, deploy_locker, + deploy_eth_with_owner, OWNER, NAME, SYMBOL, DEFAULT_INITIAL_SUPPLY, INITIAL_HOLDERS, + INITIAL_HOLDER_1, INITIAL_HOLDER_2, INITIAL_HOLDERS_AMOUNTS, SALT, DefaultTxInfoMock, + deploy_memecoin_through_factory, ETH_ADDRESS, deploy_memecoin_through_factory_with_owner, + JEDI_ROUTER_ADDRESS, MEMEFACTORY_ADDRESS, ALICE, BOB + }; use unruggable::tokens::interface::{ IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; @@ -649,30 +325,10 @@ mod memecoin_internals { #[test] fn test__transfer_recipients_equal_holder_cap() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts, - ) = - instantiate_params(); - - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - // set initial_holder_1 as caller to distribute tokens - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); + // set INITIAL_HOLDER_1() as caller to distribute tokens + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_1()); let mut index = 0; loop { @@ -700,69 +356,6 @@ mod memecoin_internals { }; } - #[test] - fn test__transfer_initial_holder_whole_balance() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _, - ) = - instantiate_params(); - let initial_holders_amounts = array![50, 20].span(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // set initial_holder_1 as caller to distribute tokens - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); - - let mut index = 0; - loop { - // MAX_HOLDERS_BEFORE_LAUNCH - 2 because there are 2 initial holders - if index == MAX_HOLDERS_BEFORE_LAUNCH - 2 { - break; - } - - // create a unique address - let unique_recipient: ContractAddress = (index.into() + 9999).try_into().unwrap(); - - // creating and setting unique tx_hash here - let mut tx_info: TxInfoMock = Default::default(); - tx_info.transaction_hash = Option::Some(index.into() + 9999); - snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); - - // Transfer 1 token to the unique recipient - memecoin.transfer(unique_recipient, 1); - - // Check recipient balance. Should be equal to 1. - let recipient_balance = memecoin.balanceOf(unique_recipient); - assert(recipient_balance == 1, 'Invalid balance recipient'); - - index += 1; - }; - - let unique_recipient: ContractAddress = (index.into() + 9999).try_into().unwrap(); - - // Send initial_holder_2 whole balance to the unique recipient - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_2); - memecoin.transfer(unique_recipient, 20); - - // Check recipient balance. Should be equal to 0. - let initial_holder_2_balance = memecoin.balanceOf(initial_holder_2); - assert(initial_holder_2_balance.is_zero(), 'Invalid balance holder 2'); - } - #[test] fn test__transfer_existing_holders() { /// pre launch holder number should not change when @@ -771,29 +364,10 @@ mod memecoin_internals { /// to test this, we are going to continously self transfer tokens /// and ensure that we can transfer more than `MAX_HOLDERS_BEFORE_LAUNCH` times - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - initial_holders_amounts, - ) = - instantiate_params(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - // set initial_holder_1 as caller to distribute tokens - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); + // set INITIAL_HOLDER_1() as caller to distribute tokens + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_1()); let mut index = 0; loop { @@ -807,7 +381,7 @@ mod memecoin_internals { snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); // Self transfer tokens - memecoin.transfer(initial_holder_2, 1); + memecoin.transfer(INITIAL_HOLDER_2(), 1); index += 1; }; @@ -816,31 +390,10 @@ mod memecoin_internals { #[test] #[should_panic(expected: ('Unruggable: max holders reached',))] fn test__transfer_above_holder_cap() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _ - ) = - instantiate_params(); - let initial_holders_amounts = array![50, 30].span(); - - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; + let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); - let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - - // set initial_holder_1 as caller to distribute tokens - start_prank(CheatTarget::One(memecoin.contract_address), initial_holder_1); + // set INITIAL_HOLDER_1() as caller to distribute tokens + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_1()); let mut index = 0; loop { @@ -862,30 +415,23 @@ mod memecoin_internals { }; } - // TODO: Uncomment test when foundry solves issue (read comment) - // #[test] + #[test] + #[ignore] + //TODO: implement this test fn test__transfer_no_holder_cap_after_launch() { - let ( - owner, - name, - symbol, - initial_supply, - initial_holder_1, - initial_holder_2, - initial_holders, - _ - ) = - instantiate_params(); + // The initial call to launch the memecoin should be made by the owner. + // However the subsequent calls should be made by the router - as such, + // we can't mock the owner using starknet-foundry. + // We simply set the test contract as the owner of the memecoin. + let owner = starknet::get_contract_address(); let initial_holders_amounts = array![50, 30,].span(); - let contract_address = - match deploy_contract( - owner, name, symbol, initial_supply, initial_holders, initial_holders_amounts - ) { - Result::Ok(address) => address, - Result::Err(msg) => panic(msg.panic_data), - }; - + let mut calldata = array![owner.into(), NAME().into(), SYMBOL().into()]; + Serde::serialize(@DEFAULT_INITIAL_SUPPLY(), ref calldata); + Serde::serialize(@INITIAL_HOLDERS(), ref calldata); + Serde::serialize(@initial_holders_amounts, ref calldata); + let contract = declare('UnruggableMemecoin'); + let contract_address = contract.deploy(@calldata).expect('failed to deploy memecoin'); let memecoin = IUnruggableMemecoinDispatcher { contract_address }; // set owner as caller to launch the token @@ -901,15 +447,15 @@ mod memecoin_internals { // start_prank(CheatTarget::One(memecoin_address), router_address); // start_prank(CheatTarget::One(router_address), memecoin_address); - // unruggable_memecoin + // memecoin // .launch_memecoin( // AMMV2::JediSwap, counterparty_token_address, 20000000000000000, 1 * ETH_UNIT_DECIMALS // ); // TODO: call launch_memecoin() with params // memecoin.launch_memecoin(); - // set initial_holder_1 as caller to distribute tokens - start_prank(CheatTarget::All, initial_holder_1); + // set INITIAL_HOLDER_1() as caller to distribute tokens + start_prank(CheatTarget::All, INITIAL_HOLDER_1()); let mut index = 0; loop { diff --git a/contracts/src/tests/utils.cairo b/contracts/src/tests/utils.cairo index 071a3784..8a24838a 100644 --- a/contracts/src/tests/utils.cairo +++ b/contracts/src/tests/utils.cairo @@ -7,12 +7,32 @@ use snforge_std::{ use starknet::ContractAddress; use unruggable::amm::amm::{AMM, AMMV2, AMMTrait}; use unruggable::factory::{IFactoryDispatcher, IFactoryDispatcherTrait}; +use unruggable::tokens::interface::{ + IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait +}; + // Constants fn OWNER() -> ContractAddress { 'owner'.try_into().unwrap() } +fn RECIPIENT() -> ContractAddress { + 'recipient'.try_into().unwrap() +} + +fn SPENDER() -> ContractAddress { + 'spender'.try_into().unwrap() +} + +fn ALICE() -> ContractAddress { + 'alice'.try_into().unwrap() +} + +fn BOB() -> ContractAddress { + 'bob'.try_into().unwrap() +} + fn NAME() -> felt252 { 'name'.try_into().unwrap() } @@ -29,13 +49,13 @@ fn INITIAL_HOLDER_2() -> ContractAddress { 'initial_holder_2'.try_into().unwrap() } - fn INITIAL_HOLDERS() -> Span { array![INITIAL_HOLDER_1(), INITIAL_HOLDER_2()].span() } +// Hold 5% of the supply each - reaching 10% of the supply, the maximum allowed fn INITIAL_HOLDERS_AMOUNTS() -> Span { - array![50, 50].span() + array![1_050_000 * pow_256(10, 18), 1_050_000 * pow_256(10, 18)].span() } fn DEPLOYER() -> ContractAddress { @@ -50,26 +70,19 @@ fn DEFAULT_INITIAL_SUPPLY() -> u256 { 21_000_000 * pow_256(10, 18) } - -trait TxInfoMockTrait { - fn default() -> TxInfoMock; +fn ETH_INITIAL_SUPPLY() -> u256 { + 2 * pow_256(10, ETH_DECIMALS) } -impl DefaultTxInfoMock of Default { - fn default() -> TxInfoMock { - TxInfoMock { - version: Option::None, - account_contract_address: Option::None, - max_fee: Option::None, - signature: Option::None, - transaction_hash: Option::None, - chain_id: Option::None, - nonce: Option::None, - } - } +fn LOCKER_ADDRESS() -> ContractAddress { + 'locker'.try_into().unwrap() } +const ETH_DECIMALS: u8 = 18; +const TRANSFER_LIMIT_DELAY: u64 = 1000; + + // NOT THE ACTUAL ETH ADDRESS // It's set to a the maximum possible value for a contract address // This ensures that in Jediswap pairs, the ETH side is always token1 @@ -77,12 +90,51 @@ fn ETH_ADDRESS() -> ContractAddress { 0x7fff6570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7.try_into().unwrap() } +fn JEDI_ROUTER_ADDRESS() -> ContractAddress { + 'router_address'.try_into().unwrap() +} + +fn JEDI_FACTORY_ADDRESS() -> ContractAddress { + 'jediswap factory'.try_into().unwrap() +} + + +fn MEMEFACTORY_ADDRESS() -> ContractAddress { + 'memefactory_address'.try_into().unwrap() +} + fn DEFAULT_MIN_LOCKTIME() -> u64 { 200 } // Deployments +// Deploys a simple instance of the memcoin to test ERC20 basic entrypoints. +fn deploy_standalone_memecoin() -> (IUnruggableMemecoinDispatcher, ContractAddress) { + // Deploy the locker associated with the memecoin. + let locker = deploy_locker(); + + // Deploy the memecoin with the default parameters. + let contract = declare('UnruggableMemecoin'); + let mut calldata = array![ + OWNER().into(), locker.into(), TRANSFER_LIMIT_DELAY.into(), NAME().into(), SYMBOL().into(), + ]; + Serde::serialize(@DEFAULT_INITIAL_SUPPLY(), ref calldata); + Serde::serialize(@INITIAL_HOLDERS(), ref calldata); + Serde::serialize(@INITIAL_HOLDERS_AMOUNTS(), ref calldata); + let contract_address = contract.deploy(@calldata).expect('failed to deploy memecoin'); + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Set the transaction_hash to an arbitrary value - used to test the + // multicall buy prevention feature. + let mut tx_info: TxInfoMock = Default::default(); + tx_info.transaction_hash = Option::Some(1234); + snforge_std::start_spoof(CheatTarget::One(memecoin.contract_address), tx_info); + + (memecoin, contract_address) +} + + // AMM fn deploy_amm_factory() -> ContractAddress { @@ -94,7 +146,9 @@ fn deploy_amm_factory() -> ContractAddress { Serde::serialize(@DEPLOYER(), ref constructor_calldata); let factory_class = declare('FactoryC1'); - let factory_address = factory_class.deploy(@constructor_calldata).unwrap(); + let factory_address = factory_class + .deploy_at(@constructor_calldata, JEDI_FACTORY_ADDRESS()) + .unwrap(); factory_address } @@ -105,7 +159,9 @@ fn deploy_router(factory_address: ContractAddress) -> ContractAddress { let mut router_constructor_calldata = Default::default(); Serde::serialize(@factory_address, ref router_constructor_calldata); - let amm_router_address = amm_router_class.deploy(@router_constructor_calldata).unwrap(); + let amm_router_address = amm_router_class + .deploy_at(@router_constructor_calldata, JEDI_ROUTER_ADDRESS()) + .unwrap(); amm_router_address } @@ -129,7 +185,7 @@ fn deploy_meme_factory(router_address: ContractAddress) -> ContractAddress { Serde::serialize(@OWNER(), ref calldata); Serde::serialize(@memecoin_class_hash, ref calldata); Serde::serialize(@amms.into(), ref calldata); - contract.deploy(@calldata).expect('UnrugFactory deployment failed') + contract.deploy_at(@calldata, MEMEFACTORY_ADDRESS()).expect('UnrugFactory deployment failed') } fn deploy_meme_factory_with_owner( @@ -154,17 +210,11 @@ fn deploy_locker() -> ContractAddress { let mut calldata = Default::default(); Serde::serialize(@DEFAULT_MIN_LOCKTIME(), ref calldata); let locker_contract = declare('TokenLocker'); - locker_contract.deploy(@calldata).expect('Locker deployment failed') + locker_contract.deploy_at(@calldata, LOCKER_ADDRESS()).expect('Locker deployment failed') } // ETH Token -const ETH_DECIMALS: u8 = 18; - -fn ETH_INITIAL_SUPPLY() -> u256 { - 2 * pow_256(10, ETH_DECIMALS) -} - fn deploy_eth() -> (ERC20ABIDispatcher, ContractAddress) { deploy_eth_with_owner(OWNER()) } @@ -182,38 +232,69 @@ fn deploy_eth_with_owner(owner: ContractAddress) -> (ERC20ABIDispatcher, Contrac // Memercoin -fn deploy_memecoin() -> ContractAddress { +fn deploy_memecoin_through_factory_with_owner( + owner: ContractAddress +) -> (IUnruggableMemecoinDispatcher, ContractAddress) { // Required contracts let (_, router_address) = deploy_amm_factory_and_router(); let memecoin_factory_address = deploy_meme_factory(router_address); let memecoin_factory = IFactoryDispatcher { contract_address: memecoin_factory_address }; let locker_address = deploy_locker(); - let (eth, eth_address) = deploy_eth(); + let (eth, eth_address) = deploy_eth_with_owner(owner); let eth_amount: u256 = eth.total_supply() / 2; // 50% of supply - start_prank(CheatTarget::One(eth.contract_address), OWNER()); + start_prank(CheatTarget::One(eth.contract_address), owner); eth.approve(memecoin_factory_address, eth_amount); stop_prank(CheatTarget::One(eth.contract_address)); - start_prank(CheatTarget::One(memecoin_factory.contract_address), OWNER()); + start_prank(CheatTarget::One(memecoin_factory.contract_address), owner); let memecoin_address = memecoin_factory .create_memecoin( - owner: OWNER(), + owner: owner, :locker_address, name: NAME(), symbol: SYMBOL(), initial_supply: DEFAULT_INITIAL_SUPPLY(), initial_holders: INITIAL_HOLDERS(), initial_holders_amounts: INITIAL_HOLDERS_AMOUNTS(), - transfer_limit_delay: 1000, + transfer_limit_delay: TRANSFER_LIMIT_DELAY, counterparty_token: eth, contract_address_salt: SALT(), ); stop_prank(CheatTarget::One(memecoin_factory.contract_address)); - memecoin_address + + // Upon deployment, we mock the transaction_hash of the current tx. + // This is because for each tx, we check during transfers whether a transfer already + // occured in the same tx. Rather than adding these lines in each test, we make it a default. + let mut tx_info: TxInfoMock = Default::default(); + tx_info.transaction_hash = Option::Some(1234); + snforge_std::start_spoof(CheatTarget::One(memecoin_address), tx_info); + + (IUnruggableMemecoinDispatcher { contract_address: memecoin_address }, memecoin_address) } + +fn deploy_memecoin_through_factory() -> (IUnruggableMemecoinDispatcher, ContractAddress) { + deploy_memecoin_through_factory_with_owner(OWNER()) +} + + +impl DefaultTxInfoMock of Default { + fn default() -> TxInfoMock { + TxInfoMock { + version: Option::None, + account_contract_address: Option::None, + max_fee: Option::None, + signature: Option::None, + transaction_hash: Option::None, + chain_id: Option::None, + nonce: Option::None, + } + } +} + + // Math fn pow_256(self: u256, mut exponent: u8) -> u256 { if self.is_zero() { @@ -235,97 +316,3 @@ fn pow_256(self: u256, mut exponent: u8) -> u256 { base = base * base; } } - -//TODO: legacy, remove later -mod DeployerHelper { - use openzeppelin::token::erc20::interface::{ - IERC20, ERC20ABIDispatcher, ERC20ABIDispatcherTrait - }; - use snforge_std::{ - ContractClass, ContractClassTrait, CheatTarget, declare, start_prank, stop_prank - }; - use starknet::{ContractAddress, ClassHash, contract_address_const}; - use unruggable::amm::amm::AMM; - - const ETH_UNIT_DECIMALS: u256 = 1000000000000000000; - - fn deploy_contracts() -> (ContractAddress, ContractAddress) { - let deployer = contract_address_const::<'DEPLOYER'>(); - let pair_class = declare('PairC1'); - - let mut factory_constructor_calldata = Default::default(); - - Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); - Serde::serialize(@deployer, ref factory_constructor_calldata); - let factory_class = declare('FactoryC1'); - - let factory_address = factory_class.deploy(@factory_constructor_calldata).unwrap(); - - let mut router_constructor_calldata = Default::default(); - Serde::serialize(@factory_address, ref router_constructor_calldata); - let router_class = declare('RouterC1'); - - let router_address = router_class.deploy(@router_constructor_calldata).unwrap(); - - (factory_address, router_address) - } - - fn deploy_unruggable_memecoin_contract( - owner: ContractAddress, - recipient: ContractAddress, - name: felt252, - symbol: felt252, - initial_supply: u256, - amms: Array - ) -> ContractAddress { - let contract = declare('UnruggableMemecoin'); - let mut constructor_calldata = array![ - owner.into(), - recipient.into(), - name, - symbol, - initial_supply.low.into(), - initial_supply.high.into(), - ]; - contract.deploy(@constructor_calldata).unwrap() - } - - fn deploy_memecoin_factory( - owner: ContractAddress, memecoin_class_hash: ClassHash, amms: Array - ) -> ContractAddress { - let contract = declare('Factory'); - let mut calldata = array![]; - calldata.append(owner.into()); - calldata.append(memecoin_class_hash.into()); - Serde::serialize(@amms.into(), ref calldata); - - contract.deploy(@calldata).unwrap() - } - - fn create_eth( - initial_supply: u256, owner: ContractAddress, factory: ContractAddress - ) -> ERC20ABIDispatcher { - let erc20_token = declare('ERC20Token'); - let eth_amount: u256 = initial_supply; - let erc20_calldata: Array = array![ - eth_amount.low.into(), eth_amount.high.into(), owner.into() - ]; - let eth_address = erc20_token - .deploy_at( - @erc20_calldata, - contract_address_const::< - 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 - >() - ) - .unwrap(); - let eth = ERC20ABIDispatcher { contract_address: eth_address }; - assert(eth.balanceOf(owner) == initial_supply, 'wrong eth balance'); - start_prank(CheatTarget::One(eth.contract_address), owner); - eth.approve(spender: factory, amount: 1 * ETH_UNIT_DECIMALS); - stop_prank(CheatTarget::One(eth.contract_address)); - assert( - eth.allowance(:owner, spender: factory) == 1 * ETH_UNIT_DECIMALS, 'wrong eth allowance' - ); - eth - } -} diff --git a/contracts/src/tokens/interface.cairo b/contracts/src/tokens/interface.cairo index 63fd4ffd..e1327aed 100644 --- a/contracts/src/tokens/interface.cairo +++ b/contracts/src/tokens/interface.cairo @@ -4,6 +4,13 @@ use unruggable::amm::amm::AMMV2; #[starknet::interface] trait IUnruggableMemecoin { + // ************************************ + // * Ownership + // ************************************ + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + // ************************************ // * Metadata // ************************************ @@ -44,6 +51,8 @@ trait IUnruggableMemecoin { ref self: TState, amm_v2: AMMV2, counterparty_token_address: ContractAddress, ) -> ContractAddress; fn get_team_allocation(self: @TState) -> u256; + fn memecoin_factory_address(self: @TState) -> ContractAddress; + fn locker_address(self: @TState) -> ContractAddress; } #[starknet::interface] @@ -78,4 +87,6 @@ trait IUnruggableAdditional { ref self: TState, amm_v2: AMMV2, counterparty_token_address: ContractAddress, ) -> ContractAddress; fn get_team_allocation(self: @TState) -> u256; + fn memecoin_factory_address(self: @TState) -> ContractAddress; + fn locker_address(self: @TState) -> ContractAddress; } diff --git a/contracts/src/tokens/memecoin.cairo b/contracts/src/tokens/memecoin.cairo index c5686f6e..8f073f16 100644 --- a/contracts/src/tokens/memecoin.cairo +++ b/contracts/src/tokens/memecoin.cairo @@ -32,15 +32,14 @@ mod UnruggableMemecoin { // Components. component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; component!(path: ERC20Component, storage: erc20, event: ERC20Event); - // Internals - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // ERC20 entrypoints. #[abi(embed_v0)] impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; // Constants. /// The maximum number of holders allowed before launch. @@ -179,7 +178,7 @@ mod UnruggableMemecoin { }; let counterparty_token_balance = counterparty_token.balanceOf(memecoin_address); - // [Approve] + // Approval is done with the internal _approve as the owner must be `address(this)`, not `caller`. self.erc20._approve(memecoin_address, amm_router.contract_address, memecoin_balance); counterparty_token.approve(amm_router.contract_address, counterparty_token_balance); @@ -199,7 +198,7 @@ mod UnruggableMemecoin { assert(self.balanceOf(pair_address) == memecoin_balance, 'add liquidity meme failed'); assert( counterparty_token.balanceOf(pair_address) == counterparty_token_balance, - 'add liquidity eth failed' + 'add liq counterparty failed' ); let pair = ERC20ABIDispatcher { contract_address: pair_address, }; @@ -209,8 +208,6 @@ mod UnruggableMemecoin { let locker_address = self.locker_contract.read(); let locker_dispatcher = ITokenLockerDispatcher { contract_address: locker_address }; pair.approve(locker_address, liquidity_received); - // unlock_time: u64, - // withdrawer: ContractAddress locker_dispatcher .lock_tokens( token: pair_address, @@ -235,6 +232,14 @@ mod UnruggableMemecoin { fn get_team_allocation(self: @ContractState) -> u256 { self.team_allocation.read() } + + fn memecoin_factory_address(self: @ContractState) -> ContractAddress { + self.factory_contract.read() + } + + fn locker_address(self: @ContractState) -> ContractAddress { + self.locker_contract.read() + } } #[abi(embed_v0)] @@ -490,6 +495,8 @@ mod UnruggableMemecoin { /// /// * If the transfer amount exceeds the maximum allowed percentage of the total supply during the launch phase. /// + //TODO: verify compatibility with LP pool. If lp calls `transferFrom` this might fail. + // Not sure why the pool is whitelisted #[inline(always)] fn enforce_max_transfer_percentage( self: @ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256