From a7cbf1843f58507b558de54bd5fc1982d0d6388b Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Sun, 24 May 2026 07:48:04 +0000 Subject: [PATCH 1/4] feat: [DSM-103] Charge for storage after aborting DTS executions When charging canisters for storage, we don't charge canisters with paused executions because they should have a constant cycle balance throughuot the DTS execution. But we do charge canisters with aborted executions. So swap things around, so that we charge for storage after aborting paused executions (whether executions above the limit on regular rounds; or all paused executions on checkpoint rounds). This ensures that we can charge all canisters on checkpoint rounds (and makes it more likely to charge the average busy canister on regular rounds), making it possible to evenrually mote away from trying to charge every canister every round just to make sure we succeed. --- rs/execution_environment/src/scheduler.rs | 18 +-- rs/execution_environment/tests/dts.rs | 128 ++++++++++------------ 2 files changed, 70 insertions(+), 76 deletions(-) diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index ba3cacc71ab3..5901de86c5e1 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -1457,14 +1457,6 @@ impl Scheduler for SchedulerImpl { let _timer = self.metrics.round_finalization_ingress.start_timer(); final_state.prune_ingress_history(); } - { - let _timer = self.metrics.round_finalization_charge.start_timer(); - self.charge_canisters_for_resource_allocation_and_usage( - &mut final_state, - registry_settings.subnet_size, - current_round, - ); - } // Update canister priorities. { @@ -1474,6 +1466,16 @@ impl Scheduler for SchedulerImpl { self.finish_round(&mut final_state, current_round_type); + // Charge canisters after (some) paused executions were aborted. + { + let _timer = self.metrics.round_finalization_charge.start_timer(); + self.charge_canisters_for_resource_allocation_and_usage( + &mut final_state, + registry_settings.subnet_size, + current_round, + ); + } + final_state .metadata .subnet_metrics diff --git a/rs/execution_environment/tests/dts.rs b/rs/execution_environment/tests/dts.rs index 7dbd7e4a3250..15208f2e7e29 100644 --- a/rs/execution_environment/tests/dts.rs +++ b/rs/execution_environment/tests/dts.rs @@ -24,7 +24,6 @@ use ic_types_cycles::Cycles; use ic_universal_canister::{ CallArgs, UNIVERSAL_CANISTER_NO_HEARTBEAT_WASM, UNIVERSAL_CANISTER_WASM, call_args, wasm, }; -use more_asserts::assert_ge; use std::sync::OnceLock; const INITIAL_CYCLES_BALANCE: Cycles = Cycles::new(100_000_000_000_000); @@ -1794,8 +1793,11 @@ fn dts_ingress_status_of_update_with_call_is_correct() { env.await_ingress(update, 100).unwrap(); } -#[test] -fn dts_canister_uninstalled_due_to_resource_charges_with_aborted_updrade() { +/// Causes a canister to run out of cycles with a paused / aborted upgrade and +/// returns the result of the upgrade. +fn impl_dts_canister_with_upgrade_is_uninstalled_due_to_resource_charges( + aborted: bool, +) -> Result { let env = dts_env( NumInstructions::from(1_000_000_000), NumInstructions::from(10_000), @@ -1815,108 +1817,98 @@ fn dts_canister_uninstalled_due_to_resource_charges_with_aborted_updrade() { .install_canister_with_cycles(binary.clone(), vec![], settings, INITIAL_CYCLES_BALANCE) .unwrap(); - // Make sure that the upgrade message gets aborted after each round. - env.set_checkpoints_enabled(true); + // Checkpointing will abort the upgrade after each round. + env.set_checkpoints_enabled(aborted); let upgrade = { let args = InstallCodeArgs::new(CanisterInstallMode::Upgrade, canister, binary, vec![]); env.send_ingress(user_id, IC_00, Method::InstallCode, args.encode()) }; - env.tick(); - // Advance the time so that the canister gets uninstalled due to the + // Advance the time enough to get the canister uninstalled due to the // resource usage. env.advance_time(Duration::from_secs(10_000_000)); - env.tick(); // Enable normal message execution. env.set_checkpoints_enabled(false); - let result = env.await_ingress(upgrade, 30).unwrap(); + env.await_ingress(upgrade, 30) +} - // The canister is uninstalled after the execution completes because an - // aborted install_code is always restarted and becomes a paused execution - // by the time we charge canister for resource allocation. - assert_eq!(result, WasmResult::Reply(EmptyBlob.encode())); +#[test] +fn dts_canister_with_aborted_upgrade_is_uninstalled_due_to_resource_charges() { + let result = impl_dts_canister_with_upgrade_is_uninstalled_due_to_resource_charges(true); + + // Canister with aborted upgrade is uninstalled. Upgrade fails. + assert_matches!( + result, + Err(err) if err.code() == ErrorCode::CanisterWasmModuleNotFound + ); } #[test] -fn dts_canister_uninstalled_due_resource_charges_with_aborted_update() { +fn dts_canister_with_paused_upgrade_is_not_uninstalled_due_to_resource_charges() { + let result = impl_dts_canister_with_upgrade_is_uninstalled_due_to_resource_charges(false); + + // Canister is only uninstalled after paused upgrade completes successfully. + assert_eq!(result, Ok(WasmResult::Reply(EmptyBlob.encode()))); +} + +fn impl_dts_canister_with_update_is_uninstalled_due_to_resource_charges( + aborted: bool, +) -> Result { let env = dts_env( NumInstructions::from(1_000_000_000), NumInstructions::from(10_000), ); - let binary = wat2wasm(DTS_WAT); - let user_id = PrincipalId::new_anonymous(); - let n = 10; - - let mut canisters = vec![]; - for _ in 0..n { - let settings = Some( - CanisterSettingsArgsBuilder::new() - .with_compute_allocation(1) - .build(), - ); - - let id = env - .install_canister_with_cycles(binary.clone(), vec![], settings, INITIAL_CYCLES_BALANCE) - .unwrap(); - canisters.push(id); - } - - // Make sure that the update messages get aborted after each round. - env.set_checkpoints_enabled(true); + let settings = Some( + CanisterSettingsArgsBuilder::new() + .with_compute_allocation(1) + .build(), + ); + let canister = env + .install_canister_with_cycles(binary.clone(), vec![], settings, INITIAL_CYCLES_BALANCE) + .unwrap(); - let mut updates = vec![]; - for canister in canisters.iter() { - let update = env.send_ingress(user_id, *canister, "update", vec![]); - updates.push(update); - } + // Checkpointing will abort the update after each round. + env.set_checkpoints_enabled(aborted); - // Ensure that each update message starts executing. - for _ in 0..n { - env.tick(); - } + let update = env.send_ingress(user_id, canister, "update", vec![]); + env.tick(); // Advance the time so that the canister gets uninstalled due to the // resource usage. env.advance_time(Duration::from_secs(10_000_000)); - env.tick(); // Enable normal message execution. env.set_checkpoints_enabled(false); - let mut errors = 0; + env.await_ingress(update.clone(), 100) +} - // Canisters that were chosen for execution before charging for resources - // become paused and don't get uninstalled until their execution completes. - // All other canister are uninstalled before resuming their aborted - // executions. - for i in 0..n { - match env.await_ingress(updates[i].clone(), 100) { - Ok(result) => { - assert_eq!(result, WasmResult::Reply(vec![])); - } - Err(err) => { - err.assert_contains( - ErrorCode::CanisterWasmModuleNotFound, - &format!( - "Error from Canister {}: Attempted to execute a message, \ - but the canister contains no Wasm module.", - canisters[i] - ), - ); - errors += 1; - } - } - } - assert_ge!(errors, 1); +#[test] +fn dts_canister_with_aborted_update_is_uninstalled_due_to_resource_charges() { + let result = impl_dts_canister_with_update_is_uninstalled_due_to_resource_charges(true); + + // Canister with aborted update is uninstalled. Update fails. + assert_matches!( + result, + Err(err) if err.code() == ErrorCode::CanisterWasmModuleNotFound + ); +} + +#[test] +fn dts_canister_with_paused_update_is_not_uninstalled_due_to_resource_charges() { + let result = impl_dts_canister_with_update_is_uninstalled_due_to_resource_charges(false); + + // Canister is only uninstalled after paused update completes successfully. + assert_matches!(result, Ok(_)); } #[test] From 78faffd8ecb5b576cc81b60234ff2897ce1c3697 Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Sun, 24 May 2026 09:11:17 +0000 Subject: [PATCH 2/4] feat: [DSM-103] Lower frequency storage charging Instead of checking every canister every round whether 10 seconds have passed since it was last charged, only do it every N rounds; AND on checkpoint rounds, to ensure that canisters with paused DTS executions get charged at least once per checkpoint interval. --- rs/execution_environment/src/scheduler.rs | 24 ++++++++++++++++--- .../src/scheduler/test_utilities.rs | 1 + .../src/scheduler/tests/charging.rs | 20 ++++++++++------ .../tests/canister_history.rs | 4 +++- .../tests/execution_test.rs | 3 ++- .../tests/subnet_size_test.rs | 3 ++- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index 5901de86c5e1..c9bd998afe2f 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -808,14 +808,30 @@ impl SchedulerImpl { } /// Charge canisters for their resource allocation and usage. Canisters - /// that did not manage to pay are uninstalled. - /// This function is expected to be called at the end of a round. + /// that cannot pay are uninstalled. + /// + /// This function is expected to be called at the end of a round and + /// particularly after paused executions were aborted on checkpoint rounds. fn charge_canisters_for_resource_allocation_and_usage( &self, state: &mut ReplicatedState, subnet_size: usize, current_round: ExecutionRound, + current_round_type: ExecutionRoundType, ) { + // Because it is relatively expensive to make every canister mutable just to + // check whether 10 seconds have passed since it was last charged, only + // charge canisters every 50 rounds and/or on checkpoint rounds. + // + // The latter ensures that (because we skip canisters with paused + // executions), every canister is charged at least once per checkpoint + // interval. + if current_round.get() % 50 != 0 + && current_round_type != ExecutionRoundType::CheckpointRound + { + return; + } + let cost_schedule = state.get_own_cost_schedule(); let state_time = state.time(); let threshold_last_allocation_charge = state_time.saturating_sub( @@ -1464,15 +1480,17 @@ impl Scheduler for SchedulerImpl { round_schedule.finish_round(&mut final_state, current_round, &self.metrics); } + // Abort (some) paused executions. self.finish_round(&mut final_state, current_round_type); - // Charge canisters after (some) paused executions were aborted. + // Charge canisters after paused executions were aborted. { let _timer = self.metrics.round_finalization_charge.start_timer(); self.charge_canisters_for_resource_allocation_and_usage( &mut final_state, registry_settings.subnet_size, current_round, + current_round_type, ); } diff --git a/rs/execution_environment/src/scheduler/test_utilities.rs b/rs/execution_environment/src/scheduler/test_utilities.rs index 1fedc17b0e79..dd2a87d07bc9 100644 --- a/rs/execution_environment/src/scheduler/test_utilities.rs +++ b/rs/execution_environment/src/scheduler/test_utilities.rs @@ -675,6 +675,7 @@ impl SchedulerTest { self.state.as_mut().unwrap(), subnet_size, ExecutionRound::from(0), + ExecutionRoundType::CheckpointRound, ) } diff --git a/rs/execution_environment/src/scheduler/tests/charging.rs b/rs/execution_environment/src/scheduler/tests/charging.rs index 83f6d0cc74b1..949b20621769 100644 --- a/rs/execution_environment/src/scheduler/tests/charging.rs +++ b/rs/execution_environment/src/scheduler/tests/charging.rs @@ -56,8 +56,9 @@ fn only_charge_for_allocation_after_specified_duration() { ); // Don't charge because the time since the last charge is too small. + // Checkpoint round, to force charging for storage. test.set_time(initial_time + time_between_batches); - test.execute_round(ExecutionRoundType::OrdinaryRound); + test.execute_round(ExecutionRoundType::CheckpointRound); assert_eq!( test.canister_state(canister).system_state.balance().get(), @@ -65,9 +66,9 @@ fn only_charge_for_allocation_after_specified_duration() { ); // The time of the current batch is now long enough that allocation charging - // should be triggered. + // should be triggered. Checkpoint round, to force charging for storage. test.set_time(initial_time + 2 * time_between_batches); - test.execute_round(ExecutionRoundType::OrdinaryRound); + test.execute_round(ExecutionRoundType::CheckpointRound); assert_eq!( test.canister_state(canister).system_state.balance().get(), initial_cycles - 10, @@ -106,7 +107,7 @@ fn charging_for_message_memory_works() { // Send an ingress that triggers an inter-canister call. Because of the scheduler // configuration, we can only execute the ingress message but not the - // inter-canister message so this remain in the canister's input queue. + // inter-canister message so this remains in the canister's input queue. test.send_ingress( canister, ingress(1).call(other_side(canister, 1), on_response(1)), @@ -220,7 +221,8 @@ fn canisters_with_insufficient_cycles_are_uninstalled() { .duration_between_allocation_charges(), ); - test.execute_round(ExecutionRoundType::OrdinaryRound); + // Checkpoint round, to force charging for storage. + test.execute_round(ExecutionRoundType::CheckpointRound); for canister in test.state().canisters_iter() { assert!(canister.execution_state.is_none()); @@ -455,7 +457,9 @@ fn snapshot_is_deleted_when_canister_is_out_of_cycles() { .cycles_account_manager .duration_between_allocation_charges(), ); - test.execute_round(ExecutionRoundType::OrdinaryRound); + // Checkpoint round, to force charging for storage. + test.execute_round(ExecutionRoundType::CheckpointRound); + assert_eq!( test.scheduler() .metrics @@ -591,7 +595,9 @@ fn snapshot_is_deleted_when_uninstalled_canister_is_out_of_cycles() { .cycles_account_manager .duration_between_allocation_charges(), ); - test.execute_round(ExecutionRoundType::OrdinaryRound); + // Checkpoint round, to force charging for storage. + test.execute_round(ExecutionRoundType::CheckpointRound); + assert_eq!( test.scheduler() .metrics diff --git a/rs/execution_environment/tests/canister_history.rs b/rs/execution_environment/tests/canister_history.rs index 28c080b305c9..749ee8de2ed6 100644 --- a/rs/execution_environment/tests/canister_history.rs +++ b/rs/execution_environment/tests/canister_history.rs @@ -635,7 +635,9 @@ fn canister_history_cleared_if_canister_out_of_cycles() { / compute_percent_allocated_per_second_fee.get() as u64; now += Duration::from_secs(seconds_to_burn_balance + 1); env.set_time(now); - env.tick(); + // canisters are always charged for storage on checkpoint rounds + env.checkpointed_tick(); + // check canister history let total_num_change_entries = reference_change_entries.len(); reference_change_entries.clear(); diff --git a/rs/execution_environment/tests/execution_test.rs b/rs/execution_environment/tests/execution_test.rs index 5aa63293b48b..97c443116c46 100644 --- a/rs/execution_environment/tests/execution_test.rs +++ b/rs/execution_environment/tests/execution_test.rs @@ -476,7 +476,8 @@ fn canister_has_zero_balance_when_uninstalled_due_to_low_cycles() { let seconds_to_burn_balance = env.cycle_balance(canister_id) as u64 / compute_percent_allocated_per_second_fee.get() as u64; env.advance_time(Duration::from_secs(seconds_to_burn_balance + 1)); - env.tick(); + // Checkpoint round, to force charging for storage. + env.checkpointed_tick(); // Verify the original canister still exists but it's uninstalled and has a // zero cycle balance. diff --git a/rs/execution_environment/tests/subnet_size_test.rs b/rs/execution_environment/tests/subnet_size_test.rs index 7ea7c172be73..95d1811c87f7 100644 --- a/rs/execution_environment/tests/subnet_size_test.rs +++ b/rs/execution_environment/tests/subnet_size_test.rs @@ -304,7 +304,8 @@ fn simulate_one_gib_per_second_cost( env.advance_time(duration_between_allocation_charges); let balance_before = env.cycle_balance(canister_id); - env.tick(); + // Checkpoint round, to force charging for storage. + env.checkpointed_tick(); let balance_after = env.cycle_balance(canister_id); // Scale the cost from a defined in config value to a 1 second duration. From 1d7714a5b2aa61e07af332a146537b25160a6f2e Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Sun, 24 May 2026 09:23:50 +0000 Subject: [PATCH 3/4] Make clippy happy. --- rs/execution_environment/src/scheduler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index c9bd998afe2f..9affe545e5f5 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -826,7 +826,7 @@ impl SchedulerImpl { // The latter ensures that (because we skip canisters with paused // executions), every canister is charged at least once per checkpoint // interval. - if current_round.get() % 50 != 0 + if !current_round.get().is_multiple_of(50) && current_round_type != ExecutionRoundType::CheckpointRound { return; From 686e3f2e367f49dfacbe3c8d19d44136518ff08b Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Tue, 2 Jun 2026 20:20:19 +0000 Subject: [PATCH 4/4] Address review comments: define a CHARGE_INTERVAL_ROUNDS constant; test that we only charge once every CHARGE_INTERVAL_ROUNDS. --- rs/execution_environment/src/scheduler.rs | 9 +- .../src/scheduler/test_utilities.rs | 6 ++ .../src/scheduler/tests/charging.rs | 94 ++++++++++--------- 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index f5cca870e926..fd6391fcd9c8 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -72,6 +72,11 @@ pub(crate) mod tests; /// work here. const SUBNET_MESSAGES_LIMIT_FRACTION: u64 = 16; +/// Because it is relatively expensive to make every canister mutable just to +/// check whether 10 seconds have passed since it was last charged, only try to +/// charge canisters every 50 rounds (and/or on checkpoint rounds). +const CHARGE_INTERVAL_ROUNDS: u64 = 50; + /// Contains limits (or budget) for various resources that affect duration of /// an execution round. #[derive(Clone, Debug, Default)] @@ -827,13 +832,13 @@ impl SchedulerImpl { current_round_type: ExecutionRoundType, ) { // Because it is relatively expensive to make every canister mutable just to - // check whether 10 seconds have passed since it was last charged, only + // check whether 10 seconds have passed since it was last charged, only try to // charge canisters every 50 rounds and/or on checkpoint rounds. // // The latter ensures that (because we skip canisters with paused // executions), every canister is charged at least once per checkpoint // interval. - if !current_round.get().is_multiple_of(50) + if !current_round.get().is_multiple_of(CHARGE_INTERVAL_ROUNDS) && current_round_type != ExecutionRoundType::CheckpointRound { return; diff --git a/rs/execution_environment/src/scheduler/test_utilities.rs b/rs/execution_environment/src/scheduler/test_utilities.rs index 55bead0664c3..3cd927493389 100644 --- a/rs/execution_environment/src/scheduler/test_utilities.rs +++ b/rs/execution_environment/src/scheduler/test_utilities.rs @@ -784,6 +784,12 @@ impl SchedulerTest { ) } + pub fn duration_between_allocation_charges(&self) -> Duration { + self.scheduler + .cycles_account_manager + .duration_between_allocation_charges() + } + pub(crate) fn deliver_pre_signatures( &mut self, idkg_pre_signatures: BTreeMap, diff --git a/rs/execution_environment/src/scheduler/tests/charging.rs b/rs/execution_environment/src/scheduler/tests/charging.rs index a151e7f3ba3a..ccf9fbe889a0 100644 --- a/rs/execution_environment/src/scheduler/tests/charging.rs +++ b/rs/execution_environment/src/scheduler/tests/charging.rs @@ -28,11 +28,7 @@ fn only_charge_for_allocation_after_specified_duration() { let initial_time = Time::from_nanos_since_unix_epoch(1_000_000_000_000); test.set_time(initial_time); - let time_between_batches = test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges() - / 2; + let time_between_batches = test.duration_between_allocation_charges() / 2; // Just enough memory to cost us one cycle per second. let bytes_per_cycle = (1_u128 << 30) @@ -82,6 +78,48 @@ fn only_charge_for_allocation_after_specified_duration() { ); } +#[test] +fn charging_happens_on_average_once_every_charge_interval_rounds() { + let mut test = SchedulerTestBuilder::new().build(); + + // Charging handles time=0 as a special case, so it should be set to some + // non-zero time. + let initial_time = Time::from_nanos_since_unix_epoch(1_000_000_000_000); + test.set_time(initial_time); + + // A canister with a memory allocation and plenty of cycles, so that every + // charge reduces the balance but the canister is never uninstalled. + let canister = test.create_canister_with( + Cycles::new(1_000_000_000_000_000), + ComputeAllocation::zero(), + MemoryAllocation::from(NumBytes::from(1 << 30)), + None, + Some(initial_time), + None, + ); + + // Advance time by a full charge duration every round, so that a charge is + // always due whenever charging is attempted (every `CHARGE_INTERVAL_ROUNDS` + // rounds). + let charge_duration = test.duration_between_allocation_charges(); + + const NUM_ROUNDS: u64 = 1_000; + let mut num_charges = 0; + for _ in 0..NUM_ROUNDS { + test.advance_time(charge_duration); + let balance_before = test.canister_state(canister).system_state.balance(); + test.execute_round(ExecutionRoundType::OrdinaryRound); + if test.canister_state(canister).system_state.balance() < balance_before { + num_charges += 1; + } + } + + // Even though enough time has passed to charge in every round, the canister + // was only charged once every `CHARGE_INTERVAL_ROUNDS` rounds, i.e. on + // average once every 50 rounds. + assert_eq!(num_charges, NUM_ROUNDS / CHARGE_INTERVAL_ROUNDS); +} + #[test] fn charging_for_message_memory_works() { let mut test = SchedulerTestBuilder::new() @@ -124,10 +162,7 @@ fn charging_for_message_memory_works() { // Set time to at least one interval between charges to trigger a charge // because of message memory consumption. - let charge_duration = test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(); + let charge_duration = test.duration_between_allocation_charges(); test.set_time(initial_time + charge_duration); test.charge_for_resource_allocations(); @@ -184,10 +219,7 @@ fn charging_for_logging_memory_works() { // Set time to at least one interval between charges to trigger a charge // because of log memory consumption. - let charge_duration = test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(); + let charge_duration = test.duration_between_allocation_charges(); test.set_time(initial_time + charge_duration); test.charge_for_resource_allocations(); @@ -226,13 +258,7 @@ fn canisters_with_insufficient_cycles_are_uninstalled() { None, ); } - test.set_time( - initial_time - + test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(), - ); + test.set_time(initial_time + test.duration_between_allocation_charges()); // Checkpoint round, to force charging for storage. test.execute_round(ExecutionRoundType::CheckpointRound); @@ -285,10 +311,7 @@ fn open_call_contexts_produce_reject_responses_when_out_of_cycles() { .system_state .set_balance(Cycles::zero()); - let duration_between_allocation_charges = test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(); + let duration_between_allocation_charges = test.duration_between_allocation_charges(); test.set_time(initial_time + duration_between_allocation_charges); test.charge_for_resource_allocations(); @@ -343,10 +366,7 @@ fn dont_charge_allocations_for_paused_canisters() { .task_queue .enqueue(ExecutionTask::PausedInstallCode(PausedExecutionId(0))); - let duration_between_allocation_charges = test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(); + let duration_between_allocation_charges = test.duration_between_allocation_charges(); test.set_time(T0 + duration_between_allocation_charges); test.charge_for_resource_allocations(); @@ -464,14 +484,7 @@ fn snapshot_is_deleted_when_canister_is_out_of_cycles() { ); // Uninstall canister due to `out_of_cycles`. - test.set_time( - initial_time - + 1000 - * test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(), - ); + test.set_time(initial_time + 1000 * test.duration_between_allocation_charges()); // Checkpoint round, to force charging for storage. test.execute_round(ExecutionRoundType::CheckpointRound); @@ -602,14 +615,7 @@ fn snapshot_is_deleted_when_uninstalled_canister_is_out_of_cycles() { ); // Trigger canister `out_of_cycles`. - test.set_time( - initial_time - + 1000 - * test - .scheduler() - .cycles_account_manager - .duration_between_allocation_charges(), - ); + test.set_time(initial_time + 1000 * test.duration_between_allocation_charges()); // Checkpoint round, to force charging for storage. test.execute_round(ExecutionRoundType::CheckpointRound);