Skip to content

Commit

Permalink
Max percentage buy provided at launch (#138)
Browse files Browse the repository at this point in the history
The max percentage buy limit was hardcoded to 2% before this PR, now it
has to be provided during launch, and the value cannot fall behind 0.5%
  • Loading branch information
0xChqrles authored Jan 10, 2024
1 parent 1323cfc commit 989f075
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 21 deletions.
1 change: 1 addition & 0 deletions contracts/src/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ const NOT_FACTORY: felt252 = 'Caller not factory';
const CALLER_NOT_OWNER: felt252 = 'Caller is not the owner';
const ALREADY_LAUNCHED: felt252 = 'Already launched';
const PRICE_ZERO: felt252 = 'Starting tick cannot be 0';
const MAX_PERCENTAGE_BUY_LAUNCH_TOO_LOW: felt252 = 'Max percentage buy too low';

This comment has been minimized.

Copy link
@19Justin87

19Justin87 Jan 12, 2024

Looks Great

14 changes: 12 additions & 2 deletions contracts/src/factory/factory.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ mod Factory {
ref self: ContractState,
memecoin_address: ContractAddress,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16,
quote_address: ContractAddress,
quote_amount: u256,
unlock_time: u64,
Expand All @@ -150,7 +151,12 @@ mod Factory {
}
);

memecoin.set_launched(LiquidityType::ERC20(pair_address), :transfer_restriction_delay);
memecoin
.set_launched(
LiquidityType::ERC20(pair_address),
:transfer_restriction_delay,
:max_percentage_buy_launch
);
self
.emit(
MemecoinLaunched {
Expand All @@ -164,6 +170,7 @@ mod Factory {
ref self: ContractState,
memecoin_address: ContractAddress,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16,
quote_address: ContractAddress,
ekubo_parameters: EkuboPoolParameters,
) -> (u64, EkuboLP) {
Expand All @@ -183,7 +190,10 @@ mod Factory {
additional_parameters: ekubo_parameters
);

memecoin.set_launched(LiquidityType::NFT(id), :transfer_restriction_delay);
memecoin
.set_launched(
LiquidityType::NFT(id), :transfer_restriction_delay, :max_percentage_buy_launch
);
self
.emit(
MemecoinLaunched {
Expand Down
3 changes: 3 additions & 0 deletions contracts/src/factory/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ trait IFactory<TContractState> {
///
/// * `memecoin_address` - The address of the memecoin contract.
/// * `transfer_restriction_delay` - The delay in seconds during which transfers will be limited to a % of max supply after launch.
/// * `max_percentage_buy_launch` - The max buyable amount in % of the max supply after launch and during the transfer restriction delay.
/// * `quote_address` - The address of the quote token contract.
/// * `quote_amount` - The amount of quote tokens to add as liquidity.
/// * `unlock_time` - The timestamp when the liquidity can be unlocked.
Expand All @@ -65,6 +66,7 @@ trait IFactory<TContractState> {
ref self: TContractState,
memecoin_address: ContractAddress,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16,
quote_address: ContractAddress,
quote_amount: u256,
unlock_time: u64,
Expand Down Expand Up @@ -102,6 +104,7 @@ trait IFactory<TContractState> {
ref self: TContractState,
memecoin_address: ContractAddress,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16,
quote_address: ContractAddress,
ekubo_parameters: EkuboPoolParameters,
) -> (u64, EkuboLP);
Expand Down
12 changes: 7 additions & 5 deletions contracts/src/tests/fork_tests/test_ekubo.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use unruggable::tests::fork_tests::utils::{
};
use unruggable::tests::unit_tests::utils::{
OWNER, DEFAULT_MIN_LOCKTIME, pow_256, LOCK_MANAGER_ADDRESS, MEMEFACTORY_ADDRESS, RECIPIENT,
ALICE, DefaultTxInfoMock, TRANSFER_RESTRICTION_DELAY,
ALICE, DefaultTxInfoMock, TRANSFER_RESTRICTION_DELAY, MAX_PERCENTAGE_BUY_LAUNCH
};
use unruggable::token::interface::{
IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait
Expand All @@ -49,6 +49,7 @@ fn launch_memecoin_on_ekubo(
.launch_on_ekubo(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
quote_address,
EkuboPoolParameters { fee, tick_spacing, starting_tick, bound }
);
Expand Down Expand Up @@ -301,7 +302,7 @@ fn test_swap_token0_price_below_1() {
};

// Check that swaps work correctly
let amount_in = 2 * pow_256(10, 16);
let amount_in = MAX_PERCENTAGE_BUY_LAUNCH.into() * pow_256(10, 14);
swap_tokens_on_ekubo(
token_in_address: quote_address,
:amount_in,
Expand Down Expand Up @@ -369,7 +370,7 @@ fn test_launch_meme_token1_price_below_1() {
);
assert(reserve_memecoin > expected_reserve_lower_bound, 'reserves holds too few token');

let amount_in = 2 * pow_256(10, 16);
let amount_in = MAX_PERCENTAGE_BUY_LAUNCH.into() * pow_256(10, 14);
swap_tokens_on_ekubo(
token_in_address: quote_address,
:amount_in,
Expand Down Expand Up @@ -437,7 +438,7 @@ fn test_launch_meme_token0_price_above_1() {
assert(reserve_memecoin > expected_reserve_lower_bound, 'reserves holds too few token');

// Test that swaps work correctly
let amount_in = 2 * pow_256(10, 16);
let amount_in = MAX_PERCENTAGE_BUY_LAUNCH.into() * pow_256(10, 14);
swap_tokens_on_ekubo(
token_in_address: quote_address,
:amount_in,
Expand Down Expand Up @@ -506,7 +507,7 @@ fn test_launch_meme_token1_price_above_1() {
assert(reserve_memecoin > expected_reserve_lower_bound, 'reserves holds too few token');

// Check that swaps work correctly
let amount_in = 2 * pow_256(10, 16);
let amount_in = MAX_PERCENTAGE_BUY_LAUNCH.into() * pow_256(10, 14);
swap_tokens_on_ekubo(
token_in_address: quote_address,
:amount_in,
Expand Down Expand Up @@ -618,6 +619,7 @@ fn test_cant_launch_twice() {
.launch_on_ekubo(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
quote_address,
EkuboPoolParameters {
fee: 0xc49ba5e353f7d00000000000000000,
Expand Down
15 changes: 10 additions & 5 deletions contracts/src/tests/fork_tests/test_jediswap.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use unruggable::tests::addresses::{JEDI_FACTORY_ADDRESS, JEDI_ROUTER_ADDRESS, ET
use unruggable::tests::fork_tests::utils::{deploy_memecoin_through_factory_with_owner, sort_tokens};
use unruggable::tests::unit_tests::utils::{
OWNER, DEFAULT_MIN_LOCKTIME, pow_256, LOCK_MANAGER_ADDRESS, MEMEFACTORY_ADDRESS,
deploy_eth_with_owner, TRANSFER_RESTRICTION_DELAY
deploy_eth_with_owner, TRANSFER_RESTRICTION_DELAY, MAX_PERCENTAGE_BUY_LAUNCH
};
use unruggable::token::interface::{IUnruggableMemecoinDispatcherTrait};
use unruggable::token::memecoin::LiquidityType;
Expand All @@ -38,7 +38,12 @@ fn test_jediswap_integration() {

let pair_address = factory
.launch_on_jediswap(
memecoin_address, TRANSFER_RESTRICTION_DELAY, quote_address, amount, unlock_time
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
quote_address,
amount,
unlock_time
);

let pair = IJediswapPairDispatcher { contract_address: pair_address };
Expand All @@ -50,10 +55,10 @@ fn test_jediswap_integration() {
quote.approve(JEDI_ROUTER_ADDRESS(), 1 * pow_256(10, 18));
stop_prank(CheatTarget::One(quote.contract_address));

// Max buy cap is 2% of total supply
// Max buy cap is `MAX_PERCENTAGE_BUY_LAUNCH` of total supply
// Initial rate is roughly 1 ETH for 21M meme,
// so max buy is ~ 2% of 1 ETH = 0.02 ETH
let amount_in = 2 * pow_256(10, 16);
// so if max buy is ~ 2% of 1 ETH = 0.02 ETH
let amount_in = MAX_PERCENTAGE_BUY_LAUNCH.into() * pow_256(10, 14);
start_prank(CheatTarget::One(router.contract_address), owner);
let first_swap = router
.swap_exact_tokens_for_tokens(
Expand Down
39 changes: 37 additions & 2 deletions contracts/src/tests/unit_tests/test_factory.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use unruggable::tests::unit_tests::utils::{
SYMBOL, DEFAULT_INITIAL_SUPPLY, INITIAL_HOLDERS, INITIAL_HOLDERS_AMOUNTS, SALT,
deploy_memecoin_through_factory, MEMEFACTORY_ADDRESS,
deploy_memecoin_through_factory_with_owner, pow_256, LOCK_MANAGER_ADDRESS, DEFAULT_MIN_LOCKTIME,
deploy_and_launch_memecoin, TRANSFER_RESTRICTION_DELAY
deploy_and_launch_memecoin, TRANSFER_RESTRICTION_DELAY, MAX_PERCENTAGE_BUY_LAUNCH
};
use unruggable::token::interface::{
IUnruggableMemecoin, IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait
Expand Down Expand Up @@ -141,6 +141,7 @@ fn test_launch_memecoin_happy_path() {
.launch_on_jediswap(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
eth.contract_address,
eth_amount,
DEFAULT_MIN_LOCKTIME,
Expand Down Expand Up @@ -196,6 +197,34 @@ fn test_launch_memecoin_already_launched() {
.launch_on_jediswap(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
eth.contract_address,
eth_amount,
DEFAULT_MIN_LOCKTIME,
);
}

#[test]
#[should_panic(expected: ('Max percentage buy too low',))]
fn test_launch_memecoin_with_percentage_buy_launch_too_low() {
let owner = snforge_std::test_address();
let (memecoin, memecoin_address) = deploy_memecoin_through_factory_with_owner(owner);
let factory = IFactoryDispatcher { contract_address: MEMEFACTORY_ADDRESS() };
let eth = ERC20ABIDispatcher { contract_address: ETH_ADDRESS() };

// approve spending of eth by factory
let eth_amount: u256 = 1 * pow_256(10, 18); // 1 ETHER
let factory_balance_meme = memecoin.balanceOf(factory.contract_address);
start_prank(CheatTarget::One(eth.contract_address), owner);
eth.approve(factory.contract_address, eth_amount);
stop_prank(CheatTarget::One(eth.contract_address));

start_prank(CheatTarget::One(factory.contract_address), owner);
let pair_address = factory
.launch_on_jediswap(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
49, // 0.49%
eth.contract_address,
eth_amount,
DEFAULT_MIN_LOCKTIME,
Expand All @@ -209,7 +238,12 @@ fn test_launch_memecoin_not_owner() {
let factory = IFactoryDispatcher { contract_address: MEMEFACTORY_ADDRESS() };
let pair_address = factory
.launch_on_jediswap(
memecoin_address, TRANSFER_RESTRICTION_DELAY, ETH_ADDRESS(), 1, DEFAULT_MIN_LOCKTIME,
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
ETH_ADDRESS(),
1,
DEFAULT_MIN_LOCKTIME,
);
}

Expand All @@ -228,6 +262,7 @@ fn test_launch_memecoin_amm_not_whitelisted() {
.launch_on_ekubo(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
eth.contract_address,
EkuboPoolParameters {
fee: 0, tick_spacing: 0, starting_tick: i129 { sign: false, mag: 0 }, bound: 0
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/tests/unit_tests/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fn UNLOCK_TIME() -> u64 {

const ETH_DECIMALS: u8 = 18;
const TRANSFER_RESTRICTION_DELAY: u64 = 1000;
const MAX_PERCENTAGE_BUY_LAUNCH: u16 = 200; // 2%


fn MEMEFACTORY_ADDRESS() -> ContractAddress {
Expand Down Expand Up @@ -274,6 +275,7 @@ fn deploy_and_launch_memecoin() -> (IUnruggableMemecoinDispatcher, ContractAddre
.launch_on_jediswap(
memecoin_address,
TRANSFER_RESTRICTION_DELAY,
MAX_PERCENTAGE_BUY_LAUNCH,
eth.contract_address,
eth_amount,
DEFAULT_MIN_LOCKTIME,
Expand Down
10 changes: 8 additions & 2 deletions contracts/src/token/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ trait IUnruggableMemecoin<TState> {
fn get_team_allocation(self: @TState) -> u256;
fn memecoin_factory_address(self: @TState) -> ContractAddress;
fn set_launched(
ref self: TState, liquidity_type: LiquidityType, transfer_restriction_delay: u64
ref self: TState,
liquidity_type: LiquidityType,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16
);
}

Expand Down Expand Up @@ -113,6 +116,9 @@ trait IUnruggableAdditional<TState> {
/// * The memecoin has already been launched (error code: `errors::ALREADY_LAUNCHED`).
///
fn set_launched(
ref self: TState, liquidity_type: LiquidityType, transfer_restriction_delay: u64
ref self: TState,
liquidity_type: LiquidityType,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16
);
}
20 changes: 15 additions & 5 deletions contracts/src/token/memecoin.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ mod UnruggableMemecoin {
/// 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 percentage of the supply that can be bought at once.
//TODO: discuss whether this should be a constant or a parameter
const MAX_PERCENTAGE_BUY_LAUNCH: u8 = 200; // 2%
/// The minimum maximum percentage of the supply that can be bought at once.
const MIN_MAX_PERCENTAGE_BUY_LAUNCH: u16 = 50; // 0.5%

#[storage]
struct Storage {
Expand All @@ -75,6 +74,7 @@ mod UnruggableMemecoin {
launch_time: u64,
factory_contract: ContractAddress,
liquidity_type: Option<LiquidityType>,
max_percentage_buy_launch: u16,
// Components.
#[substorage(v0)]
ownable: OwnableComponent::Storage,
Expand Down Expand Up @@ -145,16 +145,24 @@ mod UnruggableMemecoin {
}

fn set_launched(
ref self: ContractState, liquidity_type: LiquidityType, transfer_restriction_delay: u64
ref self: ContractState,
liquidity_type: LiquidityType,
transfer_restriction_delay: u64,
max_percentage_buy_launch: u16
) {
self.assert_only_factory();
assert(!self.is_launched(), errors::ALREADY_LAUNCHED);
assert(
max_percentage_buy_launch >= MIN_MAX_PERCENTAGE_BUY_LAUNCH,
errors::MAX_PERCENTAGE_BUY_LAUNCH_TOO_LOW
);

self.liquidity_type.write(Option::Some(liquidity_type));
self.launch_time.write(get_block_timestamp());

// Enable a transfer limit - until this time has passed,
// transfers are limited to a certain amount.
self.max_percentage_buy_launch.write(max_percentage_buy_launch);
self.transfer_restriction_delay.write(transfer_restriction_delay);

// renounce ownership
Expand Down Expand Up @@ -314,7 +322,9 @@ mod UnruggableMemecoin {
}

assert(
amount <= self.total_supply().percent_mul(MAX_PERCENTAGE_BUY_LAUNCH.into()),
amount <= self
.total_supply()
.percent_mul(self.max_percentage_buy_launch.read().into()),
'Max buy cap reached'
);

Expand Down

0 comments on commit 989f075

Please sign in to comment.