diff --git a/contracts/src/tests/test_unruggable_memecoin.cairo b/contracts/src/tests/test_unruggable_memecoin.cairo index 1167a795..ff3c3e38 100644 --- a/contracts/src/tests/test_unruggable_memecoin.cairo +++ b/contracts/src/tests/test_unruggable_memecoin.cairo @@ -161,7 +161,8 @@ mod memecoin_entrypoints { IERC20, ERC20ABIDispatcher, ERC20ABIDispatcherTrait }; use snforge_std::{ - declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, start_warp, TxInfoMock + declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, start_warp, stop_warp, + TxInfoMock }; use starknet::{ContractAddress, contract_address_const}; use unruggable::exchanges::jediswap_adapter::{ @@ -176,7 +177,8 @@ mod memecoin_entrypoints { 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, pow_256, LOCKER_ADDRESS + JEDI_ROUTER_ADDRESS, MEMEFACTORY_ADDRESS, ALICE, BOB, pow_256, LOCKER_ADDRESS, + deploy_and_launch_memecoin, TRANSFER_LIMIT_DELAY }; use unruggable::tokens::interface::{ IUnruggableMemecoin, IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait @@ -283,13 +285,31 @@ mod memecoin_entrypoints { #[test] #[should_panic(expected: ('Multi calls not allowed',))] fn test_transfer_from_multi_call() { - let (memecoin, memecoin_address) = deploy_memecoin_through_factory(); + let (memecoin, memecoin_address) = deploy_and_launch_memecoin(); // 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); + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_2()); + let send_amount = memecoin.transfer_from(INITIAL_HOLDER_2(), ALICE(), 0); + } + + #[test] + fn test_multi_call_prevention_disallowed_after_delay() { + let (memecoin, memecoin_address) = deploy_and_launch_memecoin(); + + let launch_timestamp = 1; + + // setting block timestamp >= launch_time + transfer_delay. Transfer should succeed + // as multi calls to the same recipient are allowed after the delay + start_warp( + CheatTarget::One(memecoin.contract_address), launch_timestamp + TRANSFER_LIMIT_DELAY + 1 + ); + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_1()); let send_amount = memecoin.transfer_from(INITIAL_HOLDER_1(), ALICE(), 0); + start_prank(CheatTarget::One(memecoin.contract_address), INITIAL_HOLDER_2()); + let send_amount = memecoin.transfer_from(INITIAL_HOLDER_2(), ALICE(), 0); } #[test] diff --git a/contracts/src/tests/utils.cairo b/contracts/src/tests/utils.cairo index beada457..c6f888d3 100644 --- a/contracts/src/tests/utils.cairo +++ b/contracts/src/tests/utils.cairo @@ -2,7 +2,8 @@ use core::traits::TryInto; use openzeppelin::token::erc20::interface::ERC20ABIDispatcher; use openzeppelin::token::erc20::interface::ERC20ABIDispatcherTrait; use snforge_std::{ - ContractClass, ContractClassTrait, CheatTarget, declare, start_prank, stop_prank, TxInfoMock + ContractClass, ContractClassTrait, CheatTarget, declare, start_prank, stop_prank, TxInfoMock, + start_warp, stop_warp }; use starknet::ContractAddress; use unruggable::exchanges::{Exchange, SupportedExchanges, ExchangeTrait}; @@ -287,6 +288,26 @@ fn deploy_memecoin_through_factory() -> (IUnruggableMemecoinDispatcher, Contract deploy_memecoin_through_factory_with_owner(OWNER()) } +// Sets the env block timestamp to 1 and launchs the memecoin - so that launched_at is 1 +// In this context, the owner of the factory is the address of the snforge test +fn deploy_and_launch_memecoin() -> (IUnruggableMemecoinDispatcher, ContractAddress) { + let owner = starknet::get_contract_address(); + let (memecoin, memecoin_address) = deploy_memecoin_through_factory_with_owner(owner); + let eth = ERC20ABIDispatcher { contract_address: ETH_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(JEDI_ROUTER_ADDRESS()), memecoin_address); + start_warp(CheatTarget::One(memecoin_address), 1); + let pool_address = memecoin.launch_memecoin(SupportedExchanges::JediSwap, eth.contract_address); + stop_prank(CheatTarget::One(MEMEFACTORY_ADDRESS())); + stop_warp(CheatTarget::One(memecoin_address)); + (memecoin, memecoin_address) +} + impl DefaultTxInfoMock of Default { fn default() -> TxInfoMock { diff --git a/contracts/src/tokens/memecoin.cairo b/contracts/src/tokens/memecoin.cairo index 4e623c1c..c691353e 100644 --- a/contracts/src/tokens/memecoin.cairo +++ b/contracts/src/tokens/memecoin.cairo @@ -252,7 +252,7 @@ mod UnruggableMemecoin { // which performs a transferFrom() to send the tokens to the pool. // Therefore, we need to bypass this validation if the sender is the memecoin contract. if sender != get_contract_address() { - self.ensure_not_multicall(sender); + self.ensure_not_multicall(recipient); self.enforce_max_transfer_percentage(sender, recipient, amount); self.enforce_prelaunch_holders_limit(sender, recipient, amount); } @@ -341,9 +341,15 @@ mod UnruggableMemecoin { // do multiple transfers when using complex routes. #[inline(always)] fn ensure_not_multicall(ref self: ContractState, recipient: ContractAddress) { - let tx_hash: felt252 = get_tx_info().unbox().transaction_hash; - assert(self.tx_hash_tracker.read(recipient) != tx_hash, 'Multi calls not allowed'); - self.tx_hash_tracker.write(recipient, tx_hash); + let launch_time = self.launch_time.read(); + let transfer_delay = self.transfer_limit_delay.read(); + let current_time = get_block_timestamp(); + + if (current_time < (launch_time + transfer_delay) || launch_time == 0_u64) { + let tx_hash: felt252 = get_tx_info().unbox().transaction_hash; + assert(self.tx_hash_tracker.read(recipient) != tx_hash, 'Multi calls not allowed'); + self.tx_hash_tracker.write(recipient, tx_hash); + } } /// Checks and allocates the team supply of the memecoin.