fix(security): address HIGH audit findings across 5 pallets
identity-kyc (H1): - Add IdentityHashToAccount reverse mapping to prevent same identity hash being used by multiple accounts - Check uniqueness in apply_for_citizenship, populate on confirm_citizenship, clean up on renounce_citizenship pez-rewards (H2): - Add EpochTotalClaimed storage to track claimed amounts per epoch - do_close_epoch now only claws back unclaimed rewards (total_allocated - total_claimed), not the entire pot balance tiki (H3): - Replace custom "locked" attribute with pezpallet_nfts::disable_transfer() which sets the system-level PalletAttributes::TransferDisabled attribute that is actually enforced during transfers tiki (H4): - Fix EnsureTiki to check UserTikis storage for non-unique roles (Wezir, Parlementer) instead of TikiHolder which only stores unique roles perwerde (H5): - Add MaxPointsPerCourse config constant (1000 in runtime) - Validate points in complete_course against the max - Use saturating_add in get_perwerde_score to prevent u32 overflow welati (H6): - Add NativeCurrency: ReservableCurrency to Config - Actually reserve candidacy deposit from candidate's balance welati (H7): - Add MaxEndorsers config constant (1000 in runtime) - Validate endorsers count at the start of register_candidate before any storage reads
This commit is contained in:
@@ -142,6 +142,11 @@ pub mod pezpallet {
|
||||
#[pezpallet::constant]
|
||||
type MaxCoursesPerStudent: Get<u32>;
|
||||
|
||||
/// Maximum points that can be awarded per course completion.
|
||||
/// Prevents unbounded point inflation by course owners.
|
||||
#[pezpallet::constant]
|
||||
type MaxPointsPerCourse: Get<u32>;
|
||||
|
||||
/// Trust score updater - notifies trust pallet when perwerde score changes
|
||||
type TrustScoreUpdater: TrustScoreUpdater<Self::AccountId>;
|
||||
}
|
||||
@@ -220,6 +225,8 @@ pub mod pezpallet {
|
||||
TooManyCourses,
|
||||
/// Course ID counter overflow
|
||||
CourseIdOverflow,
|
||||
/// Points exceed the maximum allowed per course
|
||||
PointsExceedMax,
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
@@ -295,6 +302,9 @@ pub mod pezpallet {
|
||||
) -> DispatchResult {
|
||||
let caller = ensure_signed(origin)?;
|
||||
|
||||
// Validate points are within the allowed maximum
|
||||
ensure!(points <= T::MaxPointsPerCourse::get(), Error::<T>::PointsExceedMax);
|
||||
|
||||
// Verify caller is the course owner
|
||||
let course = Courses::<T>::get(course_id).ok_or(Error::<T>::CourseNotFound)?;
|
||||
ensure!(course.owner == caller, Error::<T>::NotCourseOwner);
|
||||
@@ -344,7 +354,7 @@ pub mod pezpallet {
|
||||
.filter_map(|course_id| Enrollments::<T>::get((who, *course_id)))
|
||||
.filter(|enrollment| enrollment.completed_at.is_some())
|
||||
.map(|enrollment| enrollment.points_earned)
|
||||
.sum()
|
||||
.fold(0u32, |acc, points| acc.saturating_add(points))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ parameter_types! {
|
||||
pub const MaxCourseLinkLength: u32 = 200;
|
||||
pub const MaxStudentsPerCourse: u32 = 100; // Reduced for test performance
|
||||
pub const MaxCoursesPerStudent: u32 = 50; // Max courses a student can enroll in
|
||||
pub const MaxPointsPerCourse: u32 = 1000; // Max points per course completion
|
||||
}
|
||||
|
||||
// --- KESİN ÇÖZÜM BURADA BAŞLIYOR ---
|
||||
@@ -111,6 +112,7 @@ impl pezpallet_perwerde::Config for Test {
|
||||
type MaxCourseLinkLength = MaxCourseLinkLength;
|
||||
type MaxStudentsPerCourse = MaxStudentsPerCourse;
|
||||
type MaxCoursesPerStudent = MaxCoursesPerStudent;
|
||||
type MaxPointsPerCourse = MaxPointsPerCourse;
|
||||
type TrustScoreUpdater = ();
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ fn complete_course_with_zero_points() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_course_with_max_points() {
|
||||
fn complete_course_with_max_allowed_points() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let admin = 0;
|
||||
let student = 1;
|
||||
@@ -376,16 +376,38 @@ fn complete_course_with_max_points() {
|
||||
));
|
||||
assert_ok!(PerwerdePallet::enroll(RuntimeOrigin::signed(student), 0));
|
||||
|
||||
// Complete with maximum points
|
||||
// Complete with maximum allowed points (MaxPointsPerCourse = 1000)
|
||||
assert_ok!(PerwerdePallet::complete_course(
|
||||
RuntimeOrigin::signed(admin),
|
||||
student,
|
||||
0,
|
||||
u32::MAX
|
||||
1000
|
||||
));
|
||||
|
||||
let enrollment = crate::Enrollments::<Test>::get((student, 0)).unwrap();
|
||||
assert_eq!(enrollment.points_earned, u32::MAX);
|
||||
assert_eq!(enrollment.points_earned, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_course_fails_points_exceed_max() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let admin = 0;
|
||||
let student = 1;
|
||||
|
||||
assert_ok!(PerwerdePallet::create_course(
|
||||
RuntimeOrigin::signed(admin),
|
||||
create_bounded_vec(b"Course"),
|
||||
create_bounded_vec(b"Desc"),
|
||||
create_bounded_vec(b"http://example.com")
|
||||
));
|
||||
assert_ok!(PerwerdePallet::enroll(RuntimeOrigin::signed(student), 0));
|
||||
|
||||
// Points exceeding MaxPointsPerCourse (1000) should fail
|
||||
assert_noop!(
|
||||
PerwerdePallet::complete_course(RuntimeOrigin::signed(admin), student, 0, 1001),
|
||||
crate::Error::<Test>::PointsExceedMax
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user