From 4e81d7783915535e113e98f0f4da87f85d45be85 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Mon, 2 Feb 2026 17:33:26 +0900 Subject: [PATCH 01/10] Handle out-of-range service ids --- .../src/host_functions/accumulate/mod.rs | 5 +- .../src/host_functions/accumulate/tests.rs | 78 +++++++++++++++++ .../src/host_functions/general/mod.rs | 17 +++- .../src/host_functions/general/tests.rs | 84 +++++++++++++++++++ 4 files changed, 179 insertions(+), 5 deletions(-) diff --git a/pvm/pvm-host/src/host_functions/accumulate/mod.rs b/pvm/pvm-host/src/host_functions/accumulate/mod.rs index 2723e746..9bfd4783 100644 --- a/pvm/pvm-host/src/host_functions/accumulate/mod.rs +++ b/pvm/pvm-host/src/host_functions/accumulate/mod.rs @@ -1198,7 +1198,10 @@ impl AccumulateHostFunction { let service_id = if service_id_reg == u64::MAX { service_id } else { - service_id_reg as ServiceId + let Ok(service_id) = vm.read_reg_as_service_id(7) else { + continue_who!() + }; + service_id }; // Service account not found diff --git a/pvm/pvm-host/src/host_functions/accumulate/tests.rs b/pvm/pvm-host/src/host_functions/accumulate/tests.rs index 8996ca7d..0b55cf60 100644 --- a/pvm/pvm-host/src/host_functions/accumulate/tests.rs +++ b/pvm/pvm-host/src/host_functions/accumulate/tests.rs @@ -4233,6 +4233,84 @@ mod provide_tests { Ok(()) } + #[tokio::test] + async fn test_provide_service_id_out_of_range_returns_who() -> Result<(), Box> { + let fixture = ProvideTestFixture::default(); + let out_of_range_service = (1u64 << 32) + fixture.provide_service as u64; + let vm = fixture + .prepare_vm_builder()? + .with_reg(7, out_of_range_service) + .with_mem_readable_range(fixture.mem_readable_range.clone())? + .build(); + let state_provider = Arc::new(fixture.prepare_state_provider()); + let mut context = fixture + .prepare_invocation_context(state_provider.clone()) + .await?; + + // Check host-call result + let res = AccumulateHostFunction::::host_provide( + fixture.provide_service, + &vm, + state_provider.clone(), + &mut context, + ) + .await?; + assert_eq!(res, ProvideTestFixture::host_call_result_who()); + + // Check partial state after host-call (should remain unchanged) + let x = context.get_mut_accumulate_x().unwrap(); + assert!(!x.provided_preimages.contains(&( + fixture.provide_service, + Octets::from_vec(fixture.preimage_data.clone()) + ))); + Ok(()) + } + + #[tokio::test] + async fn test_provide_invalid_service_id_returns_who() -> Result<(), Box> { + let fixture = ProvideTestFixture::default(); + let invalid_service_id = u64::from(u32::MAX) + 1; + let spoofed_service_id = 0; + let vm = fixture + .prepare_vm_builder()? + .with_reg(7, invalid_service_id) + .with_mem_readable_range(fixture.mem_readable_range.clone())? + .build(); + let state_provider = Arc::new( + MockStateManager::builder() + .with_empty_account(fixture.accumulate_host) + .with_empty_account(spoofed_service_id) + .with_lookups_entry( + spoofed_service_id, + (fixture.preimage_hash.clone(), fixture.preimage_size), + AccountLookupsEntry { + value: AccountLookupsEntryTimeslots::try_from(vec![]).unwrap(), + }, + ), + ); + let mut context = fixture + .prepare_invocation_context(state_provider.clone()) + .await?; + + // Check host-call result + let res = AccumulateHostFunction::::host_provide( + fixture.accumulate_host, + &vm, + state_provider.clone(), + &mut context, + ) + .await?; + assert_eq!(res, ProvideTestFixture::host_call_result_who()); + + // Check partial state after host-call (should remain unchanged) + let x = context.get_mut_accumulate_x().unwrap(); + assert!(!x.provided_preimages.contains(&( + spoofed_service_id, + Octets::from_vec(fixture.preimage_data.clone()) + ))); + Ok(()) + } + #[tokio::test] async fn test_provide_preimage_not_solicited() -> Result<(), Box> { let fixture = ProvideTestFixture::default(); diff --git a/pvm/pvm-host/src/host_functions/general/mod.rs b/pvm/pvm-host/src/host_functions/general/mod.rs index b8098b6c..dd498748 100644 --- a/pvm/pvm-host/src/host_functions/general/mod.rs +++ b/pvm/pvm-host/src/host_functions/general/mod.rs @@ -263,10 +263,13 @@ impl GeneralHostFunction { // --- Check Preimage (Err: NONE) let service_id_reg = vm.read_reg(7); - let service_id = if service_id_reg == u64::MAX || service_id_reg == service_id as u64 { + let service_id = if service_id_reg == u64::MAX { service_id } else { - service_id_reg as ServiceId + let Ok(service_id_reg) = vm.read_reg_as_service_id(7) else { + continue_none!() + }; + service_id_reg }; let Ok(Some(entry)) = accounts_sandbox .get_account_preimages_entry(state_provider, service_id, &hash) @@ -340,7 +343,10 @@ impl GeneralHostFunction { let service_id = if service_id_reg == u64::MAX { service_id } else { - service_id_reg as ServiceId + let Ok(service_id_reg) = vm.read_reg_as_service_id(7) else { + continue_none!() + }; + service_id_reg }; let Ok(Some(entry)) = accounts_sandbox .get_account_storage_entry(state_provider, service_id, &storage_key) @@ -533,7 +539,10 @@ impl GeneralHostFunction { let service_id = if service_id_reg == u64::MAX { service_id } else { - service_id_reg as ServiceId + let Ok(service_id_reg) = vm.read_reg_as_service_id(7) else { + continue_none!() + }; + service_id_reg }; let Ok(Some(metadata)) = accounts_sandbox .get_account_metadata(state_provider, service_id) diff --git a/pvm/pvm-host/src/host_functions/general/tests.rs b/pvm/pvm-host/src/host_functions/general/tests.rs index 124ac7fd..4a3881e2 100644 --- a/pvm/pvm-host/src/host_functions/general/tests.rs +++ b/pvm/pvm-host/src/host_functions/general/tests.rs @@ -1103,6 +1103,34 @@ mod lookup_tests { Ok(()) } + #[tokio::test] + async fn test_lookup_service_id_out_of_range_returns_none() -> Result<(), Box> { + setup_tracing(); + let fixture = LookupTestFixture::default(); + let out_of_range_service_id = (1u64 << 32) + fixture.other_service_id as u64; + let vm = fixture + .prepare_vm_builder()? + .with_reg(7, out_of_range_service_id) + .with_mem_readable_range(fixture.mem_readable_range.clone())? + .with_mem_writable_range(fixture.mem_writable_range.clone())? + .build(); + + let state_provider = + Arc::new(fixture.prepare_state_provider(Some(fixture.other_service_id))); + let mut context = fixture + .prepare_invocation_context(state_provider.clone()) + .await?; + let res = GeneralHostFunction::::host_lookup( + fixture.accumulate_host, + &vm, + state_provider, + &mut context, + ) + .await?; + assert_eq!(res, LookupTestFixture::host_call_result_none()); + Ok(()) + } + #[tokio::test] async fn test_lookup_account_not_found() -> Result<(), Box> { setup_tracing(); @@ -1402,6 +1430,38 @@ mod read_tests { Ok(()) } + #[tokio::test] + async fn test_read_service_id_out_of_range_returns_none() -> Result<(), Box> { + setup_tracing(); + let fixture = ReadTestFixture::default(); + let out_of_range_service_id = (1u64 << 32) + fixture.other_service_id as u64; + let vm = fixture + .prepare_vm_builder()? + .with_reg(7, out_of_range_service_id) + .with_mem_data( + fixture.storage_key_mem_offset, + fixture.storage_key.as_slice(), + )? + .with_mem_readable_range(fixture.mem_readable_range.clone())? + .with_mem_writable_range(fixture.mem_writable_range.clone())? + .build(); + + let state_provider = + Arc::new(fixture.prepare_state_provider(Some(fixture.other_service_id))); + let mut context = fixture + .prepare_invocation_context(state_provider.clone()) + .await?; + let res = GeneralHostFunction::::host_read( + fixture.accumulate_host, + &vm, + state_provider, + &mut context, + ) + .await?; + assert_eq!(res, ReadTestFixture::host_call_result_none()); + Ok(()) + } + #[tokio::test] async fn test_read_account_not_found() -> Result<(), Box> { setup_tracing(); @@ -2061,6 +2121,30 @@ mod info_tests { Ok(()) } + #[tokio::test] + async fn test_info_service_id_out_of_range_returns_none() -> Result<(), Box> { + let fixture = InfoTestFixture::default(); + let out_of_range_service_id = (1u64 << 32) + fixture.info_service_id as u64; + let vm = fixture + .prepare_vm_builder()? + .with_reg(7, out_of_range_service_id) + .build(); + let state_provider = Arc::new(fixture.prepare_state_provider()); + let mut context = fixture + .prepare_invocation_context(state_provider.clone()) + .await?; + + let res = GeneralHostFunction::::host_info( + fixture.accumulate_host, + &vm, + state_provider, + &mut context, + ) + .await?; + assert_eq!(res, InfoTestFixture::host_call_result_none()); + Ok(()) + } + #[tokio::test] async fn test_info_successful_accumulate_host() -> Result<(), Box> { let fixture = InfoTestFixture::default(); From bef61207dba25f68d396f8d5d09e53f146cb9a93 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Mon, 2 Feb 2026 17:42:34 +0900 Subject: [PATCH 02/10] Fix service id check logic for HISTORICAL_LOOKUP --- pvm/pvm-host/src/host_functions/refine/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvm/pvm-host/src/host_functions/refine/mod.rs b/pvm/pvm-host/src/host_functions/refine/mod.rs index 2b049210..d06070b8 100644 --- a/pvm/pvm-host/src/host_functions/refine/mod.rs +++ b/pvm/pvm-host/src/host_functions/refine/mod.rs @@ -64,7 +64,7 @@ impl RefineHostFunction { let service_id_reg = vm.read_reg(7); let service_id = if service_id_reg == u64::MAX - || state_provider.account_exists(refine_service_id).await? + && state_provider.account_exists(refine_service_id).await? { refine_service_id } else { From 753ec79d769f632b6e177e28c79b3d5f8c885075 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Mon, 2 Feb 2026 19:06:19 +0900 Subject: [PATCH 03/10] Handle overflow in BLESS --- pvm/pvm-host/src/host_functions/accumulate/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pvm/pvm-host/src/host_functions/accumulate/mod.rs b/pvm/pvm-host/src/host_functions/accumulate/mod.rs index 9bfd4783..308c67f4 100644 --- a/pvm/pvm-host/src/host_functions/accumulate/mod.rs +++ b/pvm/pvm-host/src/host_functions/accumulate/mod.rs @@ -78,15 +78,18 @@ impl AccumulateHostFunction { } // Read always-accumulate services from the memory + let Some(always_accumulate_len) = always_accumulates_count.checked_mul(12) else { + host_call_panic!() + }; if !vm .memory - .is_address_range_readable(always_accumulate_offset, 12 * always_accumulates_count) + .is_address_range_readable(always_accumulate_offset, always_accumulate_len) { host_call_panic!() } let Ok(always_accumulate_services_data) = vm .memory - .read_bytes(always_accumulate_offset, 12 * always_accumulates_count) + .read_bytes(always_accumulate_offset, always_accumulate_len) else { host_call_panic!() }; From fc2e21da12cf9565072e2563efb2fa5e2561c08c Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Mon, 2 Feb 2026 21:55:21 +0900 Subject: [PATCH 04/10] Enhance TRANSFER CASH check --- pvm/pvm-host/src/host_functions/accumulate/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pvm/pvm-host/src/host_functions/accumulate/mod.rs b/pvm/pvm-host/src/host_functions/accumulate/mod.rs index 308c67f4..3f31f72d 100644 --- a/pvm/pvm-host/src/host_functions/accumulate/mod.rs +++ b/pvm/pvm-host/src/host_functions/accumulate/mod.rs @@ -555,7 +555,10 @@ impl AccumulateHostFunction { // --- Check Sender Balance (Err: CASH) let amount = vm.read_reg(8); - if accumulator_balance.saturating_sub(amount) < accumulator_threshold_balance { + let Some(remaining_balance) = accumulator_balance.checked_sub(amount) else { + continue_cash!() + }; + if remaining_balance < accumulator_threshold_balance { continue_cash!() } From 843596ed52d168fa02ac0f5885fb1c99c10071a8 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Mon, 2 Feb 2026 22:11:40 +0900 Subject: [PATCH 05/10] Check preimage validity in the integration stage of the parallel accumulation --- pvm/pvm-invocation/src/accumulate/pipeline.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pvm/pvm-invocation/src/accumulate/pipeline.rs b/pvm/pvm-invocation/src/accumulate/pipeline.rs index 3408cc0c..62c19e7a 100644 --- a/pvm/pvm-invocation/src/accumulate/pipeline.rs +++ b/pvm/pvm-invocation/src/accumulate/pipeline.rs @@ -327,6 +327,25 @@ async fn add_provided_preimages( let preimages_key = hash::(&octets)?; let lookups_key: LookupsKey = (preimages_key.clone(), octets.len() as u32); + // Ignore preimages for missing or removed accounts, or if the lookup request was dropped. + if !partial_state_union + .accounts_sandbox + .account_exists_active(state_manager.clone(), service_id) + .await? + { + continue; + } + let Some(lookups_entry) = partial_state_union + .accounts_sandbox + .get_account_lookups_entry(state_manager.clone(), service_id, &lookups_key) + .await? + else { + continue; + }; + if lookups_entry.timeslots_length() != 0 { + continue; + } + // Insert an entry to the preimages storage partial_state_union .accounts_sandbox From 8df200920a8ec12d7b57f33d55ef4f617d6d38f3 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Mon, 2 Feb 2026 22:19:43 +0900 Subject: [PATCH 06/10] Updates of storage entries of removed accounts should be ignored --- transition/src/state/services.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/transition/src/state/services.rs b/transition/src/state/services.rs index 7c2e6ff7..ab90b63d 100644 --- a/transition/src/state/services.rs +++ b/transition/src/state/services.rs @@ -208,7 +208,12 @@ async fn transition_service_account( _ => (), } + let metadata_removed = matches!(sandbox.metadata.status(), SandboxEntryStatus::Removed); + for (k, v) in sandbox.storage.iter() { + if metadata_removed && *v.status() != SandboxEntryStatus::Removed { + continue; + } match v.status() { SandboxEntryStatus::Added => { state_manager @@ -265,6 +270,9 @@ async fn transition_service_account( } for (k, v) in sandbox.preimages.iter() { + if metadata_removed && *v.status() != SandboxEntryStatus::Removed { + continue; + } match v.status() { SandboxEntryStatus::Added => { state_manager @@ -320,6 +328,9 @@ async fn transition_service_account( } for (k, v) in sandbox.lookups.iter() { + if metadata_removed && *v.status() != SandboxEntryStatus::Removed { + continue; + } match v.status() { SandboxEntryStatus::Added => { state_manager From 16e18d9c5555a296fb45ffd47ced941061a41195 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Tue, 3 Feb 2026 12:43:47 +0900 Subject: [PATCH 07/10] Use `checked_*` for gas limit/usage summations --- pvm/pvm-invocation/src/accumulate/pipeline.rs | 50 ++++++++++++------- pvm/pvm-invocation/src/error.rs | 2 + 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/pvm/pvm-invocation/src/accumulate/pipeline.rs b/pvm/pvm-invocation/src/accumulate/pipeline.rs index 62c19e7a..ba082a96 100644 --- a/pvm/pvm-invocation/src/accumulate/pipeline.rs +++ b/pvm/pvm-invocation/src/accumulate/pipeline.rs @@ -40,20 +40,27 @@ pub struct OuterAccumulationResult { #[inline] fn max_processable_reports(reports: &[WorkReport], gas_limit: UnsignedGas) -> usize { let mut max_processable = 0; - let mut gas_counter = 0; + let mut gas_counter: UnsignedGas = 0; for report in reports { - let report_gas_usage: UnsignedGas = report - .digests - .iter() - .map(|wd| wd.accumulate_gas_limit) - .sum(); + let mut report_gas_limit: UnsignedGas = 0; + for digest in report.digests.iter() { + let Some(digests_gas_limit_sum) = + report_gas_limit.checked_add(digest.accumulate_gas_limit) + else { + return max_processable; + }; + report_gas_limit = digests_gas_limit_sum; + } - if gas_counter + report_gas_usage > gas_limit { + let Some(next_gas_counter) = gas_counter.checked_add(report_gas_limit) else { + break; + }; + if next_gas_counter > gas_limit { break; } - gas_counter += report_gas_usage; + gas_counter = next_gas_counter; max_processable += 1 } @@ -130,7 +137,10 @@ pub async fn accumulate_outer( deferred_transfers = new_deferred_transfers; report_idx += processable_reports_prediction; - let gas_used = service_gas_pairs.iter().map(|pair| pair.gas).sum(); + let gas_used = service_gas_pairs + .iter() + .try_fold(0u64, |acc, pair| acc.checked_add(pair.gas)) + .ok_or(PVMInvokeError::GasOverflow)?; remaining_gas_limit = remaining_gas_limit.saturating_sub(gas_used); service_gas_pairs_flattened.extend(service_gas_pairs); service_output_pairs_flattened.extend(output_pairs.0); @@ -612,22 +622,24 @@ async fn accumulate_single_service( .cloned() .unwrap_or(0); - let reports_gas_aggregated: UnsignedGas = reports + let reports_gas_aggregated = reports .iter() .flat_map(|wr| wr.digests.iter()) .filter(|wd| wd.service_id == service_id) - .map(|wd| wd.accumulate_gas_limit) - .sum(); - - gas_limit += reports_gas_aggregated; + .try_fold(0u64, |acc, wd| acc.checked_add(wd.accumulate_gas_limit)) + .ok_or(PVMInvokeError::GasOverflow)?; + gas_limit = gas_limit + .checked_add(reports_gas_aggregated) + .ok_or(PVMInvokeError::GasOverflow)?; - let deferred_transfers_gas_aggregated: UnsignedGas = prev_deferred_transfers + let deferred_transfers_gas_aggregated = prev_deferred_transfers .iter() .filter(|&transfer| transfer.to == service_id) - .map(|transfer| transfer.gas_limit) - .sum(); - - gas_limit += deferred_transfers_gas_aggregated; + .try_fold(0u64, |acc, transfer| acc.checked_add(transfer.gas_limit)) + .ok_or(PVMInvokeError::GasOverflow)?; + gas_limit = gas_limit + .checked_add(deferred_transfers_gas_aggregated) + .ok_or(PVMInvokeError::GasOverflow)?; AccumulateInvocation::::accumulate( state_manager, diff --git a/pvm/pvm-invocation/src/error.rs b/pvm/pvm-invocation/src/error.rs index 158286dd..e6ee798b 100644 --- a/pvm/pvm-invocation/src/error.rs +++ b/pvm/pvm-invocation/src/error.rs @@ -25,6 +25,8 @@ pub enum PVMInvokeError { MissingAccumulateHostSandbox(ServiceId), #[error("Number of work digests exceeds maximum")] WorkDigestsOverflow, + #[error("Gas usage or gas limit overflowed")] + GasOverflow, #[error("Zero padded justification data size exceeds SEGMENT_SIZE")] PagedProofJustificationBlobTooLarge, #[error("JamCodecError: {0}")] From 71269ff4190852ad5938fbeaf8ea463535e10d72 Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Tue, 3 Feb 2026 13:40:34 +0900 Subject: [PATCH 08/10] Add deferred transfer gas limits from previous parallel accumulate rounds for outer accumulation processable work reports estimation --- pvm/pvm-invocation/src/accumulate/pipeline.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pvm/pvm-invocation/src/accumulate/pipeline.rs b/pvm/pvm-invocation/src/accumulate/pipeline.rs index ba082a96..aae786e7 100644 --- a/pvm/pvm-invocation/src/accumulate/pipeline.rs +++ b/pvm/pvm-invocation/src/accumulate/pipeline.rs @@ -118,13 +118,14 @@ pub async fn accumulate_outer( Vec::new() }; + let prev_deferred_transfers = deferred_transfers; let ParallelAccumulationResult { service_gas_pairs, new_deferred_transfers, service_output_pairs: output_pairs, } = accumulate_parallel( state_manager.clone(), - Arc::new(deferred_transfers), + Arc::new(prev_deferred_transfers.clone()), Arc::new(reports_to_process), Arc::new(always_accumulate_services), &mut partial_state_union, @@ -141,7 +142,18 @@ pub async fn accumulate_outer( .iter() .try_fold(0u64, |acc, pair| acc.checked_add(pair.gas)) .ok_or(PVMInvokeError::GasOverflow)?; - remaining_gas_limit = remaining_gas_limit.saturating_sub(gas_used); + let deferred_transfer_gas = + prev_deferred_transfers + .iter() + .try_fold(0u64, |acc, transfer| { + acc.checked_add(transfer.gas_limit) + .ok_or(PVMInvokeError::GasOverflow) + })?; + remaining_gas_limit = remaining_gas_limit + .checked_add(deferred_transfer_gas) + .ok_or(PVMInvokeError::GasOverflow)? + .checked_sub(gas_used) + .ok_or(PVMInvokeError::GasOverflow)?; service_gas_pairs_flattened.extend(service_gas_pairs); service_output_pairs_flattened.extend(output_pairs.0); } From 5275b40a6996068bfba2c9fbdf5591817fb934be Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Tue, 3 Feb 2026 13:45:53 +0900 Subject: [PATCH 09/10] Use `checked_*` for memory range accessibility check --- pvm/pvm-core/src/state/memory.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pvm/pvm-core/src/state/memory.rs b/pvm/pvm-core/src/state/memory.rs index 19822234..bd317104 100644 --- a/pvm/pvm-core/src/state/memory.rs +++ b/pvm/pvm-core/src/state/memory.rs @@ -159,7 +159,17 @@ impl Memory { if length == 0 { return true; } - let end = start as usize + length - 1; + let start_usize = start as usize; + let end = match start_usize + .checked_add(length) + .and_then(|end| end.checked_sub(1)) + { + Some(end) => end, + None => return false, + }; + if end >= self.data.len() { + return false; + } let (start_page, _) = self.get_page_and_offset(start); let (end_page, _) = self.get_page_and_offset(end as MemAddress); self.is_page_range_readable(start_page..end_page + 1) @@ -205,7 +215,17 @@ impl Memory { if length == 0 { return true; } - let end = start as usize + length - 1; + let start_usize = start as usize; + let end = match start_usize + .checked_add(length) + .and_then(|end| end.checked_sub(1)) + { + Some(end) => end, + None => return false, + }; + if end >= self.data.len() { + return false; + } let (start_page, _) = self.get_page_and_offset(start); let (end_page, _) = self.get_page_and_offset(end as MemAddress); self.is_page_range_writable(start_page..end_page + 1) From 0ded0242e1249d7ec638cb0dc6bf57e7154ae53b Mon Sep 17 00:00:00 2001 From: Junha Park <0xjunha@gmail.com> Date: Tue, 3 Feb 2026 13:47:56 +0900 Subject: [PATCH 10/10] Use `checked_*` for WR accumulation gas sum --- common/src/types/workloads/work_report.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/types/workloads/work_report.rs b/common/src/types/workloads/work_report.rs index a4a24851..57efa7b4 100644 --- a/common/src/types/workloads/work_report.rs +++ b/common/src/types/workloads/work_report.rs @@ -491,7 +491,10 @@ impl WorkReport { } pub fn total_accumulation_gas_allotted(&self) -> UnsignedGas { - self.digests.iter().map(|wd| wd.accumulate_gas_limit).sum() + self.digests + .iter() + .try_fold(0u64, |acc, wd| acc.checked_add(wd.accumulate_gas_limit)) + .unwrap_or(UnsignedGas::MAX) } pub fn extract_exports_manifest(&self) -> ReportedWorkPackage {