diff --git a/apps/trading/client-pages/referrals/referral-statistics.tsx b/apps/trading/client-pages/referrals/referral-statistics.tsx index d1d4137d4..5a12ea6fc 100644 --- a/apps/trading/client-pages/referrals/referral-statistics.tsx +++ b/apps/trading/client-pages/referrals/referral-statistics.tsx @@ -212,6 +212,7 @@ export const Statistics = ({ ).toString(), } )} + testId="base-commission-rate" overrideWithNoProgram={!details} > {baseCommissionValue * 100}% @@ -221,6 +222,7 @@ export const Statistics = ({ const stakingMultiplierTile = ( {finalCommissionFormatted}% @@ -261,7 +264,9 @@ export const Statistics = ({ ); const numberOfTradersValue = data.referees.length; const numberOfTradersTile = ( - {numberOfTradersValue} + + {numberOfTradersValue} + ); const codeTile = ( @@ -276,6 +281,7 @@ export const Statistics = ({ title={t('myVolume', 'My volume (last {{count}} epochs)', { count: details?.windowLength || DEFAULT_AGGREGATION_DAYS, })} + testId="my-volume" overrideWithNoProgram={!details} > {compactNumFormat.format(referrerVolumeValue)} @@ -291,6 +297,7 @@ export const Statistics = ({ count: details?.windowLength || DEFAULT_AGGREGATION_DAYS, })} description={} + testId="total-commission" > {getNumberFormat(0).format(Number(totalCommissionValue))} @@ -316,6 +323,7 @@ export const Statistics = ({ const currentBenefitTierTile = ( ); const discountFactorTile = ( - + {isApplyCodePreview && benefitTiers.length >= 1 ? benefitTiers[0].discountFactor * 100 : discountFactorValue * 100} @@ -347,23 +359,34 @@ export const Statistics = ({ count: details?.windowLength, } )} + testId="combined-volume" overrideWithNoProgram={!details} > {compactNumFormat.format(runningVolumeValue)} ); const epochsTile = ( - {epochsValue} + + {epochsValue} + ); const nextTierVolumeTile = ( - + {nextBenefitTierVolumeValue <= 0 ? '0' : compactNumFormat.format(nextBenefitTierVolumeValue)} ); const nextTierEpochsTile = ( - + {nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue} ); diff --git a/apps/trading/client-pages/referrals/tile.tsx b/apps/trading/client-pages/referrals/tile.tsx index ee447b873..8c839a4c3 100644 --- a/apps/trading/client-pages/referrals/tile.tsx +++ b/apps/trading/client-pages/referrals/tile.tsx @@ -32,6 +32,7 @@ export const Tile = ({ type StatTileProps = { title: string; + testId?: string; description?: ReactNode; children?: ReactNode; overrideWithNoProgram?: boolean; @@ -40,6 +41,7 @@ export const StatTile = ({ title, description, children, + testId, overrideWithNoProgram = false, }: StatTileProps) => { if (overrideWithNoProgram) { @@ -47,10 +49,15 @@ export const StatTile = ({ } return ( -

+

{title}

-
{children}
+
+ {children} +
{description && (
{description} diff --git a/apps/trading/e2e/actions/utils.py b/apps/trading/e2e/actions/utils.py index 8dd831f14..94b5a4d63 100644 --- a/apps/trading/e2e/actions/utils.py +++ b/apps/trading/e2e/actions/utils.py @@ -6,23 +6,27 @@ from typing import Optional WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"]) ASSET_NAME = "tDAI" + def wait_for_toast_confirmation(page: Page, timeout: int = 30000): page.wait_for_function(""" document.querySelector('[data-testid="toast-content"]') && document.querySelector('[data-testid="toast-content"]').innerText.includes('AWAITING CONFIRMATION') """, timeout=timeout) + def create_and_faucet_wallet( vega: VegaServiceNull, wallet: WalletConfig, symbol: Optional[str] = None, amount: float = 1e4, - + ): - asset_id = vega.find_asset_id(symbol=symbol if symbol is not None else ASSET_NAME) + asset_id = vega.find_asset_id( + symbol=symbol if symbol is not None else ASSET_NAME) vega.create_key(wallet.name) vega.mint(wallet.name, asset_id, amount) + def next_epoch(vega: VegaServiceNull): forwards = 0 epoch_seq = vega.statistics().epoch_seq @@ -36,13 +40,34 @@ def next_epoch(vega: VegaServiceNull): vega.wait_fn(1) vega.wait_for_total_catchup() + def truncate_middle(market_id, start=6, end=4): if len(market_id) < 11: return market_id return market_id[:start] + '\u2026' + market_id[-end:] -def change_keys(page: Page, vega:VegaServiceNull, key_name): + +def change_keys(page: Page, vega: VegaServiceNull, key_name): page.get_by_test_id("manage-vega-wallet").click() page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click() - page.click(f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex') + page.click( + f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex') page.reload() + + +def forward_time(vega: VegaServiceNull, forward_epoch: bool = False): + vega.wait_fn(1) + vega.wait_for_total_catchup() + + if forward_epoch: + next_epoch(vega) + + +# This is for when the element will initially load but contain an outdated value. It will wait for the element to contain the expected text, returning False after a timeout or exception +def selector_contains_text(page: Page, selector, expected_text, timeout=5000): + try: + page.wait_for_selector( + f'{selector} >> text={expected_text}', timeout=timeout) + return True + except: + return False diff --git a/apps/trading/e2e/actions/vega.py b/apps/trading/e2e/actions/vega.py index ff8f83a29..1b99202e8 100644 --- a/apps/trading/e2e/actions/vega.py +++ b/apps/trading/e2e/actions/vega.py @@ -1,6 +1,7 @@ from typing import List, Tuple, Optional from vega_sim.service import VegaService, PeggedOrder + def submit_order( vega: VegaService, wallet_name: str, @@ -35,7 +36,7 @@ def submit_multiple_orders( submit_order(vega, wallet_name, market_id, side, volume, price) -def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str): +def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vol=99, sell_vol=99, custom_price=None): vega.submit_simple_liquidity( key_name=wallet_name, market_id=market_id, @@ -51,7 +52,7 @@ def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str): pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1), wait=False, time_in_force="TIME_IN_FORCE_GTC", - volume=99, + volume=buy_vol, ) vega.submit_order( market_id=market_id, @@ -61,5 +62,5 @@ def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str): pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1), wait=False, time_in_force="TIME_IN_FORCE_GTC", - volume=99, - ) \ No newline at end of file + volume=sell_vol, + ) diff --git a/apps/trading/e2e/fixtures/market.py b/apps/trading/e2e/fixtures/market.py index 244787250..f49a9f255 100644 --- a/apps/trading/e2e/fixtures/market.py +++ b/apps/trading/e2e/fixtures/market.py @@ -8,12 +8,17 @@ logger = logging.getLogger() mint_amount: float = 10e5 market_name = "BTC:DAI_2023" +default_sell_orders = [[1, 110], [1, 105]] +default_buy_orders = [[1, 90], [1, 95]] + + def setup_simple_market( vega: VegaService, approve_proposal=True, custom_market_name=market_name, custom_asset_name="tDAI", custom_asset_symbol="tDAI", + custom_quantum=1 ): for wallet in wallets: vega.create_key(wallet.name) @@ -37,6 +42,7 @@ def setup_simple_market( symbol=custom_asset_symbol, decimals=5, max_faucet_amount=1e10, + quantum=custom_quantum, ) vega.wait_fn(1) vega.wait_for_total_catchup() @@ -111,16 +117,17 @@ def setup_simple_successor_market( return market_id -def setup_opening_auction_market(vega: VegaService, market_id: str = None, **kwargs): - if market_id is None or market_id not in vega.all_markets(): +def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs): + if not market_exists(vega, market_id): market_id = setup_simple_market(vega, **kwargs) - submit_liquidity(vega, MM_WALLET.name, market_id) + if add_liquidity: + submit_liquidity(vega, MM_WALLET.name, market_id) submit_multiple_orders( - vega, MM_WALLET.name, market_id, "SIDE_SELL", [[1, 110], [1, 105]] + vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders ) submit_multiple_orders( - vega, MM_WALLET2.name, market_id, "SIDE_BUY", [[1, 90], [1, 95]] + vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders ) vega.forward("10s") @@ -130,11 +137,22 @@ def setup_opening_auction_market(vega: VegaService, market_id: str = None, **kwa return market_id -def setup_continuous_market(vega: VegaService, market_id: str = None, **kwargs): - if market_id is None or market_id not in vega.all_markets(): - market_id = setup_opening_auction_market(vega, **kwargs) +def market_exists(vega: VegaService, market_id: str): + if market_id is None: + return False + all_markets = vega.all_markets() + market_ids = [market.id for market in all_markets] + return market_id in market_ids - submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110) + +# Add sell orders and buy orders to put on the book +def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs): + if not market_exists(vega, market_id) or buy_orders != default_buy_orders or sell_orders != default_sell_orders: + market_id = setup_opening_auction_market( + vega, market_id, buy_orders, sell_orders, add_liquidity, **kwargs) + + submit_order(vega, "Key 1", market_id, "SIDE_BUY", + sell_orders[0][0], sell_orders[0][1]) vega.forward("10s") vega.wait_fn(1) @@ -142,6 +160,7 @@ def setup_continuous_market(vega: VegaService, market_id: str = None, **kwargs): return market_id + def setup_perps_market( vega: VegaService, custom_asset_name="tDAI", @@ -210,7 +229,7 @@ def setup_perps_market( settlement_data_key=TERMINATE_WALLET.name, funding_payment_frequency_in_seconds=10, market_decimals=5, - ) + ) vega.wait_for_total_catchup() submit_liquidity(vega, MM_WALLET.name, market_id) @@ -225,4 +244,4 @@ def setup_perps_market( vega.wait_fn(1) vega.wait_for_total_catchup() - return market_id \ No newline at end of file + return market_id diff --git a/apps/trading/e2e/tests/referrals/test_referrals.py b/apps/trading/e2e/tests/referrals/test_referrals.py new file mode 100644 index 000000000..33ae3208e --- /dev/null +++ b/apps/trading/e2e/tests/referrals/test_referrals.py @@ -0,0 +1,175 @@ +import pytest +from playwright.sync_api import Page, expect +from vega_sim.service import VegaService +from conftest import init_vega +from fixtures.market import setup_continuous_market, setup_simple_market +from actions.utils import change_keys, create_and_faucet_wallet, forward_time, selector_contains_text +from actions.vega import submit_order, submit_liquidity +from wallet_config import MM_WALLET, PARTY_A, PARTY_B + +SELL_ORDERS = [[1, 111], [1, 111], [1, 112], [1, 112], [ + 1, 113], [1, 113], [1, 114], [1, 114], [1, 115], [1, 115]] +BUY_ORDERS = [[1, 106], [1, 107], [1, 108]] + + +@pytest.fixture(scope="module") +def vega(request): + with init_vega(request) as vega: + yield vega + + +@pytest.fixture(scope="module") +def continuous_market(vega): + market = setup_simple_market(vega, custom_quantum=100000) + return setup_continuous_market(vega, market, BUY_ORDERS, SELL_ORDERS, add_liquidity=False) + + +def generate_referrer_expected_value_dic(expected_base_commission, expected_staking_multiplier, expected_final_commission_rate, expected_volume, expected_num_traders, expected_total_commission): + return { + '[data-testid=my-volume-value]': expected_volume, + '[data-testid=total-commission-value]': expected_total_commission, + '[data-testid=base-commission-rate-value]': expected_base_commission, + '[data-testid=number-of-traders-value]': expected_num_traders, + '[data-testid=final-commission-rate-value]': expected_final_commission_rate, + '[data-testid=staking-multiplier-value]': expected_staking_multiplier + } + + +def generate_referral_expected_value_dic(expected_volume, expected_tier, expected_discount, expected_epochs, expected_epochs_to_next_tier): + return { + '[data-testid=combined-volume-value]': expected_volume, + '[data-testid=current-tier-value]': expected_tier, + '[data-testid=discount-value]': expected_discount, + '[data-testid=epochs-in-set-value]': expected_epochs, + '[data-testid=epochs-to-next-tier-value]': expected_epochs_to_next_tier + } + + +def check_tile_values(page: Page, expected_results: dict): + if "referrals" in page.url: + page.reload() + else: + page.goto("/#/referrals/") + + for selector, expected_text in expected_results.items(): + assert selector_contains_text( + page, selector, expected_text), f"Expected text '{expected_text}' not found in selector '{selector}'" + + +def create_benefit_tier(minimum_running_notional_taker_volume, minimum_epochs, referral_reward_factor, referral_discount_factor): + return { + "minimum_running_notional_taker_volume": minimum_running_notional_taker_volume, + "minimum_epochs": minimum_epochs, + "referral_reward_factor": referral_reward_factor, + "referral_discount_factor": referral_discount_factor, + } + + +def create_staking_tier(minimum_staked_tokens, referral_reward_multiplier): + return { + "minimum_staked_tokens": minimum_staked_tokens, + "referral_reward_multiplier": referral_reward_multiplier, + } + + +def setup_market_and_referral_scheme(vega: VegaService, continuous_market: str, page: Page): + page.goto(f"/#/markets/{continuous_market}") + + create_and_faucet_wallet(vega=vega, wallet=PARTY_A) + create_and_faucet_wallet(vega=vega, wallet=PARTY_B) + forward_time(vega) + + benefit_tiers = [] + staking_tiers = [] + for i in range(1, 4): + benefit_tiers.append(create_benefit_tier( + i * 100, i, i * 0.01, i * 0.01)) + staking_tiers.append(create_staking_tier( + i * 100, i)) + + vega.update_referral_program( + proposal_key=MM_WALLET.name, + benefit_tiers=benefit_tiers, + staking_tiers=staking_tiers, + window_length=1, + ) + forward_time(vega, True) + + vega.create_referral_set(key_name=PARTY_A.name) + forward_time(vega, True) + + referral_set_id = list(vega.list_referral_sets().keys())[0] + vega.apply_referral_code(key_name=PARTY_B.name, id=referral_set_id) + + tdai_id = vega.find_asset_id(symbol="tDAI") + vega.mint( + "Key 1", + asset=tdai_id, + amount=10e6, + ) + vega.mint( + PARTY_B.name, + asset=tdai_id, + amount=10e6, + ) + + submit_liquidity(vega, MM_WALLET.name, continuous_market, 100, 100) + forward_time(vega) + + +@pytest.mark.usefixtures("page", "auth", "risk_accepted") +def test_can_traverse_up_and_down_through_tiers(continuous_market, vega: VegaService, page: Page): + setup_market_and_referral_scheme(vega, continuous_market, page) + change_keys(page, vega, PARTY_B.name) + submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 1, 115) + forward_time(vega, True) + check_tile_values(page, generate_referral_expected_value_dic( + "110", "1", "1%", "1", "1")) + + change_keys(page, vega, PARTY_A.name) + check_tile_values(page, generate_referrer_expected_value_dic( + "1%", "1", "1%", "0", "1", "0")) + + change_keys(page, vega, PARTY_B.name) + submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 2, 115) + forward_time(vega, True) + check_tile_values(page, generate_referral_expected_value_dic( + "221", "2", "2%", "2", "1")) + + change_keys(page, vega, PARTY_A.name) + check_tile_values(page, generate_referrer_expected_value_dic( + "2%", "1", "2%", "0", "1", "0")) + + change_keys(page, vega, PARTY_B.name) + submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 3, 115) + forward_time(vega, True) + check_tile_values(page, generate_referral_expected_value_dic( + "331", "3", "3%", "3", "0")) + + change_keys(page, vega, PARTY_A.name) + check_tile_values(page, generate_referrer_expected_value_dic( + "3%", "1", "3%", "0", "1", "1")) + + change_keys(page, vega, PARTY_B.name) + submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 1, 115) + forward_time(vega, True) + check_tile_values(page, generate_referral_expected_value_dic( + "110", "1", "1%", "4", "0")) + + change_keys(page, vega, PARTY_A.name) + check_tile_values(page, generate_referrer_expected_value_dic( + "1%", "1", "1%", "0", "1", "1")) + + +@pytest.mark.usefixtures("page", "auth", "risk_accepted") +def test_does_not_move_up_tiers_when_not_enough_epochs(continuous_market, vega: VegaService, page: Page): + setup_market_and_referral_scheme(vega, continuous_market, page) + change_keys(page, vega, PARTY_B.name) + submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 2, 115) + forward_time(vega, True) + check_tile_values(page, generate_referral_expected_value_dic( + "221", "1", "1%", "1", "1")) + + change_keys(page, vega, PARTY_A.name) + check_tile_values(page, generate_referrer_expected_value_dic( + "2%", "1", "2%", "0", "1", "0")) diff --git a/apps/trading/e2e/wallet_config.py b/apps/trading/e2e/wallet_config.py index f85dd8374..c078666bf 100644 --- a/apps/trading/e2e/wallet_config.py +++ b/apps/trading/e2e/wallet_config.py @@ -7,6 +7,9 @@ WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"]) MM_WALLET = WalletConfig("market_maker", "pin") MM_WALLET2 = WalletConfig("market_maker_2", "pin2") TERMINATE_WALLET = WalletConfig("FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs") -GOVERNANCE_WALLET = WalletConfig("FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs") +GOVERNANCE_WALLET = WalletConfig( + "FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs") +PARTY_A = WalletConfig("party_a", "party_a") +PARTY_B = WalletConfig("party_b", "party_b") wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET]