From d5c0efb7a58693f3e3fb99b63b2f826725a87c15 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Mon, 9 Dec 2024 13:42:50 +0100 Subject: [PATCH 1/5] XC-240: Parse pagination query parameters --- rs/ethereum/cketh/minter/src/dashboard.rs | 49 ++++++++++++++++++- .../cketh/minter/src/dashboard/tests.rs | 22 ++++----- rs/ethereum/cketh/minter/src/main.rs | 13 ++++- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/rs/ethereum/cketh/minter/src/dashboard.rs b/rs/ethereum/cketh/minter/src/dashboard.rs index 632f9108529..aae315f9905 100644 --- a/rs/ethereum/cketh/minter/src/dashboard.rs +++ b/rs/ethereum/cketh/minter/src/dashboard.rs @@ -3,6 +3,7 @@ mod tests; use askama::Template; use candid::{Nat, Principal}; +use ic_canisters_http_types::HttpRequest; use ic_cketh_minter::endpoints::{EthTransaction, RetrieveEthStatus}; use ic_cketh_minter::erc20::CkTokenSymbol; use ic_cketh_minter::eth_logs::{EventSource, ReceivedEvent}; @@ -22,6 +23,7 @@ use ic_ethereum_types::Address; use icrc_ledger_types::icrc1::account::Account; use std::cmp::Reverse; use std::collections::{BTreeMap, BTreeSet}; +use std::str::FromStr; mod filters { pub fn timestamp_to_datetime(timestamp: T) -> askama::Result { @@ -145,6 +147,36 @@ impl DashboardPendingDeposit { } } +// Number of entries per page in dashboard tables (e.g. minted events, finalized transactions). +const DEFAULT_PAGE_SIZE: usize = 100; + +#[derive(Default)] +pub struct DashboardPagingParameters { + minted_events_start: usize, + finalized_transactions_start: usize, + reimbursed_transactions_start: usize, +} + +impl DashboardPagingParameters { + pub(crate) fn from_query_params( + req: &HttpRequest, + ) -> Result { + fn parse_query_param(req: &HttpRequest, param_name: &str) -> Result { + Ok(match req.raw_query_param(param_name) { + Some(arg) => usize::from_str(arg) + .map_err(|_| format!("failed to parse the '{}' parameter", param_name))?, + None => 0, + }) + } + + Ok(Self { + minted_events_start: parse_query_param(req, "minted_events_start")?, + finalized_transactions_start: parse_query_param(req, "finalized_transactions_start")?, + reimbursed_transactions_start: parse_query_param(req, "reimbursed_transactions_start")?, + }) + } +} + #[derive(Template)] #[template(path = "dashboard.html")] #[derive(Clone)] @@ -171,12 +203,17 @@ pub struct DashboardTemplate { } impl DashboardTemplate { - pub fn from_state(state: &State) -> Self { + pub fn from_state(state: &State, args: DashboardPagingParameters) -> Self { let mut minted_events: Vec<_> = state.minted_events.values().cloned().collect(); minted_events.sort_unstable_by_key(|event| { let deposit_event = &event.deposit_event; Reverse((deposit_event.block_number(), deposit_event.log_index())) }); + minted_events = minted_events + .into_iter() + .skip(args.minted_events_start) + .take(DEFAULT_PAGE_SIZE) + .collect(); let mut supported_ckerc20_tokens: Vec<_> = state .supported_ck_erc20_tokens() @@ -278,6 +315,11 @@ impl DashboardTemplate { }) .collect(); finalized_transactions.sort_unstable_by_key(|tx| Reverse(tx.ledger_burn_index)); + finalized_transactions = finalized_transactions + .into_iter() + .skip(args.finalized_transactions_start) + .take(DEFAULT_PAGE_SIZE) + .collect(); let mut reimbursed_transactions: Vec<_> = state .eth_transactions @@ -320,6 +362,11 @@ impl DashboardTemplate { .collect(); reimbursed_transactions .sort_unstable_by_key(|reimbursed_tx| Reverse(reimbursed_tx.cketh_ledger_burn_index())); + reimbursed_transactions = reimbursed_transactions + .into_iter() + .skip(args.reimbursed_transactions_start) + .take(DEFAULT_PAGE_SIZE) + .collect(); DashboardTemplate { ethereum_network: state.ethereum_network, diff --git a/rs/ethereum/cketh/minter/src/dashboard/tests.rs b/rs/ethereum/cketh/minter/src/dashboard/tests.rs index c69193e6c8e..2bb84730ae6 100644 --- a/rs/ethereum/cketh/minter/src/dashboard/tests.rs +++ b/rs/ethereum/cketh/minter/src/dashboard/tests.rs @@ -1,5 +1,5 @@ use crate::dashboard::tests::assertions::DashboardAssert; -use crate::dashboard::DashboardTemplate; +use crate::dashboard::{DashboardPagingParameters, DashboardTemplate}; use crate::erc20::CkErc20Token; use candid::{Nat, Principal}; use ic_cketh_minter::eth_logs::{ @@ -164,7 +164,7 @@ fn should_display_supported_erc20_tokens() { state.ethereum_network = EthereumNetwork::Mainnet; state.record_add_ckerc20_token(usdc.clone()); state.record_add_ckerc20_token(usdt.clone()); - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -213,7 +213,7 @@ fn should_display_pending_deposits_sorted_by_decreasing_block_number() { apply_state_transition(&mut state, &EventType::AcceptedDeposit(event_1)); apply_state_transition(&mut state, &EventType::AcceptedDeposit(event_2)); apply_state_transition(&mut state, &EventType::AcceptedErc20Deposit(event_3)); - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -311,7 +311,7 @@ fn should_display_minted_events_sorted_by_decreasing_mint_block_index() { mint_block_index: LedgerMintIndex::new(44), }, ); - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -386,7 +386,7 @@ fn should_display_rejected_deposits() { reason: "failed to decode principal".to_string(), }, ); - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -423,7 +423,7 @@ fn should_display_correct_cketh_token_symbol_based_on_network() { LedgerBurnIndex::new(15), )), ); - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard).has_withdrawal_requests( 1, @@ -467,7 +467,7 @@ fn should_display_withdrawal_requests_sorted_by_decreasing_cketh_ledger_burn_ind ..cketh_withdrawal_request_with_index(LedgerBurnIndex::new(17)) }), ); - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -567,7 +567,7 @@ fn should_display_pending_transactions_sorted_by_decreasing_cketh_ledger_burn_in ); } - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -688,7 +688,7 @@ fn should_display_finalized_transactions_sorted_by_decreasing_cketh_ledger_burn_ ); } - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -895,7 +895,7 @@ fn should_display_reimbursed_requests() { } } } - DashboardTemplate::from_state(&state) + DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) }; // Check that we show latest first. @@ -1002,7 +1002,7 @@ fn should_display_reimbursed_requests() { } fn initial_dashboard() -> DashboardTemplate { - DashboardTemplate::from_state(&initial_state()) + DashboardTemplate::from_state(&initial_state(), DashboardPagingParameters::default()) } const INITIAL_LAST_SCRAPED_BLOCK_NUMBER: u32 = 3_956_206_u32; diff --git a/rs/ethereum/cketh/minter/src/main.rs b/rs/ethereum/cketh/minter/src/main.rs index 32bb99986f9..e073184bd30 100644 --- a/rs/ethereum/cketh/minter/src/main.rs +++ b/rs/ethereum/cketh/minter/src/main.rs @@ -1,4 +1,6 @@ +use crate::dashboard::DashboardPagingParameters; use candid::Nat; +use dashboard::DashboardTemplate; use ic_canister_log::log; use ic_canisters_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder}; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; @@ -1032,7 +1034,16 @@ fn http_request(req: HttpRequest) -> HttpResponse { } } else if req.path() == "/dashboard" { use askama::Template; - let dashboard = read_state(dashboard::DashboardTemplate::from_state); + + let paging_parameters = match DashboardPagingParameters::from_query_params(&req) { + Ok(args) => args, + Err(error) => { + return HttpResponseBuilder::bad_request() + .with_body_and_content_length(error) + .build() + } + }; + let dashboard = read_state(|state| DashboardTemplate::from_state(state, paging_parameters)); HttpResponseBuilder::ok() .header("Content-Type", "text/html; charset=utf-8") .with_body_and_content_length(dashboard.render().unwrap()) From 1d4b2d457192fe2292f1c433a91b60c7c386b645 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Mon, 9 Dec 2024 15:58:53 +0100 Subject: [PATCH 2/5] XC-240: Paginate tables in ckETH dashboard --- rs/ethereum/cketh/minter/BUILD.bazel | 1 + rs/ethereum/cketh/minter/src/dashboard.rs | 102 +++++++++++++++--- .../cketh/minter/templates/dashboard.html | 12 ++- .../cketh/minter/templates/pagination.html | 12 +++ 4 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 rs/ethereum/cketh/minter/templates/pagination.html diff --git a/rs/ethereum/cketh/minter/BUILD.bazel b/rs/ethereum/cketh/minter/BUILD.bazel index a1ff214df31..4793b5f7a8b 100644 --- a/rs/ethereum/cketh/minter/BUILD.bazel +++ b/rs/ethereum/cketh/minter/BUILD.bazel @@ -133,6 +133,7 @@ rust_test( srcs = glob(["src/**/*.rs"]), compile_data = [ "templates/dashboard.html", + "templates/pagination.html", "templates/principal_to_bytes.js", ], crate_features = features, diff --git a/rs/ethereum/cketh/minter/src/dashboard.rs b/rs/ethereum/cketh/minter/src/dashboard.rs index aae315f9905..2312578f3bb 100644 --- a/rs/ethereum/cketh/minter/src/dashboard.rs +++ b/rs/ethereum/cketh/minter/src/dashboard.rs @@ -150,7 +150,7 @@ impl DashboardPendingDeposit { // Number of entries per page in dashboard tables (e.g. minted events, finalized transactions). const DEFAULT_PAGE_SIZE: usize = 100; -#[derive(Default)] +#[derive(Default, Clone)] pub struct DashboardPagingParameters { minted_events_start: usize, finalized_transactions_start: usize, @@ -177,6 +177,87 @@ impl DashboardPagingParameters { } } +#[derive(Clone)] +pub struct DashboardPaginatedTable { + current_page: Vec, + pagination: DashboardTablePagination, +} + +impl DashboardPaginatedTable { + pub fn from_items( + items: &[T], + current_page_offset: &usize, + page_size: usize, + num_cols: usize, + table_reference: &str, + page_offset_query_param: &str, + ) -> Self { + let current_page_offset = *current_page_offset; + Self { + current_page: items + .iter() + .skip(current_page_offset) + .take(page_size) + .cloned() + .collect(), + pagination: DashboardTablePagination::pagination( + items.len(), + current_page_offset, + page_size, + num_cols, + table_reference, + page_offset_query_param, + ), + } + } + + pub fn has_more_than_one_page(&self) -> bool { + self.pagination.pages.len() > 1 + } +} + +#[derive(Clone)] +pub struct DashboardTablePage { + index: usize, + offset: usize, +} + +#[derive(Template)] +#[template(path = "pagination.html")] +#[derive(Clone)] +pub struct DashboardTablePagination { + pub table_id: String, + pub table_width: usize, + pub page_offset_query_param: String, + pub current_page_index: usize, + pub pages: Vec, +} + +impl DashboardTablePagination { + fn pagination( + num_items: usize, + current_offset: usize, + page_size: usize, + table_width: usize, + table_reference: &str, + page_offset_query_param: &str, + ) -> Self { + let pages = (0..num_items) + .step_by(page_size) + .enumerate() + .map(|(index, offset)| DashboardTablePage { index, offset }) + .collect(); + let current_page_index = current_offset / page_size; + Self { + table_id: String::from(table_reference), + page_offset_query_param: String::from(page_offset_query_param), + table_width, + current_page_index, + pages, + } + } +} + #[derive(Template)] #[template(path = "dashboard.html")] #[derive(Clone)] @@ -200,20 +281,16 @@ pub struct DashboardTemplate { pub eth_balance: EthBalance, pub skipped_blocks: BTreeMap>, pub supported_ckerc20_tokens: Vec, + pub pagination_parameters: DashboardPagingParameters, } impl DashboardTemplate { - pub fn from_state(state: &State, args: DashboardPagingParameters) -> Self { + pub fn from_state(state: &State, pagination_parameters: DashboardPagingParameters) -> Self { let mut minted_events: Vec<_> = state.minted_events.values().cloned().collect(); minted_events.sort_unstable_by_key(|event| { let deposit_event = &event.deposit_event; Reverse((deposit_event.block_number(), deposit_event.log_index())) }); - minted_events = minted_events - .into_iter() - .skip(args.minted_events_start) - .take(DEFAULT_PAGE_SIZE) - .collect(); let mut supported_ckerc20_tokens: Vec<_> = state .supported_ck_erc20_tokens() @@ -315,11 +392,6 @@ impl DashboardTemplate { }) .collect(); finalized_transactions.sort_unstable_by_key(|tx| Reverse(tx.ledger_burn_index)); - finalized_transactions = finalized_transactions - .into_iter() - .skip(args.finalized_transactions_start) - .take(DEFAULT_PAGE_SIZE) - .collect(); let mut reimbursed_transactions: Vec<_> = state .eth_transactions @@ -362,11 +434,6 @@ impl DashboardTemplate { .collect(); reimbursed_transactions .sort_unstable_by_key(|reimbursed_tx| Reverse(reimbursed_tx.cketh_ledger_burn_index())); - reimbursed_transactions = reimbursed_transactions - .into_iter() - .skip(args.reimbursed_transactions_start) - .take(DEFAULT_PAGE_SIZE) - .collect(); DashboardTemplate { ethereum_network: state.ethereum_network, @@ -395,6 +462,7 @@ impl DashboardTemplate { .map(|(contract_address, blocks)| (contract_address.to_string(), blocks.clone())) .collect(), supported_ckerc20_tokens, + pagination_parameters, } } } diff --git a/rs/ethereum/cketh/minter/templates/dashboard.html b/rs/ethereum/cketh/minter/templates/dashboard.html index 3a9c8df3abb..cbafec33df3 100644 --- a/rs/ethereum/cketh/minter/templates/dashboard.html +++ b/rs/ethereum/cketh/minter/templates/dashboard.html @@ -269,7 +269,8 @@

Minted events

- {% for event in minted_events %} + {% let table = DashboardPaginatedTable::from_items(minted_events, pagination_parameters.minted_events_start, DEFAULT_PAGE_SIZE, 7, "minted-events", "minted_events_start") %} + {% for event in table.current_page %} {% call etherscan_tx_link(event.deposit_event.transaction_hash().to_string()) %} {{ event.deposit_event.log_index() }} @@ -280,6 +281,7 @@

Minted events

{{ event.mint_block_index }} {% endfor %} + {% if table.has_more_than_one_page() %}}{{ table.pagination }}{% endif %} {% endif %} @@ -374,7 +376,8 @@

