diff --git a/Cargo.lock b/Cargo.lock index 5c53a5d7d049..02bbcffbf76f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7356,12 +7356,12 @@ dependencies = [ "ic-ethereum-types", "ic-http-types", "ic-ledger-suite-orchestrator-test-utils", + "ic-management-canister-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "ic-management-canister-types-private", "ic-metrics-encoder", "ic-secp256k1", "ic-sha3 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ic-stable-structures 0.6.9", - "ic-state-machine-tests", "ic-utils-ensure", "icrc-cbor", "icrc-ledger-client-cdk", @@ -7371,6 +7371,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "phantom_newtype", + "pocket-ic", "proptest", "rand 0.8.6", "reqwest", @@ -7399,20 +7400,15 @@ dependencies = [ "evm_rpc_types", "hex", "ic-base-types", - "ic-cdk 0.19.0", "ic-cketh-minter", - "ic-error-types 0.2.0", "ic-ethereum-types", "ic-http-types", "ic-icrc1-ledger", "ic-ledger-suite-orchestrator", "ic-ledger-suite-orchestrator-test-utils", - "ic-management-canister-types-private", + "ic-management-canister-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "ic-metrics-assert", - "ic-state-machine-tests", "ic-test-utilities-load-wasm", - "ic-types", - "ic-types-cycles", "icrc-ledger-types", "num-traits", "pocket-ic", @@ -10573,25 +10569,18 @@ dependencies = [ name = "ic-ledger-suite-orchestrator-test-utils" version = "0.9.0" dependencies = [ - "assert_matches", "candid", "ic-base-types", "ic-cdk 0.19.0", - "ic-http-types", "ic-icrc1-ledger", "ic-ledger-suite-orchestrator", "ic-management-canister-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ic-management-canister-types-private", "ic-metrics-assert", - "ic-state-machine-tests", - "ic-test-utilities-load-wasm", - "ic-types", "ic-types-cycles", "ic-universal-canister", "icrc-ledger-types", "paste", "pocket-ic", - "proptest", ] [[package]] diff --git a/rs/ethereum/cketh/minter/BUILD.bazel b/rs/ethereum/cketh/minter/BUILD.bazel index d0f9ad8e4ce3..762efba57381 100644 --- a/rs/ethereum/cketh/minter/BUILD.bazel +++ b/rs/ethereum/cketh/minter/BUILD.bazel @@ -209,6 +209,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", @@ -223,6 +224,7 @@ 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 = [ @@ -230,15 +232,15 @@ rust_ic_test_suite( ":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", diff --git a/rs/ethereum/cketh/minter/Cargo.toml b/rs/ethereum/cketh/minter/Cargo.toml index 919517949c81..c5272f0b816e 100644 --- a/rs/ethereum/cketh/minter/Cargo.toml +++ b/rs/ethereum/cketh/minter/Cargo.toml @@ -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 } diff --git a/rs/ethereum/cketh/minter/tests/ckerc20.rs b/rs/ethereum/cketh/minter/tests/ckerc20.rs index b2a312fcb941..126b25bf0c51 100644 --- a/rs/ethereum/cketh/minter/tests/ckerc20.rs +++ b/rs/ethereum/cketh/minter/tests/ckerc20.rs @@ -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}; @@ -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; @@ -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 { @@ -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 @@ -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); + 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 @@ -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) @@ -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!( @@ -408,9 +407,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; @@ -422,7 +421,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, @@ -662,7 +661,7 @@ mod withdraw_erc20 { subaccount: None, }, spender: Some(Account { - owner: minter.into(), + owner: minter, subaccount: None, }), memo: Some(Memo::from(BurnMemo::Erc20GasFee { @@ -691,7 +690,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); @@ -790,7 +789,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( @@ -802,7 +801,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( @@ -948,7 +947,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 { @@ -969,7 +968,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 { @@ -1056,7 +1055,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, @@ -1331,7 +1330,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!( @@ -1544,7 +1543,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); @@ -1707,12 +1706,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( @@ -1756,15 +1751,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(&[ @@ -1791,10 +1782,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) ); } @@ -1928,7 +1916,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), } ); } @@ -1980,8 +1968,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); @@ -2057,8 +2046,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); @@ -2099,7 +2089,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() diff --git a/rs/ethereum/cketh/minter/tests/cketh.rs b/rs/ethereum/cketh/minter/tests/cketh.rs index 94e618b52b6e..68d569fb3c9e 100644 --- a/rs/ethereum/cketh/minter/tests/cketh.rs +++ b/rs/ethereum/cketh/minter/tests/cketh.rs @@ -34,13 +34,12 @@ use ic_cketh_test_utils::{ LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL, MINTER_ADDRESS, }; use ic_ethereum_types::Address; -use ic_management_canister_types_private::CanisterStatusType; +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::{Burn, Mint}; use num_traits::cast::ToPrimitive; use serde_json::json; -use std::collections::BTreeMap; use std::str::FromStr; use std::time::Duration; @@ -80,7 +79,7 @@ fn should_deposit_and_withdraw() { subaccount: params.recipient_subaccount, }, }; - let minter: Principal = cketh.minter_id.into(); + let minter: Principal = cketh.minter_id; let withdrawal_amount = Nat::from(withdrawal_amount); let cketh = cketh @@ -182,7 +181,7 @@ fn should_retrieve_cache_transaction_price() { let destination = DEFAULT_WITHDRAWAL_DESTINATION_ADDRESS.to_string(); let result = cketh.eip_1559_transaction_price(None); - assert_matches!(result, Err(e) if e.code() == ic_state_machine_tests::ErrorCode::CanisterCalledTrap); + assert_matches!(result, Err(e) if e.error_code == pocket_ic::ErrorCode::CanisterCalledTrap); let cketh = cketh .deposit(DepositParams::default()) @@ -213,7 +212,7 @@ fn should_retrieve_cache_transaction_price() { assert_eq!(price, second_price); let price_using_ledger_id = - cketh.eip_1559_transaction_price_expecting_ok(Some(cketh.ledger_id.into())); + cketh.eip_1559_transaction_price_expecting_ok(Some(cketh.ledger_id)); assert_eq!(price, price_using_ledger_id); } @@ -437,7 +436,7 @@ fn should_not_send_eth_transaction_when_fee_history_inconsistent() { #[test] fn should_reimburse() { let cketh = CkEthSetup::default(); - let minter: Principal = cketh.minter_id.into(); + let minter: Principal = cketh.minter_id; let caller: Principal = cketh.caller.into(); let withdrawal_amount = Nat::from(CKETH_WITHDRAWAL_AMOUNT); let destination = "0x221E931fbFcb9bd54DdD26cE6f5e29E98AdD01C0".to_string(); @@ -467,7 +466,7 @@ fn should_reimburse() { assert_eq!(balance_before_withdrawal, withdrawal_amount); // advance time so that time does not grow implicitly when executing a round - cketh.env.advance_time(Duration::from_secs(1)); + cketh.advance_time(Duration::from_secs(1)); let time_at_withdrawal = cketh.env.get_time().as_nanos_since_unix_epoch(); let cketh = cketh @@ -503,7 +502,7 @@ fn should_reimburse() { assert_eq!(cketh.balance_of(caller), Nat::from(0_u8)); - cketh.env.advance_time(PROCESS_REIMBURSEMENT); + cketh.advance_time(PROCESS_REIMBURSEMENT); cketh.env.tick(); let cost_of_failed_transaction = withdrawal_amount @@ -628,7 +627,7 @@ fn should_resubmit_new_transaction_when_price_increased() { let withdrawal_amount = Nat::from(CKETH_WITHDRAWAL_AMOUNT); let (expected_tx, expected_sig) = default_signed_eip_1559_transaction(); let first_tx_hash = hash_transaction(expected_tx.clone(), expected_sig); - let resubmitted_sent_tx = "0x02f873018084625900808507b81d70e382520894221e931fbfcb9bd54ddd26ce6f5e29e98add01c0880160cc412e75c2de80c001a03d58ee49c9dce3b3c646eeb18317b46cc852a5384be9026cb0aa3d59f9b16292a007276dfb5e003bd7f527675e15c8512f1324e6434c62e7ffa4c68971d726fa0b"; + let resubmitted_sent_tx = "0x02f873018084625900808507b81d70e382520894221e931fbfcb9bd54ddd26ce6f5e29e98add01c0880160cc412e75c2de80c001a0a50a97743db2c45bfafaa668abb848ea6a15818d7c704b06c8957827fa68c6e8a04175e5fa7fbda1bb84e254f31acafd0fdf95f094127b0520b4e447e80d1afe37"; 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!( @@ -703,7 +702,7 @@ fn should_not_overlap_when_scrapping_logs() { let cketh = CkEthSetup::default(); let max_eth_logs_block_range = cketh.max_logs_block_range(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) .respond_for_all_with(block_response(DEFAULT_BLOCK_NUMBER)) .build() @@ -754,8 +753,9 @@ fn should_retry_from_same_block_when_scrapping_fails() { let max_eth_logs_block_range = cketh.max_logs_block_range(); let prev_events_len = cketh.get_all_events().len(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) + .with_request_params(json!(["finalized", false])) .respond_for_all_with(block_response(DEFAULT_BLOCK_NUMBER)) .build() .expect_rpc_calls(&cketh); @@ -783,8 +783,9 @@ fn should_retry_from_same_block_when_scrapping_fails() { block_number: LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL.into(), }]); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) + .with_request_params(json!(["finalized", false])) .respond_for_all_with(block_response(DEFAULT_BLOCK_NUMBER)) .build() .expect_rpc_calls(&cketh); @@ -810,7 +811,7 @@ fn should_retry_from_same_block_when_scrapping_fails() { fn should_scrap_one_block_when_at_boundary_with_last_finalized_block() { let cketh = CkEthSetup::default(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) .respond_for_all_with(block_response(LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + 1)) .build() @@ -848,7 +849,7 @@ fn should_be_able_to_stop_canister_during_scraping() { let cketh = CkEthSetup::default(); let max_eth_logs_block_range = cketh.as_ref().max_logs_block_range(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); mock_eth_get_block_by_number(&cketh, MAX_BLOCK); // Starts scraping to create open call contexts. @@ -871,7 +872,7 @@ fn should_be_able_to_stop_canister_during_scraping() { cketh.env.tick(); cketh.env.tick(); assert_eq!( - cketh.env.canister_http_request_contexts().len(), + cketh.env.get_canister_http().len(), 4, "Expected HTTPS outcalls since scraping is still in progress: {}", debug_http_outcalls(&cketh.env) @@ -904,15 +905,15 @@ fn should_be_able_to_stop_canister_during_scraping() { cketh.tick_until_minter_canister_status(CanisterStatusType::Stopped); assert_eq!( - cketh.env.canister_http_request_contexts(), - BTreeMap::default(), + cketh.env.get_canister_http(), + Vec::new(), "Unexpected pending HTTPS outcalls" ); // Restarting the canister should resume scraping from where we stopped cketh.start_minter(); cketh.tick_until_minter_canister_status(CanisterStatusType::Running); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); mock_eth_get_block_by_number(&cketh, MAX_BLOCK); from_block = to_block.checked_increment().unwrap(); @@ -936,8 +937,9 @@ fn should_panic_when_last_finalized_block_in_the_past() { let cketh = CkEthSetup::default(); let prev_events_len = cketh.get_all_events().len(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) + .with_request_params(json!(["finalized", false])) .respond_for_all_with(block_response(LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL - 1)) .build() .expect_rpc_calls(&cketh); @@ -951,8 +953,9 @@ fn should_panic_when_last_finalized_block_in_the_past() { }]); let last_finalized_block = LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + 10; - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) + .with_request_params(json!(["finalized", false])) .respond_for_all_with(block_response(last_finalized_block)) .build() .expect_rpc_calls(&cketh); @@ -1047,7 +1050,7 @@ fn should_half_range_of_scrapped_logs_when_response_over_two_mega_bytes() { .checked_add(BlockNumber::from(max_eth_logs_block_range / 2)) .unwrap(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) .respond_for_all_with(block_response(DEFAULT_BLOCK_NUMBER)) .build() @@ -1096,7 +1099,7 @@ fn should_skip_single_block_containing_too_many_events() { let large_amount_of_logs = multi_logs_for_single_transaction(deposit.clone(), 3_500); assert!(serde_json::to_vec(&large_amount_of_logs).unwrap().len() > 2_000_000); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) .respond_for_all_with(block_response(LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + 3)) .build() @@ -1174,6 +1177,16 @@ fn should_skip_single_block_containing_too_many_events() { ]); } +#[test] +fn should_derive_minter_address() { + let cketh = CkEthSetup::default(); + + assert_eq!( + Address::from_str(&cketh.minter_address()).unwrap(), + Address::from_str(MINTER_ADDRESS).unwrap() + ); +} + #[allow(deprecated)] #[test] fn should_retrieve_minter_info() { @@ -1187,7 +1200,9 @@ fn should_retrieve_minter_info() { assert_eq!( info_at_start, MinterInfo { - minter_address: Some(format_ethereum_address_to_eip_55(MINTER_ADDRESS)), + // The minter derives its address from a timer scheduled at install, so a + // fixture that has executed no round has not derived it yet. + minter_address: None, smart_contract_address: Some(format_ethereum_address_to_eip_55( ETH_HELPER_CONTRACT_ADDRESS )), @@ -1208,8 +1223,8 @@ fn should_retrieve_minter_info() { last_deposit_with_subaccount_scraped_block_number: Some( LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL.into() ), - cketh_ledger_id: Some(cketh.ledger_id.into()), - evm_rpc_id: Some(cketh.evm_rpc_id.into()), + cketh_ledger_id: Some(cketh.ledger_id), + evm_rpc_id: Some(cketh.evm_rpc_id), } ); @@ -1220,6 +1235,7 @@ fn should_retrieve_minter_info() { assert_eq!( info_after_deposit, MinterInfo { + minter_address: Some(format_ethereum_address_to_eip_55(MINTER_ADDRESS)), last_observed_block_number: Some(Nat::from(new_eth_scraped_block_number)), eth_balance: Some(Nat::from(EXPECTED_BALANCE)), last_eth_scraped_block_number: Some(new_eth_scraped_block_number.into()), @@ -1310,7 +1326,7 @@ mod cketh_evm_rpc { fn should_retrieve_block_number() { let cketh = CkEthSetup::default(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) .respond_for_all_with(block_response(LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + 3)) .build() @@ -1326,11 +1342,11 @@ mod cketh_evm_rpc { cketh.stop_minter(); cketh .env - .stop_canister(cketh.evm_rpc_id) + .stop_canister(cketh.evm_rpc_id, None) .expect("Failed to stop EVM RPC canister"); cketh.start_minter(); - cketh.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + cketh.advance_time(SCRAPING_ETH_LOGS_INTERVAL); for _ in 0..10 { cketh.env.tick(); diff --git a/rs/ethereum/cketh/test_utils/BUILD.bazel b/rs/ethereum/cketh/test_utils/BUILD.bazel index 1fc4dcd05f3f..35b7cf4fe856 100644 --- a/rs/ethereum/cketh/test_utils/BUILD.bazel +++ b/rs/ethereum/cketh/test_utils/BUILD.bazel @@ -16,28 +16,23 @@ rust_library( version = "0.1.0", deps = [ # Keep sorted. - "//packages/ic-error-types", "//packages/ic-ethereum-types", "//packages/ic-http-types", - "//packages/ic-metrics-assert", + "//packages/ic-metrics-assert:ic-metrics-assert_pocket_ic", "//packages/icrc-ledger-types:icrc_ledger_types_storable", "//packages/pocket-ic", "//rs/ethereum/cketh/minter", "//rs/ethereum/ledger-suite-orchestrator:ledger_suite_orchestrator", "//rs/ethereum/ledger-suite-orchestrator/test_utils", "//rs/ledger_suite/icrc1/ledger", - "//rs/state_machine_tests", "//rs/test_utilities/load_wasm", "//rs/types/base_types", - "//rs/types/cycles", - "//rs/types/management_canister_types", - "//rs/types/types", "@crate_index//:assert_matches", "@crate_index//:candid", "@crate_index//:ethers-core", "@crate_index//:evm_rpc_types", "@crate_index//:hex", - "@crate_index//:ic-cdk", + "@crate_index//:ic-management-canister-types", "@crate_index//:num-traits", "@crate_index//:reqwest", "@crate_index//:serde", diff --git a/rs/ethereum/cketh/test_utils/Cargo.toml b/rs/ethereum/cketh/test_utils/Cargo.toml index f47ed2f44ae4..d2cbeead1e37 100644 --- a/rs/ethereum/cketh/test_utils/Cargo.toml +++ b/rs/ethereum/cketh/test_utils/Cargo.toml @@ -13,20 +13,15 @@ ethers-core = { workspace = true } evm_rpc_types = { workspace = true } hex = { workspace = true } ic-base-types = { path = "../../../types/base_types" } -ic-cdk = { workspace = true } ic-cketh-minter = { path = "../minter" } -ic-error-types = { path = "../../../../packages/ic-error-types" } ic-ethereum-types = { path = "../../../../packages/ic-ethereum-types" } ic-http-types = { path = "../../../../packages/ic-http-types" } ic-icrc1-ledger = { path = "../../../ledger_suite/icrc1/ledger" } ic-ledger-suite-orchestrator = { path = "../../ledger-suite-orchestrator" } ic-ledger-suite-orchestrator-test-utils = { path = "../../ledger-suite-orchestrator/test_utils" } -ic-management-canister-types-private = { path = "../../../types/management_canister_types" } -ic-metrics-assert = { path = "../../../../packages/ic-metrics-assert" } -ic-state-machine-tests = { path = "../../../state_machine_tests" } +ic-management-canister-types = { workspace = true } +ic-metrics-assert = { path = "../../../../packages/ic-metrics-assert", features = ["pocket_ic"] } ic-test-utilities-load-wasm = { path = "../../../test_utilities/load_wasm" } -ic-types-cycles = { path = "../../../types/cycles" } -ic-types = { path = "../../../types/types" } icrc-ledger-types = { path = "../../../../packages/icrc-ledger-types" } num-traits = { workspace = true } pocket-ic = { path = "../../../../packages/pocket-ic" } diff --git a/rs/ethereum/cketh/test_utils/src/ckerc20.rs b/rs/ethereum/cketh/test_utils/src/ckerc20.rs index 693361af9086..5c40077bd374 100644 --- a/rs/ethereum/cketh/test_utils/src/ckerc20.rs +++ b/rs/ethereum/cketh/test_utils/src/ckerc20.rs @@ -13,12 +13,12 @@ use crate::{ DEPOSIT_WITH_SUBACCOUNT_HELPER_CONTRACT_ADDRESS, ERC20_HELPER_CONTRACT_ADDRESS, ETH_HELPER_CONTRACT_ADDRESS, LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL, LedgerBalance, MAX_TICKS, RECEIVED_ERC20_EVENT_TOPIC, RECEIVED_ETH_OR_ERC20_WITH_SUBACCOUNT_EVENT_TOPIC, assert_reply, - format_ethereum_address_to_eip_55, new_state_machine, + format_ethereum_address_to_eip_55, new_pocket_ic, }; use assert_matches::assert_matches; use candid::{Decode, Encode, Nat, Principal}; use evm_rpc_types::Hex32; -use ic_base_types::{CanisterId, PrincipalId}; +use ic_base_types::PrincipalId; use ic_cketh_minter::endpoints::ckerc20::{ RetrieveErc20Request, WithdrawErc20Arg, WithdrawErc20Error, }; @@ -33,11 +33,12 @@ use ic_cketh_minter::{ use ic_ethereum_types::Address; pub use ic_ledger_suite_orchestrator::candid::AddErc20Arg as Erc20Token; use ic_ledger_suite_orchestrator::candid::InitArg as LedgerSuiteOrchestratorInitArg; -use ic_ledger_suite_orchestrator_test_utils::{LedgerSuiteOrchestrator, supported_erc20_tokens}; -use ic_state_machine_tests::{ErrorCode, StateMachine, WasmResult}; -use ic_types::messages::MessageId; +use ic_ledger_suite_orchestrator_test_utils::pocket_ic::LedgerSuiteOrchestrator; +use ic_ledger_suite_orchestrator_test_utils::supported_erc20_tokens; use icrc_ledger_types::icrc1::account::Account; use num_traits::ToPrimitive; +use pocket_ic::common::rest::RawMessageId; +use pocket_ic::{ErrorCode, PocketIc}; use serde_json::json; use std::collections::{BTreeMap, BTreeSet}; use std::convert::identity; @@ -53,7 +54,7 @@ pub const ONE_USDC: u64 = 1_000_000; //6 decimals pub const TWO_USDC: u64 = 2_000_000; //6 decimals pub struct CkErc20Setup { - pub env: Arc, + pub env: Arc, pub cketh: CkEthSetup, pub orchestrator: LedgerSuiteOrchestrator, pub supported_erc20_tokens: Vec, @@ -61,7 +62,7 @@ pub struct CkErc20Setup { impl Default for CkErc20Setup { fn default() -> Self { - Self::new(Arc::new(new_state_machine())) + Self::new(Arc::new(new_pocket_ic())) } } @@ -72,28 +73,24 @@ impl AsRef for CkErc20Setup { } impl CkErc20Setup { - pub fn new(env: Arc) -> Self { + pub fn new(env: Arc) -> Self { let mut ckerc20 = Self::new_without_ckerc20_active(env); ckerc20.cketh = ckerc20 .cketh .upgrade_minter_to_add_orchestrator_id( - ckerc20 - .orchestrator - .ledger_suite_orchestrator_id - .get_ref() - .0, + ckerc20.orchestrator.ledger_suite_orchestrator_id, ) .upgrade_minter_to_add_erc20_helper_contract(ERC20_HELPER_CONTRACT_ADDRESS.to_string()); ckerc20 } - pub fn new_without_ckerc20_active(env: Arc) -> Self { + pub fn new_without_ckerc20_active(env: Arc) -> Self { let cketh = CkEthSetup::new(env.clone()); let orchestrator = LedgerSuiteOrchestrator::new( env.clone(), LedgerSuiteOrchestratorInitArg { more_controller_ids: vec![], - minter_id: Some(cketh.minter_id.get_ref().0), + minter_id: Some(cketh.minter_id), cycles_management: None, }, ) @@ -255,34 +252,25 @@ impl CkErc20Setup { } pub fn stop_ckerc20_ledger(&self, ledger_id: Principal) { - let stop_res = self.env.stop_canister_as( - self.orchestrator.ledger_suite_orchestrator_id.get(), - CanisterId::unchecked_from_principal(ledger_id.into()), - ); - assert_matches!( - stop_res, - Ok(WasmResult::Reply(_)), - "Failed to stop ckERC20 ledger" - ); + self.env + .stop_canister( + ledger_id, + Some(self.orchestrator.ledger_suite_orchestrator_id), + ) + .expect("Failed to stop ckERC20 ledger"); } pub fn start_ckerc20_ledger(&self, ledger_id: Principal) { - let start_res = self.env.start_canister_as( - self.orchestrator.ledger_suite_orchestrator_id.get(), - CanisterId::unchecked_from_principal(ledger_id.into()), - ); - assert_matches!( - start_res, - Ok(WasmResult::Reply(_)), - "Failed to start ckERC20 ledger" - ); + self.env + .start_canister( + ledger_id, + Some(self.orchestrator.ledger_suite_orchestrator_id), + ) + .expect("Failed to start ckERC20 ledger"); } pub fn balance_of_ledger(&self, ledger_id: Principal, account: impl Into) -> Nat { - self.cketh.balance_of_ledger( - CanisterId::unchecked_from_principal(ledger_id.into()), - account, - ) + self.cketh.balance_of_ledger(ledger_id, account) } pub fn call_cketh_ledger_approve_minter( @@ -307,12 +295,7 @@ impl CkErc20Setup { ) -> Self { self.cketh = self .cketh - .call_ledger_id_approve_minter( - CanisterId::unchecked_from_principal(ledger_id.into()), - from, - amount, - from_subaccount, - ) + .call_ledger_id_approve_minter(ledger_id, from, amount, from_subaccount) .expect_ok(1); self } @@ -337,11 +320,8 @@ impl CkErc20Setup { ledger_id: Principal, ledger_index: T, ) -> LedgerTransactionAssert { - let ledger_transaction = crate::flow::call_ledger_id_get_transaction( - &self.env, - CanisterId::unchecked_from_principal(ledger_id.into()), - ledger_index, - ); + let ledger_transaction = + crate::flow::call_ledger_id_get_transaction(&self.env, ledger_id, ledger_index); LedgerTransactionAssert { setup: self, ledger_transaction, @@ -370,12 +350,15 @@ impl CkErc20Setup { from: Principal, withdraw_erc20_arg: WithdrawErc20Arg, ) -> RefreshGasFeeEstimate { - let message_id = self.env.send_ingress( - PrincipalId::from(from), - self.cketh.minter_id, - "withdraw_erc20", - Encode!(&withdraw_erc20_arg).expect("failed to encode withdraw args"), - ); + let message_id = self + .env + .submit_call( + self.cketh.minter_id, + from, + "withdraw_erc20", + Encode!(&withdraw_erc20_arg).expect("failed to encode withdraw args"), + ) + .expect("failed to submit withdraw_erc20 call"); RefreshGasFeeEstimate { setup: self, message_id, @@ -398,24 +381,31 @@ impl CkErc20Setup { from: Principal, deposit_erc20_arg: DepositErc20Arg, ) -> DepositErc20Flow { - let message_id = self.env.send_ingress( - PrincipalId::from(from), - self.cketh.minter_id, - "deposit_erc20", - Encode!(&deposit_erc20_arg).expect("failed to encode deposit_erc20 args"), - ); + let message_id = self + .env + .submit_call( + self.cketh.minter_id, + from, + "deposit_erc20", + Encode!(&deposit_erc20_arg).expect("failed to encode deposit_erc20 args"), + ) + .expect("failed to submit deposit_erc20 call"); DepositErc20Flow { setup: self, message_id, } } + pub fn advance_time(&self, duration: Duration) { + self.cketh.advance_time(duration); + } + pub fn caller(&self) -> Principal { self.cketh.caller.into() } pub fn cketh_ledger_id(&self) -> Principal { - self.cketh.ledger_id.get_ref().0 + self.cketh.ledger_id } pub fn find_ckerc20_token(&self, token_symbol: &str) -> CkErc20Token { @@ -827,8 +817,9 @@ impl CkErc20DepositFlow { let max_eth_logs_block_range = self.as_ref().max_logs_block_range(); let latest_finalized_block = LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + 1 + max_eth_logs_block_range; - self.setup.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + self.setup.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(self); @@ -939,7 +930,7 @@ impl CkErc20DepositFlow { pub struct RefreshGasFeeEstimate { pub setup: CkErc20Setup, - pub message_id: MessageId, + pub message_id: RawMessageId, } impl RefreshGasFeeEstimate { @@ -979,16 +970,13 @@ impl RefreshGasFeeEstimate { pub struct Erc20WithdrawalFlow { pub setup: CkErc20Setup, - pub message_id: MessageId, + pub message_id: RawMessageId, } impl Erc20WithdrawalFlow { pub fn expect_trap(self, error_substring: &str) -> CkErc20Setup { - let result = self - .setup - .env - .await_ingress(self.message_id.clone(), MAX_TICKS); - assert_matches!(result, Err(e) if e.code() == ErrorCode::CanisterCalledTrap && e.description().contains(error_substring)); + let result = self.setup.env.await_call(self.message_id.clone()); + assert_matches!(result, Err(e) if e.error_code == ErrorCode::CanisterCalledTrap && e.reject_message.contains(error_substring)); self.setup } @@ -1015,27 +1003,23 @@ impl Erc20WithdrawalFlow { #[allow(clippy::result_large_err)] fn minter_response(&self) -> Result { - Decode!(&assert_reply( - self.setup.env - .await_ingress(self.message_id.clone(), MAX_TICKS) - .expect("failed to resolve message with id: {message_id}"), - ), Result) + Decode!( + &assert_reply(self.setup.env.await_call(self.message_id.clone())), + Result + ) .unwrap() } } pub struct DepositErc20Flow { pub setup: CkErc20Setup, - pub message_id: MessageId, + pub message_id: RawMessageId, } impl DepositErc20Flow { pub fn expect_trap(self, error_substring: &str) -> CkErc20Setup { - let result = self - .setup - .env - .await_ingress(self.message_id.clone(), MAX_TICKS); - assert_matches!(result, Err(e) if e.code() == ErrorCode::CanisterCalledTrap && e.description().contains(error_substring)); + let result = self.setup.env.await_call(self.message_id.clone()); + assert_matches!(result, Err(e) if e.error_code == ErrorCode::CanisterCalledTrap && e.reject_message.contains(error_substring)); self.setup } @@ -1057,12 +1041,7 @@ impl DepositErc20Flow { fn minter_response(&self) -> Result { Decode!( - &assert_reply( - self.setup - .env - .await_ingress(self.message_id.clone(), MAX_TICKS) - .expect("failed to resolve message with id: {message_id}") - ), + &assert_reply(self.setup.env.await_call(self.message_id.clone())), Result ) .unwrap() diff --git a/rs/ethereum/cketh/test_utils/src/flow.rs b/rs/ethereum/cketh/test_utils/src/flow.rs index bc89f88270b3..bda7feead1df 100644 --- a/rs/ethereum/cketh/test_utils/src/flow.rs +++ b/rs/ethereum/cketh/test_utils/src/flow.rs @@ -9,13 +9,13 @@ use crate::{ DEFAULT_DEPOSIT_FROM_ADDRESS, DEFAULT_DEPOSIT_LOG_INDEX, DEFAULT_DEPOSIT_TRANSACTION_HASH, DEFAULT_DEPOSIT_TRANSACTION_INDEX, DEFAULT_PRINCIPAL_ID, DEFAULT_USER_SUBACCOUNT, EFFECTIVE_GAS_PRICE, EXPECTED_BALANCE, GAS_USED, JsonRpcProvider, - LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL, MAX_TICKS, MINTER_ADDRESS, RECEIVED_ETH_EVENT_TOPIC, + LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL, MINTER_ADDRESS, RECEIVED_ETH_EVENT_TOPIC, RECEIVED_ETH_OR_ERC20_WITH_SUBACCOUNT_EVENT_TOPIC, assert_reply, format_ethereum_address_to_eip_55, }; use candid::{Decode, Encode, Nat, Principal}; use ethers_core::utils::{hex, rlp}; -use ic_base_types::{CanisterId, PrincipalId}; +use ic_base_types::PrincipalId; use ic_cketh_minter::endpoints::ckerc20::RetrieveErc20Request; use ic_cketh_minter::endpoints::events::{Event, EventPayload, EventSource}; use ic_cketh_minter::endpoints::{ @@ -27,12 +27,12 @@ use ic_cketh_minter::{ SCRAPING_ETH_LOGS_INTERVAL, }; use ic_ethereum_types::Address; -use ic_state_machine_tests::StateMachine; -use ic_types::messages::MessageId; use icrc_ledger_types::icrc1::account::Account; use icrc_ledger_types::icrc2::approve::ApproveError; use icrc_ledger_types::icrc3::transactions::{Burn, Mint, Transaction as LedgerTransaction}; use num_traits::ToPrimitive; +use pocket_ic::PocketIc; +use pocket_ic::common::rest::RawMessageId; use serde_json::json; use std::convert::identity; use std::str::FromStr; @@ -309,7 +309,7 @@ impl DepositFlow { fn updated_balance(&self, balance_before: &Nat) -> Nat { let mut current_balance = balance_before.clone(); for _ in 0..10 { - self.setup.env.advance_time(Duration::from_secs(1)); + self.setup.advance_time(Duration::from_secs(1)); self.setup.env.tick(); current_balance = self.setup.balance_of(self.params.recipient()); if ¤t_balance != balance_before { @@ -331,16 +331,17 @@ impl DepositFlow { let max_eth_logs_block_range = self.setup.max_logs_block_range(); let latest_finalized_block = LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL + 1 + max_eth_logs_block_range; - self.setup.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + self.setup.advance_time(SCRAPING_ETH_LOGS_INTERVAL); let default_get_block_by_number = MockJsonRpcProviders::when(JsonRpcMethod::EthGetBlockByNumber) + .with_request_params(json!(["finalized", false])) .respond_for_all_with(block_response(latest_finalized_block)); (self.override_rpc_eth_get_block_by_number)(default_get_block_by_number) .build() .expect_rpc_calls(&self.setup); - self.setup.env.advance_time(SCRAPING_ETH_LOGS_INTERVAL); + self.setup.advance_time(SCRAPING_ETH_LOGS_INTERVAL); match &self.params { DepositParams::CkEth(_) => { @@ -398,8 +399,8 @@ impl LedgerTransactionAssert { } pub fn call_ledger_id_get_transaction>( - env: &StateMachine, - ledger_id: CanisterId, + env: &PocketIc, + ledger_id: Principal, ledger_index: T, ) -> LedgerTransaction { use icrc_ledger_types::icrc3::transactions::{GetTransactionsRequest, GetTransactionsResponse}; @@ -409,10 +410,12 @@ pub fn call_ledger_id_get_transaction>( length: 1_u8.into(), }; let mut response = Decode!( - &assert_reply( - env.query(ledger_id, "get_transactions", Encode!(&request).unwrap()) - .expect("failed to query get_transactions on the ledger") - ), + &assert_reply(env.query_call( + ledger_id, + Principal::anonymous(), + "get_transactions", + Encode!(&request).unwrap() + )), GetTransactionsResponse ) .unwrap(); @@ -452,7 +455,7 @@ impl ApprovalFlow { pub struct WithdrawalFlow { pub(crate) setup: CkEthSetup, - pub(crate) message_id: MessageId, + pub(crate) message_id: RawMessageId, } impl WithdrawalFlow { @@ -478,11 +481,10 @@ impl WithdrawalFlow { } fn minter_response(&self) -> Result { - Decode!(&assert_reply( - self.setup.env - .await_ingress(self.message_id.clone(), MAX_TICKS) - .expect("failed to resolve message with id: {message_id}"), - ), Result) + Decode!( + &assert_reply(self.setup.env.await_call(self.message_id.clone())), + Result + ) .unwrap() } } diff --git a/rs/ethereum/cketh/test_utils/src/lib.rs b/rs/ethereum/cketh/test_utils/src/lib.rs index 2c7bb6023e33..37248e4c6f30 100644 --- a/rs/ethereum/cketh/test_utils/src/lib.rs +++ b/rs/ethereum/cketh/test_utils/src/lib.rs @@ -5,7 +5,7 @@ use crate::flow::{ use crate::mock::JsonRpcMethod; use assert_matches::assert_matches; use candid::{Decode, Encode, Nat, Principal}; -use ic_base_types::{CanisterId, PrincipalId}; +use ic_base_types::PrincipalId; use ic_cketh_minter::endpoints::events::{Event, EventPayload, GetEventsResult}; use ic_cketh_minter::endpoints::{ AddCkErc20Token, DecodeLedgerMemoArgs, DecodeLedgerMemoResult, Eip1559TransactionPriceArg, @@ -21,17 +21,17 @@ use ic_cketh_minter::{ use ic_ethereum_types::Address; use ic_http_types::{HttpRequest, HttpResponse}; use ic_icrc1_ledger::{InitArgsBuilder as LedgerInitArgsBuilder, LedgerArgument}; -use ic_management_canister_types_private::{CanisterHttpResponsePayload, CanisterStatusType}; -use ic_metrics_assert::{CanisterHttpQuery, MetricsAssert}; -use ic_state_machine_tests::{ - PayloadBuilder, StateMachine, StateMachineBuilder, UserError, WasmResult, -}; +use ic_management_canister_types::{CanisterId, CanisterIdRecord, CanisterStatusType}; +use ic_metrics_assert::{MetricsAssert, PocketIcHttpQuery}; use ic_test_utilities_load_wasm::load_wasm; -use ic_types::ingress::{IngressState, IngressStatus}; -use ic_types_cycles::Cycles; use icrc_ledger_types::icrc1::account::Account; use icrc_ledger_types::icrc2::approve::{ApproveArgs, ApproveError}; use num_traits::cast::ToPrimitive; +use pocket_ic::common::rest::{ + CanisterHttpReject, CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, IcpConfig, + IcpConfigFlag, MockCanisterHttpResponse, RawEffectivePrincipal, RawMessageId, +}; +use pocket_ic::{PocketIc, PocketIcBuilder, RejectResponse}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -54,6 +54,13 @@ mod tests; pub const CKETH_TRANSFER_FEE: u64 = 2_000_000_000_000; pub const CKETH_MINIMUM_WITHDRAWAL_AMOUNT: u64 = 30_000_000_000_000_000; pub const MAX_TICKS: usize = 10; + +// `ic_error_types::RejectCode` value relevant to canister-http mocking. +pub(crate) const REJECT_CODE_SYS_FATAL: u64 = 1; +pub(crate) const REJECT_CODE_SYS_TRANSIENT: u64 = 2; +// ic_types::canister_http::CANISTER_HTTP_TIMEOUT_INTERVAL, past which PocketIC fails +// canister http requests still in flight. +const CANISTER_HTTP_TIMEOUT_INTERVAL: Duration = Duration::from_secs(60); pub const DEFAULT_PRINCIPAL_ID: u64 = 10352385; pub const DEFAULT_USER_SUBACCOUNT: [u8; 32] = [42; 32]; pub const DEFAULT_DEPOSIT_BLOCK_NUMBER: u64 = 0x9; @@ -78,18 +85,18 @@ pub const EFFECTIVE_GAS_PRICE: u64 = 4_277_923_390; pub const GAS_USED: u64 = 0x5208; pub const DEFAULT_WITHDRAWAL_TRANSACTION_HASH: &str = - "0x2cf1763e8ee3990103a31a5709b17b83f167738abb400844e67f608a98b0bdb5"; -pub const DEFAULT_WITHDRAWAL_TRANSACTION: &str = "0x02f87301808459682f008507af2c9f6282520894221e931fbfcb9bd54ddd26ce6f5e29e98add01c0880160cf1e9917a0e680c001a0b27af25a08e87836a778ac2858fdfcff1f6f3a0d43313782c81d05ca34b80271a078026b399a32d3d7abab625388a3c57f651c66a182eb7f8b1a58d9aef7547256"; + "0xa31221e733b030eb72eeb6973593a4d920c2c3391433429eed3c16b8f4f3ba7a"; +pub const DEFAULT_WITHDRAWAL_TRANSACTION: &str = "0x02f87301808459682f008507af2c9f6282520894221e931fbfcb9bd54ddd26ce6f5e29e98add01c0880160cf1e9917a0e680c001a0ad488ebb7c3cdf69ac424ab1e64e52c01fc4f93ac877946f707b0f29199f8a13a01996863aed39f3fca6350d907622c24864f9a04c21d16d4b27d37d8a3531653b"; -pub const DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION: &str = "0x02f8b001808459682f008507af2c9f6282fde894a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000221e931fbfcb9bd54ddd26ce6f5e29e98add01c000000000000000000000000000000000000000000000000000000000001e8480c080a0bb694aec6175b489523a55d5fce39452368e97096d4afa2cdcc35cf2d805152fa00112b26a028af84dd397d23549844efdaf761d90cdcfdbe6c3608239648a85a3"; +pub const DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION: &str = "0x02f8b001808459682f008507af2c9f6282fde894a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000221e931fbfcb9bd54ddd26ce6f5e29e98add01c000000000000000000000000000000000000000000000000000000000001e8480c080a0da4f476ede0aaf7da633371a938d5e2525a65a23699b55761779871a313f8cb3a045833d409eba50e3e9b145d04ea294ee791c14465503818f8b325a881938ddc1"; pub const DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION_HASH: &str = - "0x2c0c328876b8d60580e00d8e5a82599e22099e78d9d9c25cc5e6164bc8f4db62"; + "0x31d6e7ad4b7c167ca17777fd4aafd11a02c9fd8d3bc660f7ea4d7a2e2bf4a985"; pub const DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION_FEE: u64 = 2_145_241_036_770_000_u64; pub const USDC_ERC20_CONTRACT_ADDRESS: &str = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; pub const USDC_ERC20_CONTRACT_ADDRESS_LOWERCASE: &str = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; -pub const MINTER_ADDRESS: &str = "0xfd644a761079369962386f8e4259217c2a10b8d0"; +pub const MINTER_ADDRESS: &str = "0x30a14171b7c4c93ff5213f82eeb74f7c7e3f1ebc"; pub const DEFAULT_WITHDRAWAL_DESTINATION_ADDRESS: &str = "0x221E931fbFcb9bd54DdD26cE6f5e29E98AdD01C0"; pub const ETH_HELPER_CONTRACT_ADDRESS: &str = "0x907b6efc1a398fd88a8161b3ca02eec8eaf72ca1"; @@ -105,17 +112,17 @@ const RECEIVED_ETH_OR_ERC20_WITH_SUBACCOUNT_EVENT_TOPIC: &str = pub const HEADER_SIZE_LIMIT: u64 = 2 * 1024; pub struct CkEthSetup { - pub env: Arc, + pub env: Arc, pub caller: PrincipalId, - pub ledger_id: CanisterId, - pub minter_id: CanisterId, - pub evm_rpc_id: CanisterId, + pub ledger_id: Principal, + pub minter_id: Principal, + pub evm_rpc_id: Principal, pub support_subaccount: bool, } impl Default for CkEthSetup { fn default() -> Self { - Self::new(Arc::new(new_state_machine())) + Self::new(Arc::new(new_pocket_ic())) } } @@ -125,28 +132,32 @@ impl AsRef for CkEthSetup { } } -impl CanisterHttpQuery for &CkEthSetup { - fn http_query(&self, request: Vec) -> Result, UserError> { - self.env - .query(self.minter_id, "http_request", request) - .map(assert_reply) +impl PocketIcHttpQuery for &CkEthSetup { + fn get_pocket_ic(&self) -> &PocketIc { + &self.env + } + + fn get_canister_id(&self) -> CanisterId { + self.minter_id } } impl CkEthSetup { - pub fn new(env: Arc) -> Self { + pub fn new(env: Arc) -> Self { // Create minter canister first to match canister ID and Ethereum address hardcoded in tests. - let minter_id = - env.create_canister_with_cycles(None, Cycles::new(100_000_000_000_000), None); - let ledger_id = env.create_canister(None); - let evm_rpc_id = env.create_canister(None); - - env.install_existing_canister( + let minter_id = env.create_canister(); + env.add_cycles(minter_id, u128::MAX); + let ledger_id = env.create_canister(); + env.add_cycles(ledger_id, u128::MAX); + let evm_rpc_id = env.create_canister(); + env.add_cycles(evm_rpc_id, u128::MAX); + + env.install_canister( ledger_id, ledger_wasm(), Encode!(&LedgerArgument::Init( LedgerInitArgsBuilder::with_symbol_and_name("ckETH", "ckETH") - .with_minting_account(minter_id.get().0) + .with_minting_account(minter_id) .with_transfer_fee(CKETH_TRANSFER_FEE) .with_max_memo_length(80) .with_decimals(18) @@ -157,27 +168,20 @@ impl CkEthSetup { .build(), )) .unwrap(), - ) - .unwrap(); + None, + ); install_evm_rpc(&env, evm_rpc_id); let minter_id = install_minter(&env, ledger_id, minter_id, evm_rpc_id); let caller = PrincipalId::new_user_test_id(DEFAULT_PRINCIPAL_ID); - let cketh = Self { + Self { env, caller, ledger_id, minter_id, evm_rpc_id, support_subaccount: false, - }; - - assert_eq!( - Address::from_str(MINTER_ADDRESS).unwrap(), - Address::from_str(&cketh.minter_address()).unwrap() - ); - - cketh + } } pub fn add_support_for_subaccount(self) -> Self { @@ -192,16 +196,12 @@ impl CkEthSetup { pub fn minter_address(&self) -> String { Decode!( - &assert_reply( - self.env - .execute_ingress_as( - self.caller, - self.minter_id, - "minter_address", - Encode!().unwrap(), - ) - .expect("failed to get eth address") - ), + &assert_reply(self.env.update_call( + self.minter_id, + self.caller.into(), + "minter_address", + Encode!().unwrap(), + )), String ) .unwrap() @@ -209,16 +209,12 @@ impl CkEthSetup { pub fn retrieve_eth_status(&self, block_index: &Nat) -> RetrieveEthStatus { Decode!( - &assert_reply( - self.env - .execute_ingress_as( - self.caller, - self.minter_id, - "retrieve_eth_status", - Encode!(&block_index.0.to_u64().unwrap()).unwrap(), - ) - .expect("failed to get eth address") - ), + &assert_reply(self.env.update_call( + self.minter_id, + self.caller.into(), + "retrieve_eth_status", + Encode!(&block_index.0.to_u64().unwrap()).unwrap(), + )), RetrieveEthStatus ) .unwrap() @@ -229,16 +225,12 @@ impl CkEthSetup { parameter: &WithdrawalSearchParameter, ) -> Vec { Decode!( - &assert_reply( - self.env - .query_as( - self.caller, - self.minter_id, - "withdrawal_status", - Encode!(parameter).unwrap(), - ) - .expect("failed to get eth address") - ), + &assert_reply(self.env.query_call( + self.minter_id, + self.caller.into(), + "withdrawal_status", + Encode!(parameter).unwrap(), + )), Vec ) .unwrap() @@ -249,17 +241,14 @@ impl CkEthSetup { self.balance_of_ledger(ledger_id, account) } - pub fn balance_of_ledger(&self, ledger_id: CanisterId, account: impl Into) -> Nat { + pub fn balance_of_ledger(&self, ledger_id: Principal, account: impl Into) -> Nat { Decode!( - &assert_reply( - self.env - .query( - ledger_id, - "icrc1_balance_of", - Encode!(&account.into()).unwrap() - ) - .expect("failed to query balance on the ledger") - ), + &assert_reply(self.env.query_call( + ledger_id, + Principal::anonymous(), + "icrc1_balance_of", + Encode!(&account.into()).unwrap() + )), Nat ) .unwrap() @@ -268,15 +257,19 @@ impl CkEthSetup { pub fn eip_1559_transaction_price( &self, ledger_id: Option, - ) -> Result { + ) -> Result, RejectResponse> { let arg = match ledger_id { None => Encode!().unwrap(), Some(ckerc20_ledger_id) => { Encode!(&Some(Eip1559TransactionPriceArg { ckerc20_ledger_id })).unwrap() } }; - self.env - .query(self.minter_id, "eip_1559_transaction_price", arg) + self.env.query_call( + self.minter_id, + Principal::anonymous(), + "eip_1559_transaction_price", + arg, + ) } pub fn eip_1559_transaction_price_expecting_ok( @@ -284,7 +277,7 @@ impl CkEthSetup { ledger_id: Option, ) -> Eip1559TransactionPrice { Decode!( - &assert_reply(self.eip_1559_transaction_price(ledger_id).unwrap()), + &assert_reply(self.eip_1559_transaction_price(ledger_id)), Eip1559TransactionPrice ) .unwrap() @@ -294,7 +287,7 @@ impl CkEthSetup { let error = self .eip_1559_transaction_price(Some(principal_id)) .expect_err("Expecting Err but got Ok"); - assert!(error.description().contains(&format!( + assert!(error.reject_message.contains(&format!( "ERROR: Unsupported ckERC20 token ledger {principal_id}" ))); } @@ -303,31 +296,28 @@ impl CkEthSetup { &self, from: Principal, erc20: &AddCkErc20Token, - ) -> Result { - self.env.execute_ingress_as( - PrincipalId::from(from), + ) -> Result, RejectResponse> { + self.env.update_call( self.minter_id, + from, "add_ckerc20_token", Encode!(erc20).unwrap(), ) } pub fn add_ckerc20_token_expecting_ok(self, from: Principal, erc20: &AddCkErc20Token) -> Self { - Decode!( - &assert_reply(self.add_ckerc20_token(from, erc20).unwrap()), - () - ) - .unwrap(); + Decode!(&assert_reply(self.add_ckerc20_token(from, erc20)), ()).unwrap(); self } pub fn get_minter_info(&self) -> MinterInfo { Decode!( - &assert_reply( - self.env - .query(self.minter_id, "get_minter_info", Encode!().unwrap()) - .unwrap() - ), + &assert_reply(self.env.query_call( + self.minter_id, + Principal::anonymous(), + "get_minter_info", + Encode!().unwrap() + )), MinterInfo ) .unwrap() @@ -345,19 +335,19 @@ impl CkEthSetup { pub fn call_ledger_id_approve_minter( self, - ledger_id: CanisterId, + ledger_id: Principal, from: Principal, amount: u64, from_subaccount: Option<[u8; 32]>, ) -> ApprovalFlow { - let approval_response = Decode!(&assert_reply(self.env.execute_ingress_as( - PrincipalId::from(from), + let approval_response = Decode!(&assert_reply(self.env.update_call( ledger_id, + from, "icrc2_approve", Encode!(&ApproveArgs { from_subaccount, spender: Account { - owner: self.minter_id.into(), + owner: self.minter_id, subaccount: None }, amount: Nat::from(amount), @@ -367,7 +357,7 @@ impl CkEthSetup { memo: None, created_at_time: None, }).unwrap() - ).expect("failed to execute token transfer")), + )), Result ) .unwrap(); @@ -387,7 +377,7 @@ impl CkEthSetup { pub fn call_ledger_id_get_transaction>( self, - ledger_id: CanisterId, + ledger_id: Principal, ledger_index: T, ) -> LedgerTransactionAssert { let ledger_transaction = @@ -410,12 +400,15 @@ impl CkEthSetup { recipient, from_subaccount: from.subaccount, }; - let message_id = self.env.send_ingress( - PrincipalId::from(from.owner), - self.minter_id, - "withdraw_eth", - Encode!(&arg).expect("failed to encode withdraw args"), - ); + let message_id = self + .env + .submit_call( + self.minter_id, + from.owner, + "withdraw_eth", + Encode!(&arg).expect("failed to encode withdraw args"), + ) + .expect("failed to submit withdraw_eth call"); WithdrawalFlow { setup: self, message_id, @@ -430,11 +423,12 @@ impl CkEthSetup { body: serde_bytes::ByteBuf::new(), }; let response = Decode!( - &assert_reply( - self.env - .query(self.minter_id, "http_request", Encode!(&request).unwrap(),) - .expect("failed to get minter info") - ), + &assert_reply(self.env.query_call( + self.minter_id, + Principal::anonymous(), + "http_request", + Encode!(&request).unwrap(), + )), HttpResponse ) .unwrap(); @@ -465,15 +459,12 @@ impl CkEthSetup { use ic_cketh_minter::endpoints::events::GetEventsArg; Decode!( - &assert_reply( - self.env - .execute_ingress( - self.minter_id, - "get_events", - Encode!(&GetEventsArg { start, length }).unwrap(), - ) - .expect("failed to get minter info") - ), + &assert_reply(self.env.update_call( + self.minter_id, + Principal::anonymous(), + "get_events", + Encode!(&GetEventsArg { start, length }).unwrap(), + )), GetEventsResult ) .unwrap() @@ -495,11 +486,12 @@ impl CkEthSetup { fn check_audit_log(&self) { Decode!( - &assert_reply( - self.env - .query(self.minter_id, "check_audit_log", Encode!().unwrap()) - .unwrap(), - ), + &assert_reply(self.env.query_call( + self.minter_id, + Principal::anonymous(), + "check_audit_log", + Encode!().unwrap() + )), () ) .unwrap() @@ -512,29 +504,39 @@ impl CkEthSetup { self.minter_id, minter_wasm(), Encode!(&MinterArg::UpgradeArg(upgrade_arg)).unwrap(), + None, ) .unwrap(); self.start_minter(); } + pub fn submit_stop_minter(&self) -> RawMessageId { + self.env + .submit_call_with_effective_principal( + Principal::management_canister(), + RawEffectivePrincipal::CanisterId(self.minter_id.as_slice().to_vec()), + Principal::anonymous(), + "stop_canister", + Encode!(&CanisterIdRecord { + canister_id: self.minter_id + }) + .unwrap(), + ) + .expect("failed to submit stop_canister call") + } + /// Try to stop the minter without first stopping the ongoing HTTPS outcalls. Assert that the - /// `IngressStatus` is `Processing`. + /// call is still processing (i.e. blocked on the open call contexts for those outcalls). pub fn try_stop_minter_without_stopping_ongoing_https_outcalls(&self) { - const MAX_TICKS: u64 = 10; - let stop_msg_id = self.env.stop_canister_non_blocking(self.minter_id); - let mut ingress_status = self.env.ingress_status(&stop_msg_id); - for _ in 0..MAX_TICKS { - if let IngressStatus::Known { state, .. } = &ingress_status - && state == &IngressState::Processing - { - return; - } - self.env.tick(); - ingress_status = self.env.ingress_status(&stop_msg_id); - } - panic!( - "expected minter ingress status to be `Processing`, ended up with {:?}", - ingress_status + let stop_msg_id = self.submit_stop_minter(); + // The stop request only takes effect in the next round, and the outcalls the + // minter's timers issue in that last running round become visible only after + // it, so a drain placed before this tick would run too early and leave them + // pending forever. + self.env.tick(); + assert!( + self.env.ingress_status(stop_msg_id).is_none(), + "expected the minter's stop_canister call to still be processing after one tick" ); } @@ -545,47 +547,63 @@ impl CkEthSetup { if status == expected_canister_status { break; } + self.env.tick(); status = self.minter_status(); } assert_eq!(status, expected_canister_status); } pub fn stop_minter(&self) { - let stop_msg_id = self.env.stop_canister_non_blocking(self.minter_id); + let stop_msg_id = self.submit_stop_minter(); + self.env.tick(); self.stop_ongoing_https_outcalls(); - let stop_res = self.env.await_ingress(stop_msg_id, 100); - assert_matches!(stop_res, Ok(WasmResult::Reply(_))); + let stop_res = self.env.await_call(stop_msg_id); + assert_matches!(stop_res, Ok(_)); } pub fn stop_ongoing_https_outcalls(&self) { - let server_error_response = CanisterHttpResponsePayload { - status: 500_u128, - headers: vec![], - body: vec![], - }; - let ongoing_https_outcalls: Vec<_> = self - .env - .canister_http_request_contexts() - .into_keys() - .collect(); - let mut payload = PayloadBuilder::new(); - for callback_id in ongoing_https_outcalls { - payload = payload.http_response(callback_id, &server_error_response); + for request in self.env.get_canister_http() { + reply_500(&self.env, &request); } - self.env.execute_payload(payload); + self.env.tick(); } pub fn start_minter(&self) { - let start_res = self.env.start_canister(self.minter_id); - assert_matches!(start_res, Ok(WasmResult::Reply(_))); + let start_res = self.env.start_canister(self.minter_id, None); + assert_matches!(start_res, Ok(())); + } + + /// Advancing past the canister http timeout fails every outcall in flight, but the + /// requests stay listed until a round processes the failures, and the cycle awaiting + /// them keeps holding its TimerGuard until it learns of them. A stub could then bind + /// to a request that is already dead, and the firing due at the new time would be + /// dropped as AlreadyProcessing. Do to those outcalls up front, and deterministically, + /// what the advance would have done to them anyway. + pub fn advance_time(&self, duration: Duration) { + if duration > CANISTER_HTTP_TIMEOUT_INTERVAL { + self.fail_pending_https_outcalls(); + } + self.env.advance_time(duration); + } + + fn fail_pending_https_outcalls(&self) { + let pending = self.env.get_canister_http(); + // Ticking with nothing in flight would let the timers of a fixture that has + // executed no round fire early, which the flows rely on doing themselves. + if pending.is_empty() { + return; + } + for request in &pending { + fail_as_timed_out(&self.env, request); + } + self.env.tick(); } pub fn minter_status(&self) -> CanisterStatusType { self.env - .canister_status(self.minter_id) - .unwrap() + .canister_status(self.minter_id, None) .unwrap() - .status() + .status } pub fn upgrade_minter_to_add_orchestrator_id(self, orchestrator_id: Principal) -> Self { @@ -631,11 +649,11 @@ impl CkEthSetup { for _ in 0..MAX_TICKS { if let Some(unexpected_request) = self .env - .canister_http_request_contexts() - .values() - .map(|context| { + .get_canister_http() + .into_iter() + .map(|request| { crate::mock::JsonRpcRequest::from_str( - std::str::from_utf8(&context.body.clone().unwrap()).unwrap(), + std::str::from_utf8(&request.body).unwrap(), ) .expect("BUG: invalid JSON RPC method") }) @@ -695,17 +713,16 @@ impl CkEthSetup { ) -> DecodeLedgerMemoResult { Decode!( &assert_reply( - self.env - .query( - self.minter_id, - "decode_ledger_memo", - Encode!(&DecodeLedgerMemoArgs { - memo_type, - encoded_memo - }) - .unwrap() - ) - .expect("failed to call decode_ledger_memo") + self.env.query_call( + self.minter_id, + Principal::anonymous(), + "decode_ledger_memo", + Encode!(&DecodeLedgerMemoArgs { + memo_type, + encoded_memo + }) + .unwrap() + ) ), DecodeLedgerMemoResult ) @@ -713,10 +730,12 @@ impl CkEthSetup { } pub fn minter_canister_logs(&self) -> Vec { - let log = self.env.canister_log(self.minter_id); + let mut records = self + .env + .fetch_canister_logs(self.minter_id, Principal::anonymous()) + .expect("failed to fetch canister logs"); - let mut records = log.records().iter().collect::>(); - records.sort_by_key(|a| a.idx); + records.sort_by_key(|record| record.idx); records .into_iter() .map(|log| CanisterLog { @@ -732,10 +751,13 @@ pub fn format_ethereum_address_to_eip_55(address: &str) -> String { Address::from_str(address).unwrap().to_string() } -fn new_state_machine() -> StateMachine { - StateMachineBuilder::new() - .with_master_ecdsa_public_key() - .with_default_canister_range() +pub fn new_pocket_ic() -> PocketIc { + PocketIcBuilder::new() + .with_fiduciary_subnet() + .with_icp_config(IcpConfig { + canister_execution_rate_limiting: Some(IcpConfigFlag::Disabled), + ..Default::default() + }) .build() } @@ -770,41 +792,64 @@ fn evm_rpc_wasm() -> Vec { } fn install_minter( - env: &StateMachine, - ledger_id: CanisterId, - minter_id: CanisterId, - evm_rpc_id: CanisterId, -) -> CanisterId { + env: &PocketIc, + ledger_id: Principal, + minter_id: Principal, + evm_rpc_id: Principal, +) -> Principal { let args = MinterInitArgs { - ecdsa_key_name: "master_ecdsa_public_key".parse().unwrap(), + ecdsa_key_name: "key_1".parse().unwrap(), ethereum_network: EthereumNetwork::Mainnet, - ledger_id: ledger_id.get().0, + ledger_id, next_transaction_nonce: 0_u8.into(), ethereum_block_height: CandidBlockTag::Finalized, ethereum_contract_address: Some(ETH_HELPER_CONTRACT_ADDRESS.to_string()), minimum_withdrawal_amount: CKETH_MINIMUM_WITHDRAWAL_AMOUNT.into(), last_scraped_block_number: LAST_SCRAPED_BLOCK_NUMBER_AT_INSTALL.into(), - evm_rpc_id: Some(evm_rpc_id.into()), + evm_rpc_id: Some(evm_rpc_id), }; let minter_arg = MinterArg::InitArg(args); - env.install_existing_canister(minter_id, minter_wasm(), Encode!(&minter_arg).unwrap()) - .unwrap(); + env.install_canister( + minter_id, + minter_wasm(), + Encode!(&minter_arg).unwrap(), + None, + ); minter_id } -fn install_evm_rpc(env: &StateMachine, evm_rpc_id: CanisterId) { +fn install_evm_rpc(env: &PocketIc, evm_rpc_id: Principal) { let args = evm_rpc_types::InstallArgs::default(); - env.install_existing_canister(evm_rpc_id, evm_rpc_wasm(), Encode!(&args).unwrap()) - .unwrap(); + env.install_canister(evm_rpc_id, evm_rpc_wasm(), Encode!(&args).unwrap(), None); } -fn assert_reply(result: WasmResult) -> Vec { - match result { - WasmResult::Reply(bytes) => bytes, - WasmResult::Reject(reject) => { - panic!("Expected a successful reply, got a reject: {reject}") - } - } +fn fail_as_timed_out(env: &PocketIc, request: &CanisterHttpRequest) { + env.mock_canister_http_response(MockCanisterHttpResponse { + subnet_id: request.subnet_id, + request_id: request.request_id, + response: CanisterHttpResponse::CanisterHttpReject(CanisterHttpReject { + reject_code: REJECT_CODE_SYS_TRANSIENT, + message: "Canister http request timed out".to_string(), + }), + additional_responses: vec![], + }); +} + +fn reply_500(env: &PocketIc, request: &CanisterHttpRequest) { + env.mock_canister_http_response(MockCanisterHttpResponse { + subnet_id: request.subnet_id, + request_id: request.request_id, + response: CanisterHttpResponse::CanisterHttpReply(CanisterHttpReply { + status: 500, + headers: vec![], + body: vec![], + }), + additional_responses: vec![], + }); +} + +fn assert_reply(result: Result, RejectResponse>) -> Vec { + result.unwrap_or_else(|reject| panic!("Expected a successful reply, got a reject: {reject}")) } pub struct LedgerBalance { diff --git a/rs/ethereum/cketh/test_utils/src/mock.rs b/rs/ethereum/cketh/test_utils/src/mock.rs index 693b98a36f54..0da83474b0d0 100644 --- a/rs/ethereum/cketh/test_utils/src/mock.rs +++ b/rs/ethereum/cketh/test_utils/src/mock.rs @@ -1,11 +1,9 @@ -use crate::{CkEthSetup, JsonRpcProvider, MAX_TICKS, assert_reply}; -use candid::{Decode, Encode}; -use ic_cdk::management_canister::{HttpRequestResult as OutCallHttpResponse, TransformArgs}; -use ic_error_types::RejectCode; -use ic_management_canister_types_private::CanisterHttpResponsePayload; -use ic_state_machine_tests::{PayloadBuilder, StateMachine}; -use ic_types::canister_http::{CanisterHttpMethod, CanisterHttpRequestContext}; -use ic_types::messages::CallbackId; +use crate::{CkEthSetup, JsonRpcProvider, MAX_TICKS}; +use pocket_ic::PocketIc; +use pocket_ic::common::rest::{ + CanisterHttpMethod, CanisterHttpReject, CanisterHttpReply, CanisterHttpRequest, + CanisterHttpResponse, MockCanisterHttpResponse, +}; use serde::Serialize; use serde::de::DeserializeOwned; use serde_json::json; @@ -15,7 +13,7 @@ use std::time::Duration; use strum::IntoEnumIterator; trait Matcher { - fn matches(&self, context: &CanisterHttpRequestContext) -> bool; + fn matches(&self, request: &CanisterHttpRequest) -> bool; } pub struct MockJsonRpcProviders { @@ -113,12 +111,12 @@ impl JsonRpcRequestMatcher { self } - fn tick_until_next_http_request(&self, env: &StateMachine) { + fn tick_until_next_http_request(&self, env: &PocketIc) { for _ in 0..MAX_TICKS { let has_matching_request = env - .canister_http_request_contexts() - .values() - .any(|context| self.matches(context)); + .get_canister_http() + .iter() + .any(|request| self.matches(request)); if has_matching_request { break; } @@ -127,38 +125,31 @@ impl JsonRpcRequestMatcher { } } - pub fn find_rpc_call( - &self, - env: &StateMachine, - ) -> Option<(CallbackId, CanisterHttpRequestContext)> { + pub fn find_rpc_call(&self, env: &PocketIc) -> Option { self.tick_until_next_http_request(env); - env.canister_http_request_contexts() + env.get_canister_http() .into_iter() - .find(|(_id, context)| self.matches(context)) + .find(|request| self.matches(request)) } } impl Matcher for JsonRpcRequestMatcher { - fn matches(&self, context: &CanisterHttpRequestContext) -> bool { - let has_json_content_type_header = context.headers.iter().any(|header| { + fn matches(&self, request: &CanisterHttpRequest) -> bool { + let has_json_content_type_header = request.headers.iter().any(|header| { header.name.to_lowercase() == "content-type" && header.value == "application/json" }); let has_expected_max_response_bytes = - match (self.max_response_bytes, context.max_response_bytes) { - (Some(expected), Some(actual)) => expected == actual.get(), + match (self.max_response_bytes, request.max_response_bytes) { + (Some(expected), Some(actual)) => expected == actual, (Some(_), None) => false, (None, _) => true, }; - let request_body = context - .body - .as_ref() - .map(|body| std::str::from_utf8(body).unwrap()) - .expect("BUG: missing request body"); + let request_body = std::str::from_utf8(&request.body).unwrap(); let json_rpc_request = JsonRpcRequest::from_str(request_body).expect("BUG: invalid JSON RPC request"); - self.http_method == context.http_method - && self.provider.url() == context.url + self.http_method == request.http_method + && self.provider.url() == request.url && has_expected_max_response_bytes && has_json_content_type_header && self.json_rpc_method == json_rpc_request.method @@ -177,14 +168,17 @@ struct StubOnce { } impl StubOnce { - fn expect_no_matching_rpc_call(self, env: &StateMachine) { - if let Some((id, _)) = self.matcher.find_rpc_call(env) { - panic!("expect no request matching the stub {self:?} but found one {id}"); + fn expect_no_matching_rpc_call(self, env: &PocketIc) { + if let Some(request) = self.matcher.find_rpc_call(env) { + panic!( + "expect no request matching the stub {self:?} but found one {}", + request.request_id + ); } } - fn expect_rpc_call(self, env: &StateMachine) { - let (id, context) = self.matcher.find_rpc_call(env).unwrap_or_else(|| { + fn expect_rpc_call(self, env: &PocketIc) { + let request = self.matcher.find_rpc_call(env).unwrap_or_else(|| { panic!( "no request found matching the stub {:?}. Current requests {}", self, @@ -192,11 +186,7 @@ impl StubOnce { ) }); let request_id = { - let request_body = context - .body - .as_ref() - .map(|body| std::str::from_utf8(body).unwrap()) - .expect("BUG: missing request body"); + let request_body = std::str::from_utf8(&request.body).unwrap(); JsonRpcRequest::from_str(request_body) .expect("BUG: invalid JSON RPC request") .id @@ -209,80 +199,37 @@ impl StubOnce { })) .unwrap(); - if let Some(max_response_bytes) = context.max_response_bytes - && (response_body.len() as u64) > max_response_bytes.get() - { - let mut payload = PayloadBuilder::new(); - payload = payload.http_response_failure( - id, - RejectCode::SysFatal, - format!("Http body exceeds size limit of {max_response_bytes} bytes."), - ); - env.execute_payload(payload); - return; - } - - let clean_up_context = match context.transform.clone() { - Some(transform) => transform.context, - None => vec![], - }; - let transform_arg = TransformArgs { - response: OutCallHttpResponse { - status: 200_u8.into(), + let response = match request.max_response_bytes { + Some(max_response_bytes) if (response_body.len() as u64) > max_response_bytes => { + CanisterHttpResponse::CanisterHttpReject(CanisterHttpReject { + reject_code: crate::REJECT_CODE_SYS_FATAL, + message: format!("Http body exceeds size limit of {max_response_bytes} bytes."), + }) + } + _ => CanisterHttpResponse::CanisterHttpReply(CanisterHttpReply { + status: 200, headers: vec![], body: response_body, - }, - context: clean_up_context.to_vec(), + }), }; - let canister_id_cleanup_response = context.request.sender; - let clean_up_response = Decode!( - &assert_reply( - env.execute_ingress( - canister_id_cleanup_response, - "cleanup_response", - Encode!(&transform_arg).unwrap(), - ) - .expect("failed to query transform http response") - ), - OutCallHttpResponse - ) - .unwrap(); - if let Some(max_response_bytes) = context.max_response_bytes - && (clean_up_response.body.len() as u64) > max_response_bytes.get() - { - let mut payload = PayloadBuilder::new(); - payload = payload.http_response_failure( - id, - RejectCode::SysFatal, - format!("Http body exceeds size limit of {max_response_bytes} bytes."), - ); - env.execute_payload(payload); - return; - } - - let http_response = CanisterHttpResponsePayload { - status: 200_u128, - headers: vec![], - body: clean_up_response.body, - }; - let mut payload = PayloadBuilder::new(); - payload = payload.http_response(id, &http_response); - env.execute_payload(payload); + env.mock_canister_http_response(MockCanisterHttpResponse { + subnet_id: request.subnet_id, + request_id: request.request_id, + response, + additional_responses: vec![], + }); + env.tick(); } } -pub fn debug_http_outcalls(env: &StateMachine) -> String { +pub fn debug_http_outcalls(env: &PocketIc) -> String { let mut debug_str = vec![]; - for context in env.canister_http_request_contexts().values() { - let request_body = context - .body - .as_ref() - .map(|body| std::str::from_utf8(body).unwrap()) - .expect("BUG: missing request body"); + for request in env.get_canister_http() { + let request_body = std::str::from_utf8(&request.body).unwrap(); debug_str.push(format!( "{:?} {} (max_response_bytes={:?}) {}", - context.http_method, context.url, context.max_response_bytes, request_body + request.http_method, request.url, request.max_response_bytes, request_body )); } debug_str.join("\n") diff --git a/rs/ethereum/cketh/test_utils/src/response.rs b/rs/ethereum/cketh/test_utils/src/response.rs index 18c6d6ce1433..2ea1f0642b5f 100644 --- a/rs/ethereum/cketh/test_utils/src/response.rs +++ b/rs/ethereum/cketh/test_utils/src/response.rs @@ -153,11 +153,11 @@ pub fn default_signed_eip_1559_transaction() -> ( .chain_id(1_u64); let sig = ethers_core::types::Signature { r: ethers_core::types::U256::from_dec_str( - "80728915039673634151963281987194499535727562641034879173530654129915839382129", + "78378320896144165623306772901883835881146801392437857873186382435197931981331", ) .unwrap(), s: ethers_core::types::U256::from_dec_str( - "54281815563936592133007646348951747532427232100340298742740287107883437683286", + "11573774696968647626294885286453366440757932248598190533848223153778081228091", ) .unwrap(), v: 1, @@ -183,12 +183,12 @@ pub fn default_erc20_signed_eip_1559_transaction() -> ( .chain_id(1_u64); let sig = ethers_core::types::Signature { r: ethers_core::types::U256::from_str_radix( - "bb694aec6175b489523a55d5fce39452368e97096d4afa2cdcc35cf2d805152f", + "da4f476ede0aaf7da633371a938d5e2525a65a23699b55761779871a313f8cb3", 16, ) .unwrap(), s: ethers_core::types::U256::from_str_radix( - "0112b26a028af84dd397d23549844efdaf761d90cdcfdbe6c3608239648a85a3", + "45833d409eba50e3e9b145d04ea294ee791c14465503818f8b325a881938ddc1", 16, ) .unwrap(), diff --git a/rs/ethereum/ledger-suite-orchestrator/test_utils/BUILD.bazel b/rs/ethereum/ledger-suite-orchestrator/test_utils/BUILD.bazel index 314e6cb73fc9..3825cceae4b5 100644 --- a/rs/ethereum/ledger-suite-orchestrator/test_utils/BUILD.bazel +++ b/rs/ethereum/ledger-suite-orchestrator/test_utils/BUILD.bazel @@ -16,22 +16,16 @@ rust_library( version = "0.1.0", deps = [ # Keep sorted. - "//packages/ic-http-types", "//packages/ic-metrics-assert:ic-metrics-assert_pocket_ic", "//packages/icrc-ledger-types:icrc_ledger_types_storable", "//packages/pocket-ic", "//rs/ethereum/ledger-suite-orchestrator:ledger_suite_orchestrator", "//rs/ledger_suite/icrc1/ledger", - "//rs/state_machine_tests", "//rs/types/base_types", "//rs/types/cycles", - "//rs/types/management_canister_types", - "//rs/types/types", "//rs/universal_canister/lib", - "@crate_index//:assert_matches", "@crate_index//:candid", "@crate_index//:ic-cdk", "@crate_index//:ic-management-canister-types", - "@crate_index//:proptest", ], ) diff --git a/rs/ethereum/ledger-suite-orchestrator/test_utils/Cargo.toml b/rs/ethereum/ledger-suite-orchestrator/test_utils/Cargo.toml index 5280672a5d4b..b056d937f3ea 100644 --- a/rs/ethereum/ledger-suite-orchestrator/test_utils/Cargo.toml +++ b/rs/ethereum/ledger-suite-orchestrator/test_utils/Cargo.toml @@ -7,22 +7,15 @@ edition.workspace = true documentation.workspace = true [dependencies] -assert_matches = { workspace = true } candid = { workspace = true } ic-base-types = { path = "../../../types/base_types" } ic-cdk = { workspace = true } -ic-http-types = { path = "../../../../packages/ic-http-types" } ic-icrc1-ledger = { path = "../../../ledger_suite/icrc1/ledger" } ic-ledger-suite-orchestrator = { path = "../../ledger-suite-orchestrator" } ic-management-canister-types = { workspace = true } -ic-management-canister-types-private = { path = "../../../types/management_canister_types" } ic-metrics-assert = { path = "../../../../packages/ic-metrics-assert", features = ["pocket_ic"] } -ic-state-machine-tests = { path = "../../../state_machine_tests" } -ic-test-utilities-load-wasm = { path = "../../../test_utilities/load_wasm" } ic-types-cycles = { path = "../../../types/cycles" } -ic-types = { path = "../../../types/types" } ic-universal-canister = { path = "../../../universal_canister/lib" } icrc-ledger-types = { path = "../../../../packages/icrc-ledger-types" } paste = { workspace = true } pocket-ic = { path = "../../../../packages/pocket-ic" } -proptest = { workspace = true } diff --git a/rs/ethereum/ledger-suite-orchestrator/test_utils/src/flow.rs b/rs/ethereum/ledger-suite-orchestrator/test_utils/src/flow.rs deleted file mode 100644 index 3bc0e064cbdf..000000000000 --- a/rs/ethereum/ledger-suite-orchestrator/test_utils/src/flow.rs +++ /dev/null @@ -1,459 +0,0 @@ -use crate::universal_canister::UniversalCanister; -use crate::{ - LedgerAccount, LedgerMetadataKey, LedgerMetadataValue, LedgerSuiteOrchestrator, - MINTER_PRINCIPAL, assert_reply, ledger_wasm, out_of_band_upgrade, stop_canister, -}; -use candid::{Decode, Encode, Nat, Principal}; -use ic_base_types::{CanisterId, PrincipalId}; -use ic_icrc1_ledger::ChangeArchiveOptions; -use ic_ledger_suite_orchestrator::candid::{AddErc20Arg, ManagedCanisterIds}; -use ic_ledger_suite_orchestrator::state::{IndexWasm, LedgerWasm}; -use ic_management_canister_types::{CanisterInfoResult, CanisterInstallMode, InstallCodeArgs}; -use ic_management_canister_types_private::CanisterStatusResultV2; -use ic_metrics_assert::{CanisterHttpQuery, MetricsAssert}; -use ic_state_machine_tests::{StateMachine, UserError}; -use icrc_ledger_types::icrc1::transfer::{TransferArg, TransferError}; -use icrc_ledger_types::icrc3::archive::ArchiveInfo; -use icrc_ledger_types::icrc3::blocks::{GetBlocksRequest, GetBlocksResult}; -use std::collections::BTreeSet; - -pub struct AddErc20TokenFlow { - pub setup: LedgerSuiteOrchestrator, - pub params: AddErc20Arg, -} - -impl AddErc20TokenFlow { - pub fn expect_new_ledger_and_index_canisters(self) -> ManagedCanistersAssert { - let contract = self.params.contract; - let canister_ids = self.setup.wait_for(|| { - match self.setup.call_orchestrator_canister_ids(&contract) { - Some(ids) if ids.ledger.is_some() && ids.index.is_some() => Ok(ids), - incomplete_ids => Err(format!( - "Not all canister IDs are available for ERC-20 {contract:?}: {incomplete_ids:?}" - )), - } - }); - assert_ne!( - canister_ids.ledger, canister_ids.index, - "BUG: ledger and index canister IDs MUST be different" - ); - - self.setup - .wait_for_canister_to_be_installed_and_running(canister_ids.ledger.unwrap()); - self.setup - .wait_for_canister_to_be_installed_and_running(canister_ids.index.unwrap()); - - ManagedCanistersAssert { - setup: self.setup, - canister_ids, - } - } -} - -pub struct ManagedCanistersAssert { - pub setup: LedgerSuiteOrchestrator, - pub canister_ids: ManagedCanisterIds, -} - -impl AsRef for ManagedCanistersAssert { - fn as_ref(&self) -> &StateMachine { - self.setup.env.as_ref() - } -} - -impl ManagedCanistersAssert { - pub fn assert_all_controlled_by(self, expected_controllers: &[Principal]) -> Self { - for canister_id in self.all_canister_ids() { - assert_eq!( - self.setup - .canister_status_of(canister_id) - .settings() - .controllers() - .into_iter() - .map(|p| p.0) - .collect::>(), - expected_controllers - .iter() - .copied() - .collect::>(), // convert to set to ignore order - "BUG: unexpected controller for canister {} in managed canisters {}", - canister_id, - self.canister_ids - ); - } - self - } - - pub fn check_metrics(self) -> MetricsAssert { - MetricsAssert::from_http_query(self) - } - - pub fn trigger_creation_of_archive(self) -> Self { - const ARCHIVE_TRIGGER_THRESHOLD: usize = 10; - - // The productive value for `trigger_threshold` is `2_000`, - // which would require `2_000` transfers to trigger the creation of an archive, - // which would take in the order of 20s (order of magnitude is 10ms per transfer with state machine tests). - // We set this value to an artificially low number to speed up the test. - self.upgrade_ledger_to_change_archive_options(ChangeArchiveOptions { - trigger_threshold: Some(ARCHIVE_TRIGGER_THRESHOLD), - ..Default::default() - }); - let archive_ids_before: BTreeSet<_> = self - .call_ledger_archives() - .into_iter() - .map(|info| info.canister_id) - .collect(); - - for _i in 0..ARCHIVE_TRIGGER_THRESHOLD { - let from = MINTER_PRINCIPAL; - let to = Principal::management_canister(); - self.call_ledger_icrc1_transfer( - from, - &TransferArg { - from_subaccount: None, - to: to.into(), - fee: None, - created_at_time: None, - memo: None, - amount: Nat::from(1_u8), - }, - ) - .expect("BUG: fail to make a transfer to trigger archive creation"); - } - - let archive_ids_after: BTreeSet<_> = self - .call_ledger_archives() - .into_iter() - .map(|info| info.canister_id) - .collect(); - assert_eq!( - archive_ids_after.len(), - archive_ids_before.len() + 1, - "BUG: expected one more archive canister" - ); - assert!(archive_ids_before.is_subset(&archive_ids_after)); - - Self { - setup: self.setup, - canister_ids: ManagedCanisterIds { - ledger: self.canister_ids.ledger, - index: self.canister_ids.index, - archives: Vec::from_iter(archive_ids_after), - }, - } - } - - pub fn assert_ledger_canister_info_satisfy bool>( - self, - caller: &UniversalCanister, - predicate: P, - ) -> Self { - let canister_info = caller.canister_info(self.ledger_canister_id()); - assert!( - predicate(&canister_info), - "BUG: ledger canister info does not satisfy predicate. Canister info: {canister_info:?}" - ); - self - } - - pub fn assert_index_canister_info_satisfy bool>( - self, - caller: &UniversalCanister, - predicate: P, - ) -> Self { - let canister_info = caller.canister_info(self.index_canister_id()); - assert!( - predicate(&canister_info), - "BUG: index canister info does not satisfy predicate. Canister info: {canister_info:?}" - ); - self - } - - pub fn assert_all_archive_canister_info_satisfy bool>( - self, - caller: &UniversalCanister, - predicate: P, - ) -> Self { - assert!( - !self.canister_ids.archives.is_empty(), - "BUG: no archive canisters" - ); - for archive in self.archive_canister_ids() { - let canister_info = caller.canister_info(archive); - assert!( - predicate(&canister_info), - "BUG: archive canister info does not satisfy predicate. Canister info: {canister_info:?}" - ); - } - self - } - - pub fn stop_ledger(self) -> Self { - stop_canister( - &self, - self.setup.ledger_suite_orchestrator_id, - self.ledger_canister_id(), - ); - self - } - - pub fn stop_index(self) -> Self { - stop_canister( - &self, - self.setup.ledger_suite_orchestrator_id, - self.index_canister_id(), - ); - self - } - - pub fn ledger_out_of_band_upgrade>( - self, - controller: T, - wasm: LedgerWasm, - ) -> Self { - out_of_band_upgrade( - &self, - controller.into(), - self.ledger_canister_id(), - wasm.to_bytes(), - ) - .expect("failed to upgrade ledger canister"); - self - } - - pub fn index_out_of_band_upgrade>( - self, - controller: T, - wasm: IndexWasm, - ) -> Self { - out_of_band_upgrade( - &self, - controller.into(), - self.index_canister_id(), - wasm.to_bytes(), - ) - .expect("failed to upgrade index canister"); - self - } - - pub fn call_ledger_icrc1_transfer( - &self, - from: Principal, - arg: &TransferArg, - ) -> Result { - Decode!( - &self.setup.env.execute_ingress_as( - PrincipalId(from), - self.ledger_canister_id(), - "icrc1_transfer", - Encode!(arg) - .unwrap() - ) - .expect("failed to transfer funds") - .bytes(), - Result - ) - .expect("failed to decode transfer response") - } - - pub fn upgrade_ledger_to_change_archive_options(&self, archive_options: ChangeArchiveOptions) { - use ic_icrc1_ledger::{LedgerArgument, UpgradeArgs as LedgerUpgradeArgs}; - - let module_hash_before = self - .ledger_canister_status() - .module_hash() - .expect("BUG: ledger is not installed"); - - let upgrade_args = Some(LedgerArgument::Upgrade(Some(LedgerUpgradeArgs { - change_archive_options: Some(archive_options), - ..Default::default() - }))); - let res = self.setup.env.execute_ingress_as( - self.setup.ledger_suite_orchestrator_id.into(), - CanisterId::ic_00(), - "install_code", - Encode!(&InstallCodeArgs { - mode: CanisterInstallMode::Upgrade(None), - canister_id: self.ledger_canister_id().into(), - wasm_module: ledger_wasm().to_bytes(), - arg: Encode!(&upgrade_args).unwrap(), - sender_canister_version: None, - }) - .unwrap(), - ); - assert_reply(res.unwrap()); - let module_hash_after = self - .ledger_canister_status() - .module_hash() - .expect("BUG: ledger is not installed"); - assert_eq!( - module_hash_before, module_hash_after, - "BUG: ledger wasm hash changed when changing archive options" - ); - } - - pub fn call_ledger_icrc3_get_blocks(&self, request: &Vec) -> GetBlocksResult { - Decode!( - &self - .setup - .env - .execute_ingress( - self.ledger_canister_id(), - "icrc3_get_blocks", - Encode!(request).unwrap() - ) - .expect("failed to call icrc3_get_blocks") - .bytes(), - GetBlocksResult - ) - .expect("failed to decode icrc3_get_blocks response") - } - - fn call_ledger_archives(&self) -> Vec { - Decode!( - &self - .setup - .env - .query(self.ledger_canister_id(), "archives", Encode!().unwrap()) - .expect("failed to query archives") - .bytes(), - Vec - ) - .expect("failed to decode archives response") - } - - pub fn assert_index_has_correct_ledger_id(self) -> Self { - assert_eq!( - self.call_index_ledger_id(), - self.canister_ids.ledger.unwrap() - ); - self - } - - pub fn ledger_canister_status(&self) -> CanisterStatusResultV2 { - self.setup.canister_status_of(self.ledger_canister_id()) - } - - pub fn assert_ledger_has_cycles(self, expected: u128) -> Self { - assert_eq!(self.ledger_canister_status().cycles(), expected); - self - } - - pub fn assert_index_has_cycles(self, expected: u128) -> Self { - assert_eq!( - self.setup - .canister_status_of(self.index_canister_id()) - .cycles(), - expected - ); - self - } - - pub fn assert_all_archives_have_cycles(self, expected: u128) -> Self { - assert!( - !self.archive_canister_ids().is_empty(), - "BUG: no archive canisters" - ); - for archive in self.archive_canister_ids() { - assert_eq!(self.setup.canister_status_of(archive).cycles(), expected); - } - self - } - - pub fn assert_ledger_has_wasm_hash>(self, expected: T) -> Self { - assert_eq!( - self.ledger_canister_status().module_hash(), - Some(expected.as_ref().to_vec()), - "BUG: unexpected wasm hash for ledger canister" - ); - self - } - - pub fn assert_index_has_wasm_hash>(self, expected: T) -> Self { - assert_eq!( - self.setup - .canister_status_of(self.index_canister_id()) - .module_hash(), - Some(expected.as_ref().to_vec()), - "BUG: unexpected wasm hash for index canister" - ); - self - } - - fn call_index_ledger_id(&self) -> Principal { - Decode!( - &assert_reply( - self.setup - .env - .query(self.index_canister_id(), "ledger_id", Encode!().unwrap()) - .expect("failed to query get_transactions on the ledger") - ), - Principal - ) - .unwrap() - } - pub fn ledger_canister_id(&self) -> CanisterId { - CanisterId::unchecked_from_principal(PrincipalId::from(self.canister_ids.ledger.unwrap())) - } - - pub fn index_canister_id(&self) -> CanisterId { - CanisterId::unchecked_from_principal(PrincipalId::from(self.canister_ids.index.unwrap())) - } - - pub fn archive_canister_ids(&self) -> Vec { - self.canister_ids - .archives - .iter() - .map(|p| CanisterId::unchecked_from_principal(PrincipalId::from(*p))) - .collect() - } - - fn all_canister_ids(&self) -> Vec { - vec![self.ledger_canister_id(), self.index_canister_id()] - .into_iter() - .chain(self.archive_canister_ids()) - .collect() - } -} - -impl CanisterHttpQuery for ManagedCanistersAssert { - fn http_query(&self, request: Vec) -> Result, UserError> { - self.setup.http_query(request) - } -} - -macro_rules! assert_ledger { - ($name:expr_2021, $ty:ty) => { - paste::paste! { - pub fn [](env: &ic_state_machine_tests::StateMachine, ledger_canister_id: ic_base_types::CanisterId) -> $ty { - candid::Decode!( - &assert_reply( - env - .query(ledger_canister_id, $name, candid::Encode!().unwrap()) - .expect("failed to query on the ledger") - ), - $ty - ) - .unwrap() - } - impl ManagedCanistersAssert { - pub fn []>(self, expected: T) -> Self { - assert_eq!([](&self.setup.env, self.ledger_canister_id()), expected.into(), "BUG: unexpected value for ledger {}", stringify!($name)); - self - } - } - } - }; -} - -assert_ledger!("icrc1_name", String); -assert_ledger!("icrc1_symbol", String); -assert_ledger!("icrc1_decimals", u8); -assert_ledger!("icrc1_total_supply", Nat); -assert_ledger!("icrc1_fee", Nat); -assert_ledger!("icrc1_minting_account", Option); -assert_ledger!( - "icrc1_metadata", - Vec<(LedgerMetadataKey, LedgerMetadataValue)> -); diff --git a/rs/ethereum/ledger-suite-orchestrator/test_utils/src/lib.rs b/rs/ethereum/ledger-suite-orchestrator/test_utils/src/lib.rs index cbaecc4aa206..d6758dc9f54e 100644 --- a/rs/ethereum/ledger-suite-orchestrator/test_utils/src/lib.rs +++ b/rs/ethereum/ledger-suite-orchestrator/test_utils/src/lib.rs @@ -1,28 +1,13 @@ -use crate::flow::{AddErc20TokenFlow, ManagedCanistersAssert}; -use assert_matches::assert_matches; -use candid::{Decode, Encode, Nat, Principal}; -use ic_base_types::{CanisterId, PrincipalId}; -use ic_cdk::management_canister::CanisterStatusResult; +use candid::{Nat, Principal}; use ic_ledger_suite_orchestrator::candid::{ - AddErc20Arg, CyclesManagement, Erc20Contract, InitArg, InstalledCanister, InstalledLedgerSuite, - LedgerInitArg, ManagedCanisterIds, OrchestratorArg, OrchestratorInfo, UpgradeArg, + AddErc20Arg, Erc20Contract, InitArg, InstalledCanister, InstalledLedgerSuite, LedgerInitArg, }; -use ic_ledger_suite_orchestrator::state::{ - ArchiveWasm, IndexWasm, LedgerSuiteVersion, LedgerWasm, Wasm, WasmHash, -}; -use ic_management_canister_types::{CanisterInstallMode, InstallCodeArgs}; -use ic_management_canister_types_private::{CanisterStatusResultV2, CanisterStatusType}; -use ic_metrics_assert::{CanisterHttpQuery, MetricsAssert}; -use ic_state_machine_tests::{StateMachine, StateMachineBuilder, UserError, WasmResult}; -use ic_types_cycles::Cycles; +use ic_ledger_suite_orchestrator::state::{ArchiveWasm, IndexWasm, LedgerWasm, Wasm}; pub use icrc_ledger_types::icrc::generic_metadata_value::MetadataValue as LedgerMetadataValue; pub use icrc_ledger_types::icrc::metadata_key::MetadataKey as LedgerMetadataKey; pub use icrc_ledger_types::icrc1::account::Account as LedgerAccount; -use std::sync::Arc; -pub mod flow; pub mod pocket_ic; -pub mod universal_canister; const MAX_TICKS: usize = 10; const GIT_COMMIT_HASH: &str = "6a8e5fca2c6b4e12966638c444e994e204b42989"; @@ -34,322 +19,6 @@ pub const NNS_ROOT_PRINCIPAL: Principal = Principal::from_slice(&[0_u8]); pub const MINTER_PRINCIPAL: Principal = Principal::from_slice(&[0_u8, 0, 0, 0, 2, 48, 0, 156, 1, 1]); -pub struct LedgerSuiteOrchestrator { - pub env: Arc, - pub ledger_suite_orchestrator_id: CanisterId, - pub ledger_suite_orchestrator_wasm: Vec, - pub embedded_ledger_wasm_hash: WasmHash, - pub embedded_index_wasm_hash: WasmHash, - pub embedded_archive_wasm_hash: WasmHash, -} - -impl Default for LedgerSuiteOrchestrator { - fn default() -> Self { - Self::new(Arc::new(new_state_machine()), default_init_arg()).register_embedded_wasms() - } -} - -impl AsRef for LedgerSuiteOrchestrator { - fn as_ref(&self) -> &StateMachine { - &self.env - } -} - -impl LedgerSuiteOrchestrator { - pub fn with_cycles_management(cycles_management: CyclesManagement) -> Self { - let init_arg = InitArg { - cycles_management: Some(cycles_management), - ..default_init_arg() - }; - Self::new(Arc::new(new_state_machine()), init_arg) - } - - pub fn new(env: Arc, init_arg: InitArg) -> Self { - let ledger_suite_orchestrator_id = - env.create_canister_with_cycles(None, Cycles::new(u128::MAX), None); - Self { - env, - ledger_suite_orchestrator_id, - ledger_suite_orchestrator_wasm: ledger_suite_orchestrator_wasm(), - embedded_ledger_wasm_hash: ledger_wasm().hash().clone(), - embedded_index_wasm_hash: index_wasm().hash().clone(), - embedded_archive_wasm_hash: archive_wasm().hash().clone(), - } - .install_ledger_suite_orchestrator(init_arg) - } - - pub fn new_with_ledger_get_blocks_disabled(env: Arc, init_arg: InitArg) -> Self { - let ledger_suite_orchestrator_id = - env.create_canister_with_cycles(None, Cycles::new(u128::MAX), None); - Self { - env, - ledger_suite_orchestrator_id, - ledger_suite_orchestrator_wasm: ledger_suite_orchestrator_get_blocks_disabled_wasm(), - embedded_ledger_wasm_hash: ledger_get_blocks_disabled_wasm().hash().clone(), - embedded_index_wasm_hash: index_wasm().hash().clone(), - embedded_archive_wasm_hash: archive_wasm().hash().clone(), - } - .install_ledger_suite_orchestrator(init_arg) - } - - fn install_ledger_suite_orchestrator(self, init_arg: InitArg) -> Self { - self.env - .install_existing_canister( - self.ledger_suite_orchestrator_id, - self.ledger_suite_orchestrator_wasm.clone(), - Encode!(&OrchestratorArg::InitArg(init_arg)).unwrap(), - ) - .unwrap(); - self - } - - pub fn upgrade_ledger_suite_orchestrator_expecting_ok( - self, - upgrade_arg: &OrchestratorArg, - ) -> Self { - self.upgrade_ledger_suite_orchestrator_with_same_wasm(upgrade_arg) - .expect("Failed to upgrade ledger suite orchestrator"); - self - } - - pub fn register_embedded_wasms(self) -> Self { - self.upgrade_ledger_suite_orchestrator_expecting_ok(&OrchestratorArg::UpgradeArg( - UpgradeArg { - git_commit_hash: Some(GIT_COMMIT_HASH.to_string()), - ledger_compressed_wasm_hash: None, - index_compressed_wasm_hash: None, - archive_compressed_wasm_hash: None, - cycles_management: None, - manage_ledger_suites: None, - }, - )) - } - - pub fn embedded_ledger_suite_version(&self) -> LedgerSuiteVersion { - LedgerSuiteVersion { - ledger_compressed_wasm_hash: self.embedded_ledger_wasm_hash.clone(), - index_compressed_wasm_hash: self.embedded_index_wasm_hash.clone(), - archive_compressed_wasm_hash: self.embedded_archive_wasm_hash.clone(), - } - } - - pub fn upgrade_ledger_suite_orchestrator_with_same_wasm( - &self, - upgrade_arg: &OrchestratorArg, - ) -> Result<(), UserError> { - self.env.tick(); //tick before upgrade to finish current timers which are reset afterwards - self.env.upgrade_canister( - self.ledger_suite_orchestrator_id, - self.ledger_suite_orchestrator_wasm.clone(), - Encode!(upgrade_arg).unwrap(), - ) - } - - pub fn get_canister_status(&self) -> CanisterStatusResult { - Decode!( - &assert_reply( - self.env - .execute_ingress( - self.ledger_suite_orchestrator_id, - "get_canister_status", - Encode!().unwrap() - ) - .expect("failed to call get_canister_status") - ), - CanisterStatusResult - ) - .unwrap() - } - - pub fn assert_managed_canisters(self, contract: &Erc20Contract) -> ManagedCanistersAssert { - let canister_ids = self - .call_orchestrator_canister_ids(contract) - .unwrap_or_else(|| panic!("No managed canister IDs found for contract {contract:?}")); - - assert_ne!( - canister_ids.ledger, canister_ids.index, - "BUG: ledger and index canister IDs MUST be different" - ); - - ManagedCanistersAssert { - setup: self, - canister_ids, - } - } - - pub fn add_erc20_token(self, params: AddErc20Arg) -> AddErc20TokenFlow { - let setup = self.upgrade_ledger_suite_orchestrator_expecting_ok( - &OrchestratorArg::AddErc20Arg(params.clone()), - ); - AddErc20TokenFlow { setup, params } - } - - pub fn manage_installed_canisters( - self, - manage_installed_canister: Vec, - ) -> Self { - self.upgrade_ledger_suite_orchestrator_expecting_ok(&OrchestratorArg::UpgradeArg( - UpgradeArg { - git_commit_hash: None, - ledger_compressed_wasm_hash: None, - index_compressed_wasm_hash: None, - archive_compressed_wasm_hash: None, - cycles_management: None, - manage_ledger_suites: Some(manage_installed_canister), - }, - )) - } - - pub fn upgrade_ledger_suite_orchestrator( - self, - new_ledger_suite_orchestrator_wasm: Vec, - upgrade_arg: UpgradeArg, - ) -> Self { - self.env.tick(); //tick before upgrade to finish current timers which are reset afterwards - let new_embedded_ledger_wasm_hash = upgrade_arg - .ledger_compressed_wasm_hash - .clone() - .map(|s| s.parse().unwrap()) - .unwrap_or(self.embedded_ledger_wasm_hash); - let new_embedded_index_wasm_hash = upgrade_arg - .index_compressed_wasm_hash - .clone() - .map(|s| s.parse().unwrap()) - .unwrap_or(self.embedded_index_wasm_hash); - let new_embedded_archive_wasm_hash = upgrade_arg - .archive_compressed_wasm_hash - .clone() - .map(|s| s.parse().unwrap()) - .unwrap_or(self.embedded_archive_wasm_hash); - self.env - .upgrade_canister( - self.ledger_suite_orchestrator_id, - new_ledger_suite_orchestrator_wasm.clone(), - Encode!(&OrchestratorArg::UpgradeArg(upgrade_arg)).unwrap(), - ) - .expect("Failed to upgrade ERC20"); - Self { - env: self.env, - ledger_suite_orchestrator_id: self.ledger_suite_orchestrator_id, - ledger_suite_orchestrator_wasm: new_ledger_suite_orchestrator_wasm, - embedded_ledger_wasm_hash: new_embedded_ledger_wasm_hash, - embedded_index_wasm_hash: new_embedded_index_wasm_hash, - embedded_archive_wasm_hash: new_embedded_archive_wasm_hash, - } - } - - pub fn call_orchestrator_canister_ids( - &self, - contract: &Erc20Contract, - ) -> Option { - Decode!( - &assert_reply( - self.env - .execute_ingress( - self.ledger_suite_orchestrator_id, - "canister_ids", - Encode!(contract).unwrap() - ) - .expect("failed to execute token transfer") - ), - Option - ) - .unwrap() - } - - pub fn advance_time_for_periodic_tasks(&self) { - self.env - .advance_time(std::time::Duration::from_secs(60 * 60 + 1)); - self.env.tick(); - self.env.tick(); - self.env.tick(); - self.env.tick(); - self.env.tick(); - self.env.tick(); - } - - pub fn advance_time_for_upgrade(&self) { - self.env.tick(); - self.env.tick(); - self.env.tick(); - self.env.tick(); - self.env.tick(); - self.env.tick(); - } - - pub fn canister_status_of(&self, controlled_canister_id: CanisterId) -> CanisterStatusResultV2 { - self.env - .canister_status_as( - self.ledger_suite_orchestrator_id.into(), - controlled_canister_id, - ) - .unwrap() - .unwrap() - } - - pub fn get_orchestrator_info(&self) -> OrchestratorInfo { - Decode!( - &assert_reply( - self.env - .query( - self.ledger_suite_orchestrator_id, - "get_orchestrator_info", - Encode!().unwrap() - ) - .unwrap() - ), - OrchestratorInfo - ) - .unwrap() - } - - pub fn check_metrics(self) -> MetricsAssert { - MetricsAssert::from_http_query(self) - } - - pub fn wait_for(&self, f: F) -> T - where - F: Fn() -> Result, - E: std::fmt::Debug, - { - let mut last_error = None; - for _ in 0..MAX_TICKS { - self.env.tick(); - match f() { - Ok(t) => return t, - Err(e) => { - last_error = Some(e); - } - } - } - panic!("Failed to get result after {MAX_TICKS} ticks: {last_error:?}"); - } - - pub fn wait_for_canister_to_be_installed_and_running(&self, canister_id: Principal) { - let canister_id = PrincipalId(canister_id).try_into().unwrap(); - self.wait_for(|| { - let ledger_status = self.canister_status_of(canister_id); - if ledger_status.status() == CanisterStatusType::Running - && ledger_status.module_hash().is_some() - { - Ok(()) - } else { - Err(format!( - "Canister {canister_id} is not ready {ledger_status:?}" - )) - } - }); - } -} - -impl CanisterHttpQuery for LedgerSuiteOrchestrator { - fn http_query(&self, request: Vec) -> Result, UserError> { - self.as_ref() - .query(self.ledger_suite_orchestrator_id, "http_request", request) - .map(assert_reply) - } -} - pub fn default_init_arg() -> InitArg { InitArg { more_controller_ids: vec![NNS_ROOT_PRINCIPAL], @@ -358,13 +27,6 @@ pub fn default_init_arg() -> InitArg { } } -pub fn new_state_machine() -> StateMachine { - StateMachineBuilder::new() - .with_master_ecdsa_public_key() - .with_default_canister_range() - .build() -} - pub fn ledger_suite_orchestrator_wasm() -> Vec { let wasm_path = std::env::var("LEDGER_SUITE_ORCHESTRATOR_WASM_PATH").unwrap(); std::fs::read(wasm_path).unwrap() @@ -487,51 +149,3 @@ fn ledger_init_arg, V: Into>( token_logo: "".to_string(), } } - -pub fn assert_reply(result: WasmResult) -> Vec { - match result { - WasmResult::Reply(bytes) => bytes, - WasmResult::Reject(reject) => { - panic!("Expected a successful reply, got a reject: {reject}") - } - } -} - -pub fn out_of_band_upgrade>( - env: T, - controller: PrincipalId, - target: CanisterId, - wasm: Vec, -) -> Result<(), UserError> { - env.as_ref() - .execute_ingress_as( - controller, - CanisterId::ic_00(), - "install_code", - Encode!(&InstallCodeArgs { - mode: CanisterInstallMode::Upgrade(None), - canister_id: target.into(), - wasm_module: wasm, - arg: Encode!(&()).unwrap(), - sender_canister_version: None, - }) - .unwrap(), - ) - .map(|_| ()) -} - -pub fn stop_canister, P: Into>( - env: T, - controller: P, - target: CanisterId, -) { - let controller = controller.into(); - let stop_res = env.as_ref().stop_canister_as(controller, target); - assert_matches!(stop_res, Ok(WasmResult::Reply(_))); - let status = env - .as_ref() - .canister_status_as(controller, target) - .unwrap() - .unwrap(); - assert_eq!(status.status(), CanisterStatusType::Stopped); -} diff --git a/rs/ethereum/ledger-suite-orchestrator/test_utils/src/universal_canister.rs b/rs/ethereum/ledger-suite-orchestrator/test_utils/src/universal_canister.rs deleted file mode 100644 index ff20a7547332..000000000000 --- a/rs/ethereum/ledger-suite-orchestrator/test_utils/src/universal_canister.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::assert_reply; -use candid::{Decode, Encode}; -use ic_base_types::{CanisterId, PrincipalId}; -use ic_management_canister_types::CanisterInfoArgs; -pub use ic_management_canister_types::{ - CanisterInfoResult, CanisterStatusType, ChangeDetails, CodeDeploymentMode, CodeDeploymentRecord, -}; -use ic_state_machine_tests::StateMachine; -use ic_types_cycles::Cycles; -use ic_universal_canister::{UNIVERSAL_CANISTER_WASM, call_args, wasm}; -use std::sync::Arc; - -pub struct UniversalCanister { - pub env: Arc, - pub canister_id: CanisterId, -} - -impl UniversalCanister { - pub fn new(env: Arc) -> Self { - let canister_id = env.create_canister_with_cycles(None, Cycles::new(u128::MAX), None); - Self { env, canister_id }.install_wasm() - } - - fn install_wasm(self) -> Self { - self.env - .install_existing_canister(self.canister_id, UNIVERSAL_CANISTER_WASM.to_vec(), vec![]) - .unwrap(); - self - } - - pub fn canister_info(&self, target: CanisterId) -> CanisterInfoResult { - let info_request_payload = universal_canister_payload( - &PrincipalId::default(), - "canister_info", - Encode!(&CanisterInfoArgs { - canister_id: target.into(), - num_requested_changes: Some(u64::MAX), - }) - .unwrap(), - Cycles::new(0), - ); - let bytes = assert_reply( - self.env - .execute_ingress(self.canister_id, "update", info_request_payload) - .unwrap(), - ); - Decode!(&bytes[..], CanisterInfoResult).expect("failed to decode canister_info response") - } -} - -fn universal_canister_payload( - receiver: &PrincipalId, - method: &str, - payload: Vec, - cycles: Cycles, -) -> Vec { - wasm() - .call_with_cycles( - receiver, - method, - call_args() - .other_side(payload) - .on_reject(wasm().reject_message().reject()), - cycles, - ) - .build() -}