69345 sc low migration permits cannot be revoked after migrator role is revoked despite readme claiming revocation is possible at any time
Submitted on Mar 14th 2026 at 11:09:40 UTC by @Razkky for Audit Comp | Folks Finance: Staking Contracts
Report ID: #69345
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol
Impacts:
Description
The README states that a user can revoke a migrator permission at any time by calling:
staking.setMigrationPermit(migratorAddress, false);Source:
https://github.com/Folks-Finance/folks-staking-contracts/blob/main/README.md#L140
However, the implementation of setMigrationPermit first checks that the target address currently holds MIGRATOR_ROLE:
https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol#L77-L80
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);
}Because of this, once a migrator loses MIGRATOR_ROLE, users can no longer clear the stored approval for that address. The old approval remains in migrationPermits, and if the same address later regains MIGRATOR_ROLE, the prior approval becomes active again.
This appears to be an informational / documentation mismatch rather than a direct asset-loss issue, since exploitation depends on privileged role management. Still, the current behavior is weaker than the user-facing statement that revocation is possible "at any time".
Impact
Informational.
The issue does not directly cause theft or denial of funds on its own, but it means:
users cannot actually revoke a previously granted migration approval after role revocation, despite documentation saying they can;
stale approvals remain stored and can become effective again if the same address is re-granted
MIGRATOR_ROLE.
Proof of Concept
Observe the results:
test_CannotRevokePermitAfterMigratorRoleRevokedshows that once the migrator losesMIGRATOR_ROLE, the user can no longer revoke the stored approval becausesetMigrationPermit(migrator, false)reverts withMigratorNotFound.test_StalePermitBecomesActiveAgainIfRoleReGrantedshows that the stale approval remains stored and becomes active again if the same address later regainsMIGRATOR_ROLE.
Was this helpful?