Finalized Transactions ckETH → ETH and ckERC20 - {% for tx in finalized_transactions %} + {% let table = DashboardPaginatedTable::from_items(finalized_transactions, pagination_parameters.finalized_transactions_start, DEFAULT_PAGE_SIZE, 8, "finalized-transactions", "finalized_transactions_start") %} + {% for tx in table.current_page %} {{ tx.ledger_burn_index }} {% call etherscan_address_link(tx.destination) %} @@ -386,6 +389,7 @@

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

Reimbursed Transactions

- {% for r in reimbursed_transactions %} + {% let table = DashboardPaginatedTable::from_items(reimbursed_transactions, pagination_parameters.reimbursed_transactions_start, DEFAULT_PAGE_SIZE, 6, "reimbursed-transactions", "reimbursed_transactions_start") %} + {% for r in table.current_page %} {% match r %} {% when DashboardReimbursedTransaction::Reimbursed with {cketh_ledger_burn_index, reimbursed_in_block, reimbursed_amount, token_symbol, transaction_hash} %} @@ -426,6 +431,7 @@

Reimbursed Transactions

{% endmatch %} {% endfor %} + {% if table.has_more_than_one_page() %}}{{ table.pagination }}{% endif %} {% endif %} diff --git a/rs/ethereum/cketh/minter/templates/pagination.html b/rs/ethereum/cketh/minter/templates/pagination.html new file mode 100644 index 00000000000..b23a0a0b8b1 --- /dev/null +++ b/rs/ethereum/cketh/minter/templates/pagination.html @@ -0,0 +1,12 @@ + + + Pages: + {% for page in pages %} + {% if page.index != current_page_index %} + {{ page.index }}  + {% else %} + {{ page.index }}  + {% endif %} + {% endfor %} + + From 0021a30e0e91dcc9d3cd484938d31253068bcedc Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Dec 2024 17:31:12 +0100 Subject: [PATCH 3/5] XC-240: Add unit tests for pagination --- rs/ethereum/cketh/minter/src/dashboard.rs | 7 +- .../cketh/minter/src/dashboard/tests.rs | 370 +++++++++++++++++- .../cketh/minter/templates/dashboard.html | 6 +- .../cketh/minter/templates/pagination.html | 2 +- 4 files changed, 378 insertions(+), 7 deletions(-) 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 }}  From 496e5d01cc8101aa246221bb04a5d24951dc353c Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Thu, 12 Dec 2024 14:22:30 +0100 Subject: [PATCH 4/5] XC-240: Remove spurious curly brace --- .../cketh/minter/src/dashboard/tests.rs | 45 +++++++++++++++++-- .../cketh/minter/templates/dashboard.html | 6 +-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/rs/ethereum/cketh/minter/src/dashboard/tests.rs b/rs/ethereum/cketh/minter/src/dashboard/tests.rs index 0c5e9927052..4f40173ea30 100644 --- a/rs/ethereum/cketh/minter/src/dashboard/tests.rs +++ b/rs/ethereum/cketh/minter/src/dashboard/tests.rs @@ -1013,12 +1013,25 @@ fn should_display_minted_events_pagination() { DashboardTemplate::from_state(&state, paging_parameters) }; + // Events are displayed in order of decreasing index. Page 2 should have events 200 to 101. 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. + "200", + "0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", + "ckETH", + "10_000_000_000_000_000", + "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", + "1", + ], + ) + .has_minted_events( + 100, + &vec![ + "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", + "101", "0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", "ckETH", "10_000_000_000_000_000", @@ -1084,11 +1097,12 @@ fn should_display_finalized_transactions_pagination() { DashboardTemplate::from_state(&state, paging_parameters) }; + // Transactions are displayed in order of decreasing index. Page 2 should have transactions 200 to 101. 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. + "200", "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", "ckSepoliaETH", "1_058_000_000_000_000", @@ -1098,6 +1112,19 @@ fn should_display_finalized_transactions_pagination() { "Success", ], ) + .has_finalized_transactions( + 100, + &vec![ + "101", + "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", + "ckSepoliaETH", + "1_058_000_000_000_000", + "21_000_000_000_000", + "4190269", + "0x5a7f0423ddcbaf9429bdc18e793fa8b61f56357fb34452eae15e9396d1becd40", + "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", @@ -1135,11 +1162,12 @@ fn should_display_reimbursed_transactions_pagination() { DashboardTemplate::from_state(&state, paging_parameters) }; + // Transactions are displayed in order of decreasing index. Page 2 should have transactions 200 to 101. 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. + "200", "123", "ckSepoliaETH", "1_058_000_000_000_000", @@ -1147,6 +1175,17 @@ fn should_display_reimbursed_transactions_pagination() { "Reimbursed", ], ) + .has_reimbursed_transactions( + 100, + &vec![ + "101", + "123", + "ckSepoliaETH", + "1_058_000_000_000_000", + "0x5a7f0423ddcbaf9429bdc18e793fa8b61f56357fb34452eae15e9396d1becd40", + "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", diff --git a/rs/ethereum/cketh/minter/templates/dashboard.html b/rs/ethereum/cketh/minter/templates/dashboard.html index 0911bf6cae0..7c96a7358b5 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|safe }}{% 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|safe }}{% 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|safe }}{% endif %} + {% if table.has_more_than_one_page() %}{{ table.pagination|safe }}{% endif %} {% endif %} From 226dfa713de75ebd2a372037ab4116560c498de3 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Fri, 13 Dec 2024 12:13:14 +0100 Subject: [PATCH 5/5] XC-240: Incorporate review feedback --- rs/ethereum/cketh/minter/src/dashboard.rs | 58 ++++-- .../cketh/minter/src/dashboard/tests.rs | 188 +++++++++--------- rs/ethereum/cketh/minter/src/main.rs | 4 +- .../cketh/minter/templates/dashboard.html | 21 +- 4 files changed, 147 insertions(+), 124 deletions(-) diff --git a/rs/ethereum/cketh/minter/src/dashboard.rs b/rs/ethereum/cketh/minter/src/dashboard.rs index e6e7971c354..a6f17d04c7a 100644 --- a/rs/ethereum/cketh/minter/src/dashboard.rs +++ b/rs/ethereum/cketh/minter/src/dashboard.rs @@ -151,16 +151,16 @@ impl DashboardPendingDeposit { const DEFAULT_PAGE_SIZE: usize = 100; #[derive(Default, Clone)] -pub struct DashboardPagingParameters { +pub struct DashboardPaginationParameters { minted_events_start: usize, finalized_transactions_start: usize, reimbursed_transactions_start: usize, } -impl DashboardPagingParameters { +impl DashboardPaginationParameters { pub(crate) fn from_query_params( req: &HttpRequest, - ) -> Result { + ) -> Result { fn parse_query_param(req: &HttpRequest, param_name: &str) -> Result { Ok(match req.raw_query_param(param_name) { Some(arg) => usize::from_str(arg) @@ -178,7 +178,7 @@ impl DashboardPagingParameters { } #[derive(Clone)] -pub struct DashboardPaginatedTable { +pub struct DashboardPaginatedTable { current_page: Vec, pagination: DashboardTablePagination, } @@ -186,13 +186,12 @@ pub struct DashboardPaginatedTable { impl DashboardPaginatedTable { pub fn from_items( items: &[T], - current_page_offset: &usize, + current_page_offset: usize, page_size: usize, num_cols: usize, table_reference: &str, page_offset_query_param: &str, ) -> Self { - let current_page_offset = *current_page_offset; Self { current_page: items .iter() @@ -211,6 +210,10 @@ impl DashboardPaginatedTable { } } + pub fn is_empty(&self) -> bool { + self.current_page.is_empty() + } + pub fn has_more_than_one_page(&self) -> bool { self.pagination.pages.len() > 1 } @@ -274,27 +277,35 @@ pub struct DashboardTemplate { pub first_synced_block: BlockNumber, pub last_observed_block: Option, pub cketh_ledger_id: Principal, - pub minted_events: Vec, + pub minted_events_table: DashboardPaginatedTable, pub pending_deposits: Vec, pub invalid_events: BTreeMap, pub withdrawal_requests: Vec, pub pending_transactions: Vec, - pub finalized_transactions: Vec, - pub reimbursed_transactions: Vec, + pub finalized_transactions_table: DashboardPaginatedTable, + pub reimbursed_transactions_table: DashboardPaginatedTable, pub eth_balance: EthBalance, pub skipped_blocks: BTreeMap>, pub supported_ckerc20_tokens: Vec, - pub pagination_parameters: DashboardPagingParameters, } impl DashboardTemplate { - pub fn from_state(state: &State, pagination_parameters: DashboardPagingParameters) -> Self { + pub fn from_state(state: &State, pagination_parameters: DashboardPaginationParameters) -> Self { let mut minted_events: Vec<_> = state.minted_events.values().cloned().collect(); minted_events.sort_unstable_by_key(|event| { let deposit_event = &event.deposit_event; Reverse((deposit_event.block_number(), deposit_event.log_index())) }); + let minted_events_table = DashboardPaginatedTable::from_items( + &minted_events, + pagination_parameters.minted_events_start, + DEFAULT_PAGE_SIZE, + 7, + "minted-events", + "minted_events_start", + ); + let mut supported_ckerc20_tokens: Vec<_> = state .supported_ck_erc20_tokens() .map(|ckerc20| DashboardCkErc20Token { @@ -396,6 +407,15 @@ impl DashboardTemplate { .collect(); finalized_transactions.sort_unstable_by_key(|tx| Reverse(tx.ledger_burn_index)); + let finalized_transactions_table = DashboardPaginatedTable::from_items( + &finalized_transactions, + pagination_parameters.finalized_transactions_start, + DEFAULT_PAGE_SIZE, + 8, + "finalized-transactions", + "finalized_transactions_start", + ); + let mut reimbursed_transactions: Vec<_> = state .eth_transactions .reimbursed_transactions_iter() @@ -438,6 +458,15 @@ impl DashboardTemplate { reimbursed_transactions .sort_unstable_by_key(|reimbursed_tx| Reverse(reimbursed_tx.cketh_ledger_burn_index())); + let reimbursed_transactions_table = DashboardPaginatedTable::from_items( + &reimbursed_transactions, + pagination_parameters.reimbursed_transactions_start, + DEFAULT_PAGE_SIZE, + 6, + "reimbursed-transactions", + "reimbursed_transactions_start", + ); + DashboardTemplate { ethereum_network: state.ethereum_network, ecdsa_key_name: state.ecdsa_key_name.clone(), @@ -451,13 +480,13 @@ impl DashboardTemplate { minimum_withdrawal_amount: state.cketh_minimum_withdrawal_amount, first_synced_block: state.first_scraped_block_number, last_observed_block: state.last_observed_block_number, - minted_events, + minted_events_table, pending_deposits, invalid_events: state.invalid_events.clone(), withdrawal_requests, pending_transactions, - finalized_transactions, - reimbursed_transactions, + finalized_transactions_table, + reimbursed_transactions_table, eth_balance: state.eth_balance.clone(), skipped_blocks: state .skipped_blocks @@ -465,7 +494,6 @@ impl DashboardTemplate { .map(|(contract_address, blocks)| (contract_address.to_string(), blocks.clone())) .collect(), supported_ckerc20_tokens, - pagination_parameters, } } } diff --git a/rs/ethereum/cketh/minter/src/dashboard/tests.rs b/rs/ethereum/cketh/minter/src/dashboard/tests.rs index 4f40173ea30..6826161f9cc 100644 --- a/rs/ethereum/cketh/minter/src/dashboard/tests.rs +++ b/rs/ethereum/cketh/minter/src/dashboard/tests.rs @@ -1,5 +1,5 @@ use crate::dashboard::tests::assertions::DashboardAssert; -use crate::dashboard::{DashboardPagingParameters, DashboardTemplate}; +use crate::dashboard::{DashboardPaginationParameters, DashboardTemplate}; use crate::erc20::CkErc20Token; use candid::{Nat, Principal}; use ic_cketh_minter::eth_logs::{ @@ -164,7 +164,7 @@ fn should_display_supported_erc20_tokens() { state.ethereum_network = EthereumNetwork::Mainnet; state.record_add_ckerc20_token(usdc.clone()); state.record_add_ckerc20_token(usdt.clone()); - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -213,7 +213,7 @@ fn should_display_pending_deposits_sorted_by_decreasing_block_number() { apply_state_transition(&mut state, &EventType::AcceptedDeposit(event_1)); apply_state_transition(&mut state, &EventType::AcceptedDeposit(event_2)); apply_state_transition(&mut state, &EventType::AcceptedErc20Deposit(event_3)); - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -311,7 +311,7 @@ fn should_display_minted_events_sorted_by_decreasing_mint_block_index() { mint_block_index: LedgerMintIndex::new(44), }, ); - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -386,7 +386,7 @@ fn should_display_rejected_deposits() { reason: "failed to decode principal".to_string(), }, ); - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -423,7 +423,7 @@ fn should_display_correct_cketh_token_symbol_based_on_network() { LedgerBurnIndex::new(15), )), ); - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard).has_withdrawal_requests( 1, @@ -467,7 +467,7 @@ fn should_display_withdrawal_requests_sorted_by_decreasing_cketh_ledger_burn_ind ..cketh_withdrawal_request_with_index(LedgerBurnIndex::new(17)) }), ); - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -567,7 +567,7 @@ fn should_display_pending_transactions_sorted_by_decreasing_cketh_ledger_burn_in ); } - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -688,7 +688,7 @@ fn should_display_finalized_transactions_sorted_by_decreasing_cketh_ledger_burn_ ); } - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard) @@ -895,7 +895,7 @@ fn should_display_reimbursed_requests() { } } } - DashboardTemplate::from_state(&state, DashboardPagingParameters::default()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; // Check that we show latest first. @@ -1006,39 +1006,17 @@ fn should_display_minted_events_pagination() { let dashboard = { let mut state = initial_state(); add_minted_events(&mut state, 300); - let paging_parameters = DashboardPagingParameters { + let paging_parameters = DashboardPaginationParameters { minted_events_start: 100, // Second page. - ..DashboardPagingParameters::default() + ..DashboardPaginationParameters::default() }; DashboardTemplate::from_state(&state, paging_parameters) }; - // Events are displayed in order of decreasing index. Page 2 should have events 200 to 101. + // Events are displayed in order of decreasing log index. Page 2 should therefore have events 200 to 101. DashboardAssert::assert_that(dashboard) - .has_minted_events( - 1, - &vec![ - "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", - "200", - "0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", - "ckETH", - "10_000_000_000_000_000", - "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", - "1", - ], - ) - .has_minted_events( - 100, - &vec![ - "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", - "101", - "0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", - "ckETH", - "10_000_000_000_000_000", - "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", - "1", - ], - ) + .has_minted_events_with_log_index(1, "200") + .has_minted_events_with_log_index(100, "101") .has_minted_events_last_row_text(&vec!["Pages:", "1", "2", "3"]) .has_minted_events_last_row_links(&vec![ "?minted_events_start=0#minted-events", @@ -1051,7 +1029,7 @@ 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()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard).has_minted_events_last_row_text(&vec![ @@ -1070,7 +1048,7 @@ 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()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard).has_finalized_transactions_last_row_text(&vec![ @@ -1090,41 +1068,17 @@ fn should_display_finalized_transactions_pagination() { let dashboard = { let mut state = initial_state(); add_finalized_transactions(&mut state, 300); - let paging_parameters = DashboardPagingParameters { + let paging_parameters = DashboardPaginationParameters { finalized_transactions_start: 100, // Second page. - ..DashboardPagingParameters::default() + ..DashboardPaginationParameters::default() }; DashboardTemplate::from_state(&state, paging_parameters) }; - // Transactions are displayed in order of decreasing index. Page 2 should have transactions 200 to 101. + // Transactions are displayed in order of decreasing ledger burn index. Page 2 should therefore have transactions 200 to 101. DashboardAssert::assert_that(dashboard) - .has_finalized_transactions( - 1, - &vec![ - "200", - "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", - "ckSepoliaETH", - "1_058_000_000_000_000", - "21_000_000_000_000", - "4190269", - "0x399ba1d76e66175b7fd092050c77b036e0297d36c96d6e06a25c6205b8168774", - "Success", - ], - ) - .has_finalized_transactions( - 100, - &vec![ - "101", - "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", - "ckSepoliaETH", - "1_058_000_000_000_000", - "21_000_000_000_000", - "4190269", - "0x5a7f0423ddcbaf9429bdc18e793fa8b61f56357fb34452eae15e9396d1becd40", - "Success", - ], - ) + .has_finalized_transactions_with_ledger_burn_index(1, "200") + .has_finalized_transactions_with_ledger_burn_index(100, "101") .has_finalized_transactions_last_row_text(&vec!["Pages:", "1", "2", "3"]) .has_finalized_transactions_last_row_links(&vec![ "?finalized_transactions_start=0#finalized-transactions", @@ -1137,7 +1091,7 @@ 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()) + DashboardTemplate::from_state(&state, DashboardPaginationParameters::default()) }; DashboardAssert::assert_that(dashboard).has_reimbursed_transactions_last_row_text(&vec![ @@ -1155,37 +1109,17 @@ fn should_display_reimbursed_transactions_pagination() { let dashboard = { let mut state = initial_state(); add_reimbursed_transactions(&mut state, 300); - let paging_parameters = DashboardPagingParameters { + let paging_parameters = DashboardPaginationParameters { reimbursed_transactions_start: 100, // Second page. - ..DashboardPagingParameters::default() + ..DashboardPaginationParameters::default() }; DashboardTemplate::from_state(&state, paging_parameters) }; - // Transactions are displayed in order of decreasing index. Page 2 should have transactions 200 to 101. + // Transactions are displayed in order of decreasing ledger burn index. Page 2 should therefore have transactions 200 to 101. DashboardAssert::assert_that(dashboard) - .has_reimbursed_transactions( - 1, - &vec![ - "200", - "123", - "ckSepoliaETH", - "1_058_000_000_000_000", - "0x399ba1d76e66175b7fd092050c77b036e0297d36c96d6e06a25c6205b8168774", - "Reimbursed", - ], - ) - .has_reimbursed_transactions( - 100, - &vec![ - "101", - "123", - "ckSepoliaETH", - "1_058_000_000_000_000", - "0x5a7f0423ddcbaf9429bdc18e793fa8b61f56357fb34452eae15e9396d1becd40", - "Reimbursed", - ], - ) + .has_reimbursed_transactions_with_ledger_burn_index(1, "200") + .has_reimbursed_transactions_with_ledger_burn_index(100, "101") .has_reimbursed_transactions_last_row_text(&vec!["Pages:", "1", "2", "3"]) .has_reimbursed_transactions_last_row_links(&vec![ "?reimbursed_transactions_start=0#reimbursed-transactions", @@ -1194,7 +1128,7 @@ fn should_display_reimbursed_transactions_pagination() { } fn initial_dashboard() -> DashboardTemplate { - DashboardTemplate::from_state(&initial_state(), DashboardPagingParameters::default()) + DashboardTemplate::from_state(&initial_state(), DashboardPaginationParameters::default()) } const INITIAL_LAST_SCRAPED_BLOCK_NUMBER: u32 = 3_956_206_u32; @@ -1811,6 +1745,19 @@ mod assertions { ) } + pub fn has_minted_events_with_log_index( + &self, + row_index: u8, + expected_value: &str, + ) -> &Self { + self.has_table_row_string_value_in_column( + &format!("#minted-events + table > tbody > tr:nth-child({row_index})"), + 1, + expected_value, + "minted-events", + ) + } + 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", @@ -1863,6 +1810,19 @@ mod assertions { ) } + pub fn has_finalized_transactions_with_ledger_burn_index( + &self, + row_index: u8, + expected_value: &str, + ) -> &Self { + self.has_table_row_string_value_in_column( + &format!("#finalized-transactions + table > tbody > tr:nth-child({row_index})"), + 0, + expected_value, + "finalized-transactions", + ) + } + pub fn has_finalized_transactions_last_row_text( &self, expected_value: &Vec<&str>, @@ -1897,6 +1857,19 @@ mod assertions { ) } + pub fn has_reimbursed_transactions_with_ledger_burn_index( + &self, + row_index: u8, + expected_value: &str, + ) -> &Self { + self.has_table_row_string_value_in_column( + &format!("#reimbursed-transactions + table > tbody > tr:nth-child({row_index})"), + 0, + expected_value, + "reimbursed-transactions", + ) + } + pub fn has_reimbursed_transactions_last_row_text( &self, expected_value: &Vec<&str>, @@ -1939,6 +1912,31 @@ mod assertions { self } + fn has_table_row_string_value_in_column( + &self, + selector: &str, + column_index: usize, + expected_value: &str, + error_msg: &str, + ) -> &Self { + let actual_value = self.select_only_one(selector); + let column_values = actual_value + .text() + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect::>(); + assert_eq!( + column_values + .get(column_index) + .expect("column index out of bounds"), + &expected_value, + "{}. Rendered html: {}", + error_msg, + self.rendered_html + ); + self + } + fn has_table_row_links( &self, selector: &str, diff --git a/rs/ethereum/cketh/minter/src/main.rs b/rs/ethereum/cketh/minter/src/main.rs index e073184bd30..c50853dfb72 100644 --- a/rs/ethereum/cketh/minter/src/main.rs +++ b/rs/ethereum/cketh/minter/src/main.rs @@ -1,4 +1,4 @@ -use crate::dashboard::DashboardPagingParameters; +use crate::dashboard::DashboardPaginationParameters; use candid::Nat; use dashboard::DashboardTemplate; use ic_canister_log::log; @@ -1035,7 +1035,7 @@ fn http_request(req: HttpRequest) -> HttpResponse { } else if req.path() == "/dashboard" { use askama::Template; - let paging_parameters = match DashboardPagingParameters::from_query_params(&req) { + let paging_parameters = match DashboardPaginationParameters::from_query_params(&req) { Ok(args) => args, Err(error) => { return HttpResponseBuilder::bad_request() diff --git a/rs/ethereum/cketh/minter/templates/dashboard.html b/rs/ethereum/cketh/minter/templates/dashboard.html index 7c96a7358b5..5ec2ca14864 100644 --- a/rs/ethereum/cketh/minter/templates/dashboard.html +++ b/rs/ethereum/cketh/minter/templates/dashboard.html @@ -254,7 +254,7 @@

