Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
80ff1fe
build(cketh): switch test_utils and minter integration test deps to P…
gregorydemay Jul 29, 2026
4e4f4b9
test(cketh): migrate test_utils fixtures from StateMachine to PocketIC
gregorydemay Jul 29, 2026
556818a
test(cketh): migrate minter integration tests to PocketIC
gregorydemay Jul 29, 2026
c93b423
fix(cketh): consolidate latest-block-refresh predicate, name reject c…
gregorydemay Jul 29, 2026
b14c90c
fix(cketh): split await_call's await-vs-drain policy, fix stale ref
gregorydemay Jul 29, 2026
3d407fd
docs(cketh): explain the one use of pocket-ic's built-in await_call
gregorydemay Jul 29, 2026
9ceecbf
fix(cketh): soften await_call's refresh-outcall exclusion claim, use …
gregorydemay Jul 29, 2026
465c5a3
test(cketh): strip fixture machinery with no StateMachine counterpart
gregorydemay Jul 30, 2026
5b15aee
test(cketh): assert the minter address in a dedicated test
gregorydemay Jul 30, 2026
cd7998a
fix(cketh): let the stop request take effect before draining outcalls
gregorydemay Jul 30, 2026
2c53aa6
test(cketh): match the finalized block query and settle timed-out out…
gregorydemay Jul 30, 2026
04e5811
test(cketh): assert the minter address once the minter has derived it
gregorydemay Jul 30, 2026
c4bdbaa
test(cketh): match the finalized block query when retrying a scrape
gregorydemay Jul 30, 2026
0504780
test(cketh): match the finalized block query in the deposit flow
gregorydemay Jul 31, 2026
bdb975e
test(ckerc20): settle the pending scrape before advancing time
gregorydemay Jul 31, 2026
2a4f3f3
test(ckerc20): settle the pending scrape when driving the scraping in…
gregorydemay Aug 1, 2026
15c67fa
test(cketh): settle a pending scrape before the deposit advances time
gregorydemay Aug 1, 2026
5738506
docs(cketh): record why the fixtures tick and constrain their stubs
gregorydemay Aug 1, 2026
8174659
fix(cketh): restore the deprecation waiver on should_retrieve_minter_…
gregorydemay Aug 2, 2026
b971b62
test(cketh): fail the outcalls in flight when advancing past their ti…
gregorydemay Aug 3, 2026
8284b75
test(cketh): drop the tick that settled timed-out outcalls before mat…
gregorydemay Aug 3, 2026
4ea49b5
Merge branch 'ic_DEFI-2262_2_cketh-prep' into ic_DEFI-2262_3_cketh-po…
gregorydemay Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions rs/ethereum/cketh/minter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ rust_ic_test_suite(
),
data = [
":cketh_minter_debug.wasm.gz",
"//:pocket-ic-server",
"//rs/ethereum/ledger-suite-orchestrator:ledger_suite_orchestrator_canister.wasm.gz",
"//rs/ledger_suite/icrc1/archive:archive_canister_u256.wasm.gz",
"//rs/ledger_suite/icrc1/index-ng:index_ng_canister_u256.wasm.gz",
Expand All @@ -222,22 +223,23 @@ rust_ic_test_suite(
"INDEX_CANISTER_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/index-ng:index_ng_canister_u256.wasm.gz)",
"LEDGER_ARCHIVE_NODE_CANISTER_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/archive:archive_canister_u256.wasm.gz)",
"EVM_RPC_CANISTER_WASM_PATH": "$(rootpath @evm_rpc.wasm.gz//file)",
"POCKET_IC_BIN": "$(rootpath //:pocket-ic-server)",
},
proc_macro_deps = [],
deps = [
# Keep sorted.
":minter",
"//packages/ic-ethereum-types",
"//packages/icrc-ledger-types:icrc_ledger_types_storable",
"//packages/pocket-ic",
"//rs/ethereum/cketh/test_utils",
"//rs/ethereum/ledger-suite-orchestrator/test_utils",
"//rs/state_machine_tests",
"//rs/types/base_types",
"//rs/types/management_canister_types",
"@crate_index//:assert_matches",
"@crate_index//:candid",
"@crate_index//:ethers-core",
"@crate_index//:hex",
"@crate_index//:ic-management-canister-types",
"@crate_index//:minicbor",
"@crate_index//:num-bigint",
"@crate_index//:num-traits",
Expand Down
3 changes: 2 additions & 1 deletion rs/ethereum/cketh/minter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ ic-cketh-test-utils = { path = "../test_utils" }
ic-config = { path = "../../../config" }
ic-crypto-test-utils-reproducible-rng = { path = "../../../crypto/test_utils/reproducible_rng" }
ic-ledger-suite-orchestrator-test-utils = { path = "../../ledger-suite-orchestrator/test_utils" }
ic-state-machine-tests = { path = "../../../state_machine_tests" }
ic-management-canister-types = { workspace = true }
maplit = { workspace = true }
pocket-ic = { path = "../../../../packages/pocket-ic" }
proptest = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true }
Expand Down
88 changes: 39 additions & 49 deletions rs/ethereum/cketh/minter/tests/ckerc20.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use assert_matches::assert_matches;
use candid::{Nat, Principal};
use ic_base_types::CanisterId;
use ic_cketh_minter::blocklist::SAMPLE_BLOCKED_ADDRESS;
use ic_cketh_minter::endpoints::CandidBlockTag::Finalized;
use ic_cketh_minter::endpoints::events::{EventPayload, EventSource};
Expand Down Expand Up @@ -31,10 +30,9 @@ use ic_cketh_test_utils::{
LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL, MINTER_ADDRESS, format_ethereum_address_to_eip_55,
};
use ic_ethereum_types::Address;
use ic_ledger_suite_orchestrator_test_utils::flow::call_ledger_icrc1_total_supply;
use ic_ledger_suite_orchestrator_test_utils::pocket_ic::flow::call_ledger_icrc1_total_supply;
use ic_ledger_suite_orchestrator_test_utils::{supported_erc20_tokens, usdc};
use ic_management_canister_types_private::CanisterStatusType;
use ic_state_machine_tests::{ErrorCode, WasmResult};
use ic_management_canister_types::CanisterStatusType;
use icrc_ledger_types::icrc1::account::Account;
use icrc_ledger_types::icrc1::transfer::Memo;
use icrc_ledger_types::icrc3::transactions::Mint;
Expand All @@ -45,13 +43,13 @@ use std::time::Duration;
fn should_refuse_to_add_ckerc20_token_from_unauthorized_principal() {
let cketh = CkEthSetup::default();
let result = cketh.add_ckerc20_token(Principal::anonymous(), &ckusdc());
assert_matches!(result, Err(e) if e.code() == ErrorCode::CanisterCalledTrap && e.description().contains("ERROR: ERC-20"));
assert_matches!(result, Err(e) if e.error_code == pocket_ic::ErrorCode::CanisterCalledTrap && e.reject_message.contains("ERROR: ERC-20"));

let orchestrator_id: Principal = "nbsys-saaaa-aaaar-qaaga-cai".parse().unwrap();
let result = cketh
.upgrade_minter_to_add_orchestrator_id(orchestrator_id)
.add_ckerc20_token(Principal::anonymous(), &ckusdc());
assert_matches!(result, Err(e) if e.code() == ErrorCode::CanisterCalledTrap && e.description().contains("ERROR: only the orchestrator"));
assert_matches!(result, Err(e) if e.error_code == pocket_ic::ErrorCode::CanisterCalledTrap && e.reject_message.contains("ERROR: only the orchestrator"));

fn ckusdc() -> AddCkErc20Token {
AddCkErc20Token {
Expand Down Expand Up @@ -97,9 +95,7 @@ fn should_retry_to_add_usdc_when_minter_stopped() {

let mut ckerc20 = CkErc20Setup::default();
let usdc = usdc();
let stop_msg_id = ckerc20
.env
.stop_canister_non_blocking(ckerc20.cketh.minter_id);
let stop_msg_id = ckerc20.cketh.submit_stop_minter();
assert_eq!(ckerc20.cketh.minter_status(), CanisterStatusType::Stopping);

ckerc20.orchestrator = ckerc20
Expand All @@ -114,16 +110,19 @@ fn should_retry_to_add_usdc_when_minter_stopped() {
.ledger
.unwrap();

// `stop_ongoing_https_outcalls` above guarantees no outcall is left pending (it panics
// otherwise), so pocket-ic's own built-in `await_call` can be used as-is here: unlike
// elsewhere in this suite, there is nothing left for a test-injected mock response to answer.
ckerc20.cketh.stop_ongoing_https_outcalls();
let stop_res = ckerc20.env.await_ingress(stop_msg_id, 100);
assert_matches!(stop_res, Ok(WasmResult::Reply(_)));
let stop_res = ckerc20.env.await_call(stop_msg_id);
Comment thread
gregorydemay marked this conversation as resolved.
assert_matches!(stop_res, Ok(_));
assert_eq!(ckerc20.cketh.minter_status(), CanisterStatusType::Stopped);
ckerc20.env.advance_time(RETRY_FREQUENCY);
ckerc20.advance_time(RETRY_FREQUENCY);
ckerc20.env.tick();

ckerc20.cketh.start_minter();
assert_eq!(ckerc20.cketh.minter_status(), CanisterStatusType::Running);
ckerc20.env.advance_time(RETRY_FREQUENCY);
ckerc20.advance_time(RETRY_FREQUENCY);
ckerc20.env.tick();

ckerc20
Expand Down Expand Up @@ -151,12 +150,12 @@ mod deposit_erc20 {
use ic_cketh_minter::state::automatic_deposits::DEPOSIT_ADDRESS_SCAN_WINDOW;
use ic_cketh_test_utils::DEFAULT_USER_SUBACCOUNT;
use ic_cketh_test_utils::ckerc20::CkErc20Setup;
use ic_ledger_suite_orchestrator_test_utils::new_state_machine;
use ic_cketh_test_utils::new_pocket_ic;
use std::sync::Arc;

#[test]
fn should_trap_when_ckerc20_feature_not_active() {
let ckerc20 = CkErc20Setup::new_without_ckerc20_active(Arc::new(new_state_machine()));
let ckerc20 = CkErc20Setup::new_without_ckerc20_active(Arc::new(new_pocket_ic()));
let caller = ckerc20.caller();
ckerc20
.call_minter_deposit_erc20(caller, None)
Expand All @@ -183,7 +182,7 @@ mod deposit_erc20 {
let time_after = ckerc20.env.get_time().as_nanos_since_unix_epoch();

assert_eq!(
response.address, "0x9cEc8260d73Be0C2f2cC217808bf21008Bf22E4C",
response.address, "0xaEE5aaa7fBfA9802e128D93e12eC4387824E4e4E",
"BUG: key derivation should be stable"
);
assert!(
Expand Down Expand Up @@ -390,9 +389,9 @@ mod withdraw_erc20 {
CKETH_TRANSFER_FEE, DEFAULT_BLOCK_HASH, DEFAULT_BLOCK_NUMBER,
DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION, DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION_FEE,
DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION_HASH, DEFAULT_PRINCIPAL_ID, EXPECTED_BALANCE,
JsonRpcProvider,
JsonRpcProvider, new_pocket_ic,
};
use ic_ledger_suite_orchestrator_test_utils::{CKERC20_TRANSFER_FEE, new_state_machine};
use ic_ledger_suite_orchestrator_test_utils::CKERC20_TRANSFER_FEE;
use icrc_ledger_types::icrc3::transactions::Burn;
use num_bigint::BigUint;
use num_traits::ToPrimitive;
Expand All @@ -404,7 +403,7 @@ mod withdraw_erc20 {

#[test]
fn should_trap_when_ckerc20_feature_not_active() {
CkErc20Setup::new_without_ckerc20_active(Arc::new(new_state_machine()))
CkErc20Setup::new_without_ckerc20_active(Arc::new(new_pocket_ic()))
.call_minter_withdraw_erc20(
Principal::anonymous(),
0_u8,
Expand Down Expand Up @@ -644,7 +643,7 @@ mod withdraw_erc20 {
subaccount: None,
},
spender: Some(Account {
owner: minter.into(),
owner: minter,
subaccount: None,
}),
memo: Some(Memo::from(BurnMemo::Erc20GasFee {
Expand Down Expand Up @@ -673,7 +672,7 @@ mod withdraw_erc20 {
},
]);

ckerc20.env.advance_time(PROCESS_REIMBURSEMENT);
ckerc20.advance_time(PROCESS_REIMBURSEMENT);
ckerc20.env.tick();
ckerc20.env.tick();
let balance_after_reimbursement = ckerc20.cketh.balance_of(caller);
Expand Down Expand Up @@ -772,7 +771,7 @@ mod withdraw_erc20 {
.expect_refresh_gas_fee_estimate(identity)
.expect_error(insufficient_allowance_error.clone());

ckerc20.env.advance_time(Duration::from_secs(59));
ckerc20.advance_time(Duration::from_secs(59));

let ckerc20 = ckerc20
.call_minter_withdraw_erc20(
Expand All @@ -784,7 +783,7 @@ mod withdraw_erc20 {
.expect_no_refresh_gas_fee_estimate()
.expect_error(insufficient_allowance_error.clone());

ckerc20.env.advance_time(Duration::from_millis(1_001));
ckerc20.advance_time(Duration::from_millis(1_001));

ckerc20
.call_minter_withdraw_erc20(
Expand Down Expand Up @@ -930,7 +929,7 @@ mod withdraw_erc20 {
amount: ckerc20_tx_fee.into(),
from: cketh_account,
spender: Some(Account {
owner: minter.into(),
owner: minter,
subaccount: None,
}),
memo: Some(Memo::from(BurnMemo::Erc20GasFee {
Expand All @@ -951,7 +950,7 @@ mod withdraw_erc20 {
amount: ckerc20_withdrawal_amount.into(),
from: ckerc20_account,
spender: Some(Account {
owner: minter.into(),
owner: minter,
subaccount: None,
}),
memo: Some(Memo::from(BurnMemo::Erc20Convert {
Expand Down Expand Up @@ -1038,7 +1037,7 @@ mod withdraw_erc20 {
},
]);

ckerc20.env.advance_time(PROCESS_REIMBURSEMENT);
ckerc20.advance_time(PROCESS_REIMBURSEMENT);
let cketh_balance_after_reimbursement = ckerc20.wait_for_updated_ledger_balance(
ckerc20.cketh_ledger_id(),
cketh_account,
Expand Down Expand Up @@ -1313,7 +1312,7 @@ mod withdraw_erc20 {
let ckerc20_tx_fee = DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION_FEE;
let (first_tx, first_tx_sig) = default_erc20_signed_eip_1559_transaction();
let first_tx_hash = hash_transaction(first_tx.clone(), first_tx_sig);
let resubmitted_sent_tx = "0x02f8b0018084625900808507af2c9f6282fde894a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000221e931fbfcb9bd54ddd26ce6f5e29e98add01c000000000000000000000000000000000000000000000000000000000001e8480c001a03acbc792d2f821acaab8da81517f1905e30cd3acd2f85d7995c68c0ad1fd8817a0793a076f2163658c833ccddd37ee0a762a18adb423f689db5ffcf528ae667bf0";
let resubmitted_sent_tx = "0x02f8b0018084625900808507af2c9f6282fde894a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000221e931fbfcb9bd54ddd26ce6f5e29e98add01c000000000000000000000000000000000000000000000000000000000001e8480c001a06e09ef9986f589954218ef3f4d8959beb931d28e837f7b26845612875bf2b6c0a02595a316273c3ec265988faf4a698f73d86fd8fb7050b1569e453cab743415a2";
let (resubmitted_tx, resubmitted_tx_sig) = decode_transaction(resubmitted_sent_tx);
let resubmitted_tx_hash = hash_transaction(resubmitted_tx.clone(), resubmitted_tx_sig);
assert_eq!(
Expand Down Expand Up @@ -1526,7 +1525,7 @@ mod withdraw_erc20 {
assert_eq!(price, second_price);

// test an error case
let not_ledger_id = ckerc20.cketh.minter_id.into();
let not_ledger_id = ckerc20.cketh.minter_id;
ckerc20
.cketh
.eip_1559_transaction_price_expecting_err(not_ledger_id);
Expand Down Expand Up @@ -1689,12 +1688,8 @@ fn should_deposit_cketh_and_ckerc20_when_ledger_temporary_offline() {
let ckusdc = ckerc20.find_ckerc20_token("ckUSDC");
let caller = ckerc20.caller();

let stop_res = ckerc20.env.stop_canister(ckerc20.cketh.ledger_id);
assert_matches!(
stop_res,
Ok(WasmResult::Reply(_)),
"Failed to stop ckETH ledger"
);
let stop_res = ckerc20.env.stop_canister(ckerc20.cketh.ledger_id, None);
assert_matches!(stop_res, Ok(()), "Failed to stop ckETH ledger");
ckerc20.stop_ckerc20_ledger(ckusdc.ledger_canister_id);

let deposit_flow = ckerc20.deposit_cketh_and_ckerc20(
Expand Down Expand Up @@ -1738,15 +1733,11 @@ fn should_deposit_cketh_and_ckerc20_when_ledger_temporary_offline() {
)
});

let start_res = ckerc20.env.start_canister(ckerc20.cketh.ledger_id);
assert_matches!(
start_res,
Ok(WasmResult::Reply(_)),
"Failed to start ckETH ledger"
);
let start_res = ckerc20.env.start_canister(ckerc20.cketh.ledger_id, None);
assert_matches!(start_res, Ok(()), "Failed to start ckETH ledger");
ckerc20.start_ckerc20_ledger(ckusdc.ledger_canister_id);

ckerc20.env.advance_time(MINT_RETRY_DELAY);
ckerc20.advance_time(MINT_RETRY_DELAY);
ckerc20.env.tick();

let ckerc20 = ckerc20.check_events().assert_has_unique_events_in_order(&[
Expand All @@ -1773,10 +1764,7 @@ fn should_deposit_cketh_and_ckerc20_when_ledger_temporary_offline() {
Nat::from(CKETH_MINIMUM_WITHDRAWAL_AMOUNT)
);
assert_eq!(
call_ledger_icrc1_total_supply(
&ckerc20.env,
CanisterId::unchecked_from_principal(ckusdc.ledger_canister_id.into()),
),
call_ledger_icrc1_total_supply(&ckerc20.env, ckusdc.ledger_canister_id),
Nat::from(ONE_USDC)
);
}
Expand Down Expand Up @@ -1910,7 +1898,7 @@ fn should_retrieve_minter_info() {
LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL.into()
),
cketh_ledger_id: Some(ckerc20.cketh_ledger_id()),
evm_rpc_id: Some(ckerc20.cketh.evm_rpc_id.into()),
evm_rpc_id: Some(ckerc20.cketh.evm_rpc_id),
}
);
}
Expand Down Expand Up @@ -1962,8 +1950,9 @@ fn should_scrape_from_last_scraped_after_upgrade() {
// Set latest_finalized_block so that we scraped twice each time.
let latest_finalized_block =
LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + max_eth_logs_block_range * 2;
ckerc20.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL);
ckerc20.advance_time(SCRAPING_ETH_LOGS_INTERVAL);
MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber)
.with_request_params(json!(["finalized", false]))
.respond_for_all_with(block_response(latest_finalized_block))
.build()
.expect_rpc_calls(&ckerc20);
Expand Down Expand Up @@ -2039,8 +2028,9 @@ fn should_scrape_from_last_scraped_after_upgrade() {
// Advance block height and scrape again
let latest_finalized_block =
u64::try_from(second_to_block.into_inner()).unwrap() + max_eth_logs_block_range;
ckerc20.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL);
ckerc20.advance_time(SCRAPING_ETH_LOGS_INTERVAL);
MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber)
.with_request_params(json!(["finalized", false]))
.respond_for_all_with(block_response(latest_finalized_block))
.build()
.expect_rpc_calls(&ckerc20);
Expand Down Expand Up @@ -2081,7 +2071,7 @@ fn should_not_scrape_when_no_erc20_token() {

// Set latest_finalized_block so that we scrapped twice each time.
let latest_finalized_block = LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + max_eth_logs_block_range;
ckerc20.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL);
ckerc20.advance_time(SCRAPING_ETH_LOGS_INTERVAL);
MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber)
.respond_for_all_with(block_response(latest_finalized_block))
.build()
Expand Down
Loading
Loading