fix(security): address remaining CRITICAL audit findings

presale:
- C2: Convert refund_cancelled_presale to batch pattern (start_index, batch_size)
  to prevent unbounded iteration with many contributors
- C3: Add status validation to cancel_presale — prevent cancelling Finalized/Failed
  presales (prevents double-dipping: tokens distributed + refund issued)
- C4: Enforce CreatePresaleOrigin (was defined in Config but never checked)
  Changed to Success = AccountId for proper owner extraction
- Clarified presale_account_id expect() safety comment (Blake2_256 = 32 bytes,
  always sufficient for AccountId decode)
- Removed unused imports (Defensive, AccountIdConversion)

perwerde:
- C5: Prevent NextCourseId overflow — added ensure!(< u32::MAX) check and
  replaced unchecked += 1 with saturating_add

welati:
- C6: Enforce access control on all CollectiveDecisionType variants:
  ConstitutionalReview/Unanimous → Diwan members only
  ExecutiveDecision → Serok only
  HybridDecision → Parliament OR Serok
  VetoOverride → Parliament members only
This commit is contained in:
2026-03-21 21:23:43 +03:00
parent f1a7a7f872
commit 741a65416a
4 changed files with 92 additions and 38 deletions
@@ -218,6 +218,8 @@ pub mod pezpallet {
CourseAlreadyCompleted,
NotCourseOwner,
TooManyCourses,
/// Course ID counter overflow
CourseIdOverflow,
}
#[pezpallet::call]
@@ -233,7 +235,9 @@ pub mod pezpallet {
let owner = T::AdminOrigin::ensure_origin(origin)?;
let course_id = NextCourseId::<T>::get();
// Parameters are already bounded, no conversion needed
// Prevent overflow — ensure we haven't exhausted the u32 ID space
ensure!(course_id < u32::MAX, Error::<T>::CourseIdOverflow);
let course = Course {
id: course_id,
owner: owner.clone(),
@@ -245,7 +249,7 @@ pub mod pezpallet {
};
Courses::<T>::insert(course_id, course);
NextCourseId::<T>::mutate(|id| *id += 1);
NextCourseId::<T>::put(course_id.saturating_add(1));
Self::deposit_event(Event::CourseCreated { course_id, owner });
Ok(())