Description
require_admin() explicitly checks AdminExpiresAt and panics with "admin role has expired" if the current ledger sequence has passed it. require_multi_admin() -- used by archive_course(), transfer_admin(), update_treasury(), and set_admin_expiry() itself -- performs no such check; it only verifies that the two callers match the stored Admin/SecondaryAdmin addresses. Once an admin term expires, single-admin operations correctly become unusable, but the same expired admin pair can still archive courses, transfer the admin role, and update the treasury indefinitely via the multi-sig path.
Affected modules: contracts/hamplard/src/lib.rs
An expired admin pair is blocked from single-admin operations but retains full use of every multi-sig operation, including the ability to transfer the admin role to themselves indefinitely.
Expected Behavior
require_multi_admin() should check AdminExpiresAt the same way require_admin() does, so an admin expiry genuinely blocks all privileged operations, not just the single-admin ones.
Tasks
Add the same AdminExpiresAt check to require_multi_admin() that require_admin() already performs.
Add a test asserting multi-sig operations are blocked after the admin expiry ledger has passed.
Add a test asserting transfer_admin()/accept_admin() can still be used to appoint a fresh admin pair after expiry (if that is intended to remain possible).
Description
require_admin() explicitly checks AdminExpiresAt and panics with "admin role has expired" if the current ledger sequence has passed it. require_multi_admin() -- used by archive_course(), transfer_admin(), update_treasury(), and set_admin_expiry() itself -- performs no such check; it only verifies that the two callers match the stored Admin/SecondaryAdmin addresses. Once an admin term expires, single-admin operations correctly become unusable, but the same expired admin pair can still archive courses, transfer the admin role, and update the treasury indefinitely via the multi-sig path.
Affected modules: contracts/hamplard/src/lib.rs
An expired admin pair is blocked from single-admin operations but retains full use of every multi-sig operation, including the ability to transfer the admin role to themselves indefinitely.
Expected Behavior
require_multi_admin() should check AdminExpiresAt the same way require_admin() does, so an admin expiry genuinely blocks all privileged operations, not just the single-admin ones.
Tasks
Add the same AdminExpiresAt check to require_multi_admin() that require_admin() already performs.
Add a test asserting multi-sig operations are blocked after the admin expiry ledger has passed.
Add a test asserting transfer_admin()/accept_admin() can still be used to appoint a fresh admin pair after expiry (if that is intended to remain possible).