Summary
grant_role accepts any expires_at: Option<u64> value. Passing expires_at = Some(1) (or any timestamp before env.ledger().timestamp()) creates a RoleGrant that is immediately invalid. has_role will detect and remove it on the first call, but until then RoleCount is inflated, the storage rent is wasted, and any caller that reads the grant directly (via get_role_grant) would see a role that is no longer valid but not yet cleaned up.
Location
contracts/governance-core/src/lib.rs, grant_role
pub fn grant_role(env: Env, admin: Address, account: Address, role: Role, expires_at: Option<u64>) {
// ❌ No validation: if let Some(exp) = expires_at { if exp <= now { panic!(...) } }
Fix
if let Some(exp) = expires_at {
if exp <= env.ledger().timestamp() {
panic!("expires_at must be in the future");
}
}
Summary
grant_roleaccepts anyexpires_at: Option<u64>value. Passingexpires_at = Some(1)(or any timestamp beforeenv.ledger().timestamp()) creates aRoleGrantthat is immediately invalid.has_rolewill detect and remove it on the first call, but until thenRoleCountis inflated, the storage rent is wasted, and any caller that reads the grant directly (viaget_role_grant) would see a role that is no longer valid but not yet cleaned up.Location
contracts/governance-core/src/lib.rs,grant_roleFix