diff --git a/prdoc/pr_4823.prdoc b/prdoc/pr_4823.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a498b33f7bfa949078ca23ac196116b7f5b2f1f9 --- /dev/null +++ b/prdoc/pr_4823.prdoc @@ -0,0 +1,11 @@ +title: "`pallet-referenda`: Ensure to schedule referendas earliest at the next block" + +doc: + - audience: Runtime User + description: | + Ensure that referendas are scheduled earliest at the next block when they are enacted. + Otherwise the scheduling may fails and thus, the enactment of the referenda. + +crates: + - name: pallet-referenda + bump: patch diff --git a/substrate/frame/referenda/src/lib.rs b/substrate/frame/referenda/src/lib.rs index fbe27e1a4784733f517bb6ee3259ebdc4b1075b3..0cdf450d3b6c98e942e3613210f31eb34098b008 100644 --- a/substrate/frame/referenda/src/lib.rs +++ b/substrate/frame/referenda/src/lib.rs @@ -891,7 +891,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { call: BoundedCallOf<T, I>, ) { let now = frame_system::Pallet::<T>::block_number(); - let earliest_allowed = now.saturating_add(track.min_enactment_period); + // Earliest allowed block is always at minimum the next block. + let earliest_allowed = now.saturating_add(track.min_enactment_period.max(One::one())); let desired = desired.evaluate(now); let ok = T::Scheduler::schedule_named( (ASSEMBLY_ID, "enactment", index).using_encoded(sp_io::hashing::blake2_256), diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index d47da45581195b43328d081ea3373a36487847d3..bf0fa4e1a12e17a90f2d35fd555d5805765b26be 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -112,7 +112,7 @@ impl TracksInfo<u64, u64> for TestTracksInfo { type Id = u8; type RuntimeOrigin = <RuntimeOrigin as OriginTrait>::PalletsOrigin; fn tracks() -> &'static [(Self::Id, TrackInfo<u64, u64>)] { - static DATA: [(u8, TrackInfo<u64, u64>); 2] = [ + static DATA: [(u8, TrackInfo<u64, u64>); 3] = [ ( 0u8, TrackInfo { @@ -157,6 +157,28 @@ impl TracksInfo<u64, u64> for TestTracksInfo { }, }, ), + ( + 2u8, + TrackInfo { + name: "none", + max_deciding: 3, + decision_deposit: 1, + prepare_period: 2, + decision_period: 2, + confirm_period: 1, + min_enactment_period: 0, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), + }, + }, + ), ]; &DATA[..] } @@ -165,6 +187,7 @@ impl TracksInfo<u64, u64> for TestTracksInfo { match system_origin { frame_system::RawOrigin::Root => Ok(0), frame_system::RawOrigin::None => Ok(1), + frame_system::RawOrigin::Signed(1) => Ok(2), _ => Err(()), } } else { diff --git a/substrate/frame/referenda/src/tests.rs b/substrate/frame/referenda/src/tests.rs index 52251fcbdbeed0ec5b8830e87a2a4c66bb265e39..3f859636f7cbb34d36d9fbafdfa05c189905d2c6 100644 --- a/substrate/frame/referenda/src/tests.rs +++ b/substrate/frame/referenda/src/tests.rs @@ -682,3 +682,27 @@ fn detects_incorrect_len() { ); }); } + +/// Ensures that `DispatchTime::After(0)` plus `min_enactment_period = 0` works. +#[test] +fn zero_enactment_delay_executes_proposal_at_next_block() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Signed(1).into()), + Preimage::bound( + pallet_balances::Call::transfer_keep_alive { dest: 42, value: 20 }.into() + ) + .unwrap(), + DispatchTime::After(0), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(1), 0)); + assert_eq!(ReferendumCount::<Test>::get(), 1); + set_tally(0, 100, 0); + + run_to(9); + + assert_eq!(Balances::free_balance(42), 20); + }); +}