diff --git a/rs/ethereum/cketh/minter/src/dashboard.rs b/rs/ethereum/cketh/minter/src/dashboard.rs index 2312578f3bb..e6e7971c354 100644 --- a/rs/ethereum/cketh/minter/src/dashboard.rs +++ b/rs/ethereum/cketh/minter/src/dashboard.rs @@ -245,9 +245,12 @@ impl DashboardTablePagination { let pages = (0..num_items) .step_by(page_size) .enumerate() - .map(|(index, offset)| DashboardTablePage { index, offset }) + .map(|(index, offset)| DashboardTablePage { + index: index + 1, + offset, + }) .collect(); - let current_page_index = current_offset / page_size; + let current_page_index = current_offset / page_size + 1; Self { table_id: String::from(table_reference), page_offset_query_param: String::from(page_offset_query_param), diff --git a/rs/ethereum/cketh/minter/src/dashboard/tests.rs b/rs/ethereum/cketh/minter/src/dashboard/tests.rs index 2bb84730ae6..0c5e9927052 100644 --- a/rs/ethereum/cketh/minter/src/dashboard/tests.rs +++ b/rs/ethereum/cketh/minter/src/dashboard/tests.rs @@ -17,7 +17,7 @@ use ic_cketh_minter::state::transactions::{ create_transaction, Erc20WithdrawalRequest, EthWithdrawalRequest, ReimbursementIndex, WithdrawalRequest, }; -use ic_cketh_minter::state::State; +use ic_cketh_minter::state::{MintedEvent, State}; use ic_cketh_minter::tx::{ Eip1559Signature, Eip1559TransactionRequest, GasFeeEstimate, SignedEip1559TransactionRequest, TransactionPrice, @@ -1001,6 +1001,159 @@ fn should_display_reimbursed_requests() { ); } +#[test] +fn should_display_minted_events_pagination() { + let dashboard = { + let mut state = initial_state(); + add_minted_events(&mut state, 300); + let paging_parameters = DashboardPagingParameters { + minted_events_start: 100, // Second page. + ..DashboardPagingParameters::default() + }; + DashboardTemplate::from_state(&state, paging_parameters) + }; + + DashboardAssert::assert_that(dashboard) + .has_minted_events( + 1, + &vec![ + "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", + "200", // Events are displayed in order of decreasing index. Page 2 should have events 200 to 101. + "0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", + "ckETH", + "10_000_000_000_000_000", + "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", + "1", + ], + ) + .has_minted_events_last_row_text(&vec!["Pages:", "1", "2", "3"]) + .has_minted_events_last_row_links(&vec![ + "?minted_events_start=0#minted-events", + "?minted_events_start=200#minted-events", + ]); +} + +#[test] +fn should_not_display_minted_events_pagination() { + let dashboard = { + let mut state = initial_state(); + add_minted_events(&mut state, 75); // less than 1 full page + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + }; + + DashboardAssert::assert_that(dashboard).has_minted_events_last_row_text(&vec![ + "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", + "1", + "0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", + "ckETH", + "10_000_000_000_000_000", + "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", + "1", + ]); +} + +#[test] +fn should_not_display_finalized_transactions_pagination() { + let dashboard = { + let mut state = initial_state(); + add_finalized_transactions(&mut state, 75); // less than 1 full page + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + }; + + DashboardAssert::assert_that(dashboard).has_finalized_transactions_last_row_text(&vec![ + "1", + "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", + "ckSepoliaETH", + "1_058_000_000_000_000", + "21_000_000_000_000", + "4190269", + "0xdea6b45f0978fea7f38fe6957db7ee11dd0e351a6f24fe54598d8aec9c8a1527", + "Success", + ]); +} + +#[test] +fn should_display_finalized_transactions_pagination() { + let dashboard = { + let mut state = initial_state(); + add_finalized_transactions(&mut state, 300); + let paging_parameters = DashboardPagingParameters { + finalized_transactions_start: 100, // Second page. + ..DashboardPagingParameters::default() + }; + DashboardTemplate::from_state(&state, paging_parameters) + }; + + DashboardAssert::assert_that(dashboard) + .has_finalized_transactions( + 1, + &vec![ + "200", // Transactions are displayed in order of decreasing index. Page 2 should have transactions 200 to 101. + "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", + "ckSepoliaETH", + "1_058_000_000_000_000", + "21_000_000_000_000", + "4190269", + "0x399ba1d76e66175b7fd092050c77b036e0297d36c96d6e06a25c6205b8168774", + "Success", + ], + ) + .has_finalized_transactions_last_row_text(&vec!["Pages:", "1", "2", "3"]) + .has_finalized_transactions_last_row_links(&vec![ + "?finalized_transactions_start=0#finalized-transactions", + "?finalized_transactions_start=200#finalized-transactions", + ]); +} + +#[test] +fn should_not_display_reimbursed_transactions_pagination() { + let dashboard = { + let mut state = initial_state(); + add_reimbursed_transactions(&mut state, 75); // less than 1 full page + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + }; + + DashboardAssert::assert_that(dashboard).has_reimbursed_transactions_last_row_text(&vec![ + "1", + "123", + "ckSepoliaETH", + "1_058_000_000_000_000", + "0xdea6b45f0978fea7f38fe6957db7ee11dd0e351a6f24fe54598d8aec9c8a1527", + "Reimbursed", + ]); +} + +#[test] +fn should_display_reimbursed_transactions_pagination() { + let dashboard = { + let mut state = initial_state(); + add_reimbursed_transactions(&mut state, 300); + let paging_parameters = DashboardPagingParameters { + reimbursed_transactions_start: 100, // Second page. + ..DashboardPagingParameters::default() + }; + DashboardTemplate::from_state(&state, paging_parameters) + }; + + DashboardAssert::assert_that(dashboard) + .has_reimbursed_transactions( + 1, + &vec![ + "200", // Transactions are displayed in order of decreasing index. Page 2 should have transactions 200 to 101. + "123", + "ckSepoliaETH", + "1_058_000_000_000_000", + "0x399ba1d76e66175b7fd092050c77b036e0297d36c96d6e06a25c6205b8168774", + "Reimbursed", + ], + ) + .has_reimbursed_transactions_last_row_text(&vec!["Pages:", "1", "2", "3"]) + .has_reimbursed_transactions_last_row_links(&vec![ + "?reimbursed_transactions_start=0#reimbursed-transactions", + "?reimbursed_transactions_start=200#reimbursed-transactions", + ]); +} + fn initial_dashboard() -> DashboardTemplate { DashboardTemplate::from_state(&initial_state(), DashboardPagingParameters::default()) } @@ -1030,6 +1183,142 @@ fn initial_state_with_usdc_support() -> State { state } +fn add_minted_events(state: &mut State, num_events: u128) { + (1..=num_events).for_each(|index| { + state + .minted_events + .insert(event_source(index), minted_event(index)); + }); +} + +fn minted_event(index: u128) -> MintedEvent { + MintedEvent { + deposit_event: ReceivedEthEvent { + log_index: LogIndex::new(index), + ..received_eth_event() + } + .into(), + mint_block_index: LedgerMintIndex::new(1u64), + token_symbol: "ckETH".to_string(), + erc20_contract_address: None, + } +} + +fn event_source(index: u128) -> EventSource { + EventSource { + transaction_hash: "0x05c6ec45699c9a6a4b1a4ea2058b0cee852ea2f19b18fb8313c04bf8156efde4" + .parse() + .unwrap(), + log_index: LogIndex::new(index), + } +} + +fn add_finalized_transactions(state: &mut State, num_transactions: u64) { + let deposit = ReceivedEthEvent { + //enough for withdrawals + value: Wei::from(1_000_000_000_000_000_000_u128), + ..received_eth_event() + }; + apply_state_transition(state, &EventType::AcceptedDeposit(deposit.clone())); + apply_state_transition( + state, + &EventType::MintedCkEth { + event_source: deposit.source(), + mint_block_index: LedgerMintIndex::new(42), + }, + ); + for index in 0..num_transactions { + let (req, tx, signed_tx, receipt) = cketh_withdrawal_flow( + LedgerBurnIndex::new(index + 1), + TransactionNonce::from(index), + TransactionStatus::Success, + ); + let id = req.cketh_ledger_burn_index(); + apply_state_transition(state, &req.into_accepted_withdrawal_request_event()); + apply_state_transition( + state, + &EventType::CreatedTransaction { + withdrawal_id: id, + transaction: tx, + }, + ); + apply_state_transition( + state, + &EventType::SignedTransaction { + withdrawal_id: id, + transaction: signed_tx, + }, + ); + apply_state_transition( + state, + &EventType::FinalizedTransaction { + withdrawal_id: id, + transaction_receipt: receipt, + }, + ); + } +} + +fn add_reimbursed_transactions(state: &mut State, num_transactions: u64) { + use ic_cketh_minter::state::transactions::Reimbursed; + + let reimbursed_in_block = LedgerMintIndex::new(123); + let reimbursed_amount = CkTokenAmount::new(100_102); + + let deposit = ReceivedEthEvent { + //enough for withdrawals + value: Wei::from(1_000_000_000_000_000_000_u128), + ..received_eth_event() + }; + apply_state_transition(state, &EventType::AcceptedDeposit(deposit.clone())); + apply_state_transition( + state, + &EventType::MintedCkEth { + event_source: deposit.source(), + mint_block_index: LedgerMintIndex::new(42), + }, + ); + for index in 0..num_transactions { + let (req, tx, signed_tx, receipt) = cketh_withdrawal_flow( + LedgerBurnIndex::new(index + 1), + TransactionNonce::from(index), + TransactionStatus::Failure, + ); + let id = req.cketh_ledger_burn_index(); + apply_state_transition(state, &req.into_accepted_withdrawal_request_event()); + apply_state_transition( + state, + &EventType::CreatedTransaction { + withdrawal_id: id, + transaction: tx, + }, + ); + apply_state_transition( + state, + &EventType::SignedTransaction { + withdrawal_id: id, + transaction: signed_tx, + }, + ); + apply_state_transition( + state, + &EventType::FinalizedTransaction { + withdrawal_id: id, + transaction_receipt: receipt.clone(), + }, + ); + apply_state_transition( + state, + &EventType::ReimbursedEthWithdrawal(Reimbursed { + transaction_hash: Some(receipt.transaction_hash), + burn_in_block: id, + reimbursed_in_block, + reimbursed_amount, + }), + ); + } +} + fn received_eth_event() -> ReceivedEthEvent { ReceivedEthEvent { transaction_hash: "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2" @@ -1483,6 +1772,22 @@ mod assertions { ) } + pub fn has_minted_events_last_row_text(&self, expected_value: &Vec<&str>) -> &Self { + self.has_table_row_string_value( + "#minted-events + table > tbody > tr:last-child", + expected_value, + "minted-events", + ) + } + + pub fn has_minted_events_last_row_links(&self, expected_value: &Vec<&str>) -> &Self { + self.has_table_row_links( + "#minted-events + table > tbody > tr:last-child", + expected_value, + "minted-events", + ) + } + pub fn has_rejected_deposits(&self, row_index: u8, expected_value: &Vec<&str>) -> &Self { self.has_table_row_string_value( &format!("#rejected-deposits + table > tbody > tr:nth-child({row_index})"), @@ -1519,6 +1824,28 @@ mod assertions { ) } + pub fn has_finalized_transactions_last_row_text( + &self, + expected_value: &Vec<&str>, + ) -> &Self { + self.has_table_row_string_value( + "#finalized-transactions + table > tbody > tr:last-child", + expected_value, + "finalized-transactions", + ) + } + + pub fn has_finalized_transactions_last_row_links( + &self, + expected_value: &Vec<&str>, + ) -> &Self { + self.has_table_row_links( + "#finalized-transactions + table > tbody > tr:last-child", + expected_value, + "finalized-transactions", + ) + } + pub fn has_reimbursed_transactions( &self, row_index: u8, @@ -1531,6 +1858,28 @@ mod assertions { ) } + pub fn has_reimbursed_transactions_last_row_text( + &self, + expected_value: &Vec<&str>, + ) -> &Self { + self.has_table_row_string_value( + "#reimbursed-transactions + table > tbody > tr:last-child", + expected_value, + "reimbursed-transactions", + ) + } + + pub fn has_reimbursed_transactions_last_row_links( + &self, + expected_value: &Vec<&str>, + ) -> &Self { + self.has_table_row_links( + "#reimbursed-transactions + table > tbody > tr:last-child", + expected_value, + "reimbursed-transactions", + ) + } + fn has_table_row_string_value( &self, selector: &str, @@ -1551,6 +1900,25 @@ mod assertions { self } + fn has_table_row_links( + &self, + selector: &str, + expected_value: &Vec<&str>, + error_msg: &str, + ) -> &Self { + let links = self + .select_only_one(selector) + .select(&Selector::parse("a").unwrap()) + .map(|link| link.value().attr("href").expect("href not found")) + .collect::>(); + assert_eq!( + &links, expected_value, + "{}. Rendered html: {}", + error_msg, self.rendered_html + ); + self + } + fn has_string_value(&self, selector: &str, expected_value: &str, error_msg: &str) -> &Self { let actual_value = self.select_only_one(selector); let string_value = actual_value.text().collect::(); diff --git a/rs/ethereum/cketh/minter/templates/dashboard.html b/rs/ethereum/cketh/minter/templates/dashboard.html index cbafec33df3..0911bf6cae0 100644 --- a/rs/ethereum/cketh/minter/templates/dashboard.html +++ b/rs/ethereum/cketh/minter/templates/dashboard.html @@ -281,7 +281,7 @@

Minted events

{{ event.mint_block_index }} {% endfor %} - {% if table.has_more_than_one_page() %}}{{ table.pagination }}{% endif %} + {% if table.has_more_than_one_page() %}}{{ table.pagination|safe }}{% endif %} {% endif %} @@ -389,7 +389,7 @@

Finalized Transactions ckETH → ETH and ckERC20 {{ tx.status }} {% endfor %} - {% if table.has_more_than_one_page() %}}{{ table.pagination }}{% endif %} + {% if table.has_more_than_one_page() %}}{{ table.pagination|safe }}{% endif %} {% endif %} @@ -431,7 +431,7 @@

Reimbursed Transactions

{% endmatch %} {% endfor %} - {% if table.has_more_than_one_page() %}}{{ table.pagination }}{% endif %} + {% if table.has_more_than_one_page() %}}{{ table.pagination|safe }}{% endif %} {% endif %} diff --git a/rs/ethereum/cketh/minter/templates/pagination.html b/rs/ethereum/cketh/minter/templates/pagination.html index b23a0a0b8b1..3f9fe543fc7 100644 --- a/rs/ethereum/cketh/minter/templates/pagination.html +++ b/rs/ethereum/cketh/minter/templates/pagination.html @@ -2,7 +2,7 @@ Pages: {% for page in pages %} - {% if page.index != current_page_index %} + {% if page.index == current_page_index %} {{ page.index }}  {% else %} {{ page.index }}