69663 sc low users cannot revoke previously granted migration permit after migrator role is revoked
Submitted on Mar 16th 2026 at 06:52:27 UTC by @Oxb4b for Audit Comp | Folks Finance: Staking Contracts
Report ID: #69663
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief/Intro
The setMigrationPermit() function in Staking.sol prevents users from revoking previously granted migration permissions once the migrator's role has been revoked by the admin. This creates a consent-revocation deadlock where stale permissions persist indefinitely and can become active again if the same address is later re-granted the MIGRATOR_ROLE, allowing migrations without fresh user consent.
Vulnerability Details
Current Implementation
function setMigrationPermit(address _migrator, bool _isMigrationPermitted) external {
if (!hasRole(MIGRATOR_ROLE, _migrator)) revert MigratorNotFound(_migrator);
migrationPermits[_migrator][msg.sender] = _isMigrationPermitted;
emit MigrationPermitUpdated(_migrator, msg.sender, _isMigrationPermitted);
}The function enforces hasRole(MIGRATOR_ROLE, _migrator) check regardless of whether the user is granting or revoking permission. This design has an unintended consequence:
Granting permission (
true): The role check is appropriate — users should only grant permits to active migrators.Revoking permission (
false): The role check creates a deadlock — users cannot revoke previously granted permissions if the migrator no longer holds the role.
Root Cause
The migrationPermits mapping is never cleared when a migrator loses the MIGRATOR_ROLE. The single gate (hasRole check) applies to both grant and revoke operations, preventing users from cleaning up stale consent after role revocation.
Consent-Revocation Deadlock Scenario
Alice grants migration permission to migrator
M:setMigrationPermit(M, true)→migrationPermits[M][Alice] = trueAdmin revokes
M's role:revokeRole(MIGRATOR_ROLE, M)Alice attempts to revoke her old permit:
setMigrationPermit(M, false)Call reverts with
MigratorNotFound(M)becausehasRole(MIGRATOR_ROLE, M)now returnsfalseMonths later, admin re-grants role:
grantRole(MIGRATOR_ROLE, M)Mcan now migrate Alice's positions using the stale permit from step 1, without any fresh consent from Alice
Impact and Likelihood Details
Impact: Low
No Direct Fund Loss: Funds are not stolen; migration is a legitimate protocol operation.
Loss of User Autonomy: Users lose the ability to revoke previously granted migration consent, violating the principle that consent should always be user-controlled.
Unexpected Migration: Users who attempted to revoke consent (and were blocked) may be surprised when old permissions become active again after role re-grant.
Likelihood: Low
Requires specific operational sequence: user grants permit → role revoked → user attempts revocation → role re-granted to same address.
However, the bug is deterministic once the sequence occurs — every user who granted a permit before role revocation will experience this issue.
Link to Proof of Concept
https://gist.github.com/tharunbethina/36b3cf2705b65633fc2fd05fe6d8e599
Proof of Concept
Was this helpful?