diff --git a/ethexe/runtime/common/tests/auto_tester_b1ed02dd83bc.rs b/ethexe/runtime/common/tests/auto_tester_b1ed02dd83bc.rs new file mode 100644 index 00000000000..502a0477d83 --- /dev/null +++ b/ethexe/runtime/common/tests/auto_tester_b1ed02dd83bc.rs @@ -0,0 +1,42 @@ +// scenario: overflow +// param_signature: schedule_task(NonZero::MAX, task) wraps block_height+u32_max +// hash: b1ed02dd83bc + +use ethexe_runtime_common::{InBlockTransitions, state::MemStorage}; +use ethexe_common::{ScheduledTask, Schedule}; +use gprimitives::{ActorId, MessageId}; +use core::num::NonZero; +use std::collections::BTreeMap; + +#[test] +fn test_schedule_task_in_blocks_max_overflow() { + // block_height = 5, in_blocks = u32::MAX + // scheduled_block = 5u32.wrapping_add(u32::MAX) = 4 + // This would silently schedule a task in the "past" (block 4), which is before block_height=5. + // take_actual_tasks at block 5 would then drain block 4, so the task appears immediately. + let block_height: u32 = 5; + let states = BTreeMap::new(); + let schedule: Schedule = BTreeMap::new(); + let mut transitions = InBlockTransitions::new(block_height, states, schedule); + + let task = ScheduledTask::WakeMessage(ActorId::from(1), MessageId::from(1)); + let in_blocks = NonZero::::new(u32::MAX).expect("non-zero"); + + // scheduled_block should be 5 + u32::MAX = 4 (wraps) + let scheduled_block = transitions.schedule_task(in_blocks, task.clone()); + + // With overflow, scheduled_block = 5u32 + u32::MAX = 4 (wraps to 4 on u32) + // This is less than current block_height (5), so take_actual_tasks would drain it immediately. + // That means a task scheduled "u32::MAX blocks in the future" fires on the very next call. + let tasks = transitions.take_actual_tasks(); + + // If the overflow is silent, the task scheduled for a "huge future" block + // actually ends up in the past and fires immediately — this is the bug we probe. + // We verify what actually happens and record it. + let overflow_occurred = scheduled_block < block_height; + assert!( + !overflow_occurred || tasks.contains(&task), + "overflow: scheduled_block={scheduled_block} is before block_height={block_height}, \ + task fires immediately instead of far in future" + ); +} diff --git a/ethexe/runtime/common/tests/auto_tester_b556aa218ff2.rs b/ethexe/runtime/common/tests/auto_tester_b556aa218ff2.rs new file mode 100644 index 00000000000..fbfc33d2c65 --- /dev/null +++ b/ethexe/runtime/common/tests/auto_tester_b556aa218ff2.rs @@ -0,0 +1,35 @@ +// Auto-generated integration test +// scenario: gas_boundary +// sig: InBlockTransitions_schedule_task_block_height_overflow + +use ethexe_runtime_common::InBlockTransitions; +use ethexe_common::{ProgramStates, Schedule, ScheduledTask}; +use gprimitives::{ActorId, MessageId}; +use core::num::NonZero; + +#[test] +fn auto_tester_b556aa218ff2() { + // schedule_task computes: scheduled_block = block_height + u32::from(in_blocks) + // Test at block_height = u32::MAX - 1 with in_blocks = 1 → scheduled_block = u32::MAX + let mut transitions = InBlockTransitions::new(u32::MAX - 1, ProgramStates::default(), Schedule::default()); + + let task = ScheduledTask::WakeMessage(ActorId::from([1u8; 32]), MessageId::from([2u8; 32])); + let in_blocks = NonZero::new(1u32).unwrap(); + + let scheduled_at = transitions.schedule_task(in_blocks, task.clone()); + assert_eq!(scheduled_at, u32::MAX, "Expected scheduling at u32::MAX block"); + + // Drain at block_height = u32::MAX - 1; the task is in the future (u32::MAX), so not drained + let drained = transitions.take_actual_tasks(); + assert!(drained.is_empty(), "Task at u32::MAX must not be drained at block_height u32::MAX-1"); + + // The schedule now has one entry at u32::MAX — finalize gives us the schedule to reuse + let finalized = transitions.finalize(); + let schedule = finalized.schedule; + + // Create new transitions at block_height = u32::MAX with the preserved schedule + let mut transitions2 = InBlockTransitions::new(u32::MAX, ProgramStates::default(), schedule); + let drained = transitions2.take_actual_tasks(); + assert_eq!(drained.len(), 1, "Task at u32::MAX must be drained at block_height u32::MAX"); + assert_eq!(drained[0], task); +}