Pending deposit events

{% endif %} - {% if !minted_events.is_empty() %} + {% if !minted_events_table.is_empty() %}

Minted events

@@ -269,8 +269,7 @@

Minted events

- {% let table = DashboardPaginatedTable::from_items(minted_events, pagination_parameters.minted_events_start, DEFAULT_PAGE_SIZE, 7, "minted-events", "minted_events_start") %} - {% for event in table.current_page %} + {% for event in minted_events_table.current_page %} @@ -281,7 +280,7 @@

Minted events

{% endfor %} - {% if table.has_more_than_one_page() %}{{ table.pagination|safe }}{% endif %} + {% if minted_events_table.has_more_than_one_page() %}{{ minted_events_table.pagination|safe }}{% endif %}
{% call etherscan_tx_link(event.deposit_event.transaction_hash().to_string()) %} {{ event.deposit_event.log_index() }} {{ event.mint_block_index }}
{% endif %} @@ -360,7 +359,7 @@

Pending Transactions ckETH → ETH and ckERC20 → {% endif %} - {% if !finalized_transactions.is_empty() %} + {% if !finalized_transactions_table.is_empty() %}

Finalized Transactions ckETH → ETH and ckERC20 → ERC20

@@ -376,8 +375,7 @@

Finalized Transactions ckETH → ETH and ckERC20

