[Governance 3/4] Introduce pallet-referenda#2807
Conversation
| let now = T::BlockNumberProvider::current_block_number(); | ||
| let index = ReferendumCount::<T>::get(); | ||
| ReferendumCount::<T>::put(index.saturating_add(1)); | ||
| ActiveCount::<T>::put(active.saturating_add(1)); | ||
| ActivePerProposer::<T>::insert(&proposer, active_per_proposer.saturating_add(1)); | ||
|
|
||
| let proposal = match &track_info.decision_strategy { | ||
| DecisionStrategy::PassOrFail { | ||
| decision_period, .. | ||
| } => { | ||
| let when = now.saturating_add(*decision_period); | ||
| Self::set_alarm(index, when)?; | ||
| let bounded_call = T::Preimages::bound(*call)?; | ||
| Proposal::Action(bounded_call) | ||
| } | ||
| DecisionStrategy::Adjustable { initial_delay, .. } => { | ||
| let when = now.saturating_add(*initial_delay); | ||
| Self::schedule_enactment(index, DispatchTime::At(when), call)?; | ||
| Proposal::Review | ||
| } | ||
| }; |
There was a problem hiding this comment.
[HIGH] submit leaks queue slots when setup fails
ReferendumCount, ActiveCount, and ActivePerProposer are mutated before fallible setup (set_alarm, Preimages::bound, and schedule_enactment). FRAME does not automatically roll back earlier storage writes when an extrinsic returns DispatchError, so an authorized proposer can repeatedly trigger one of these failures and leave no ReferendumStatusFor entry for kill to clear. That permanently consumes MaxQueued / per-proposer capacity and can block governance submissions. Perform all fallible setup first, then mutate the queue counters only after the referendum can be fully recorded; if an already-bounded call must be abandoned because scheduling fails, drop that preimage handle before returning.
| let now = T::BlockNumberProvider::current_block_number(); | |
| let index = ReferendumCount::<T>::get(); | |
| ReferendumCount::<T>::put(index.saturating_add(1)); | |
| ActiveCount::<T>::put(active.saturating_add(1)); | |
| ActivePerProposer::<T>::insert(&proposer, active_per_proposer.saturating_add(1)); | |
| let proposal = match &track_info.decision_strategy { | |
| DecisionStrategy::PassOrFail { | |
| decision_period, .. | |
| } => { | |
| let when = now.saturating_add(*decision_period); | |
| Self::set_alarm(index, when)?; | |
| let bounded_call = T::Preimages::bound(*call)?; | |
| Proposal::Action(bounded_call) | |
| } | |
| DecisionStrategy::Adjustable { initial_delay, .. } => { | |
| let when = now.saturating_add(*initial_delay); | |
| Self::schedule_enactment(index, DispatchTime::At(when), call)?; | |
| Proposal::Review | |
| } | |
| }; | |
| let now = T::BlockNumberProvider::current_block_number(); | |
| let index = ReferendumCount::<T>::get(); | |
| let proposal = match &track_info.decision_strategy { | |
| DecisionStrategy::PassOrFail { | |
| decision_period, .. | |
| } => { | |
| let bounded_call = T::Preimages::bound(*call)?; | |
| let when = now.saturating_add(*decision_period); | |
| if let Err(err) = Self::set_alarm(index, when) { | |
| T::Preimages::drop(&bounded_call); | |
| return Err(err); | |
| } | |
| Proposal::Action(bounded_call) | |
| } | |
| DecisionStrategy::Adjustable { initial_delay, .. } => { | |
| let when = now.saturating_add(*initial_delay); | |
| Self::schedule_enactment(index, DispatchTime::At(when), call)?; | |
| Proposal::Review | |
| } | |
| }; | |
| ReferendumCount::<T>::put(index.saturating_add(1)); | |
| ActiveCount::<T>::put(active.saturating_add(1)); | |
| ActivePerProposer::<T>::insert(&proposer, active_per_proposer.saturating_add(1)); |
🛡️ AI Review — Skeptic (security review)VERDICT: VULNERABLE BASELINE scrutiny: l0r1s is an established contributor with write permission; no Gittensor allowlist hit found. Branch is governance-referenda -> governance-signed-voting. No Findings
ConclusionThe PR introduces a governance queue DoS in # 🔍 AI Review — Auditor (domain review) has not yet run on this PR. |
|
🔄 AI review updated — Skeptic: VULNERABLE |
WIP