- {% let table = DashboardPaginatedTable::from_items(finalized_transactions, pagination_parameters.finalized_transactions_start, DEFAULT_PAGE_SIZE, 8, "finalized-transactions", "finalized_transactions_start") %} - {% for tx in table.current_page %} + {% for tx in finalized_transactions_table.current_page %} @@ -389,12 +387,12 @@

Finalized Transactions ckETH → ETH and ckERC20

{% endfor %} - {% if table.has_more_than_one_page() %}{{ table.pagination|safe }}{% endif %} + {% if finalized_transactions_table.has_more_than_one_page() %}{{ finalized_transactions_table.pagination|safe }}{% endif %}
{{ tx.ledger_burn_index }} {% call etherscan_address_link(tx.destination) %}{{ tx.status }}
{% endif %} - {% if !reimbursed_transactions.is_empty() %} + {% if !reimbursed_transactions_table.is_empty() %}

Reimbursed Transactions

@@ -408,8 +406,7 @@

Reimbursed Transactions

- {% let table = DashboardPaginatedTable::from_items(reimbursed_transactions, pagination_parameters.reimbursed_transactions_start, DEFAULT_PAGE_SIZE, 6, "reimbursed-transactions", "reimbursed_transactions_start") %} - {% for r in table.current_page %} + {% for r in reimbursed_transactions_table.current_page %} {% match r %} {% when DashboardReimbursedTransaction::Reimbursed with {cketh_ledger_burn_index, reimbursed_in_block, reimbursed_amount, token_symbol, transaction_hash} %} @@ -431,7 +428,7 @@

Reimbursed Transactions

{% endmatch %} {% endfor %} - {% if table.has_more_than_one_page() %}{{ table.pagination|safe }}{% endif %} + {% if reimbursed_transactions_table.has_more_than_one_page() %}{{ reimbursed_transactions_table.pagination|safe }}{% endif %}
{% endif %}