69008 sc low denial of service on migration permit revocation
Submitted on Mar 12th 2026 at 11:15:24 UTC by @Afriauditor for Audit Comp | Folks Finance: Staking Contracts
Report ID: #69008
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol
Impacts:
Smart contract unable to operate due to lack of token funds
Description
Brief/Intro
The known issues acknowledge that migrationPermits may contain a migrator which had its MIGRATOR_ROLE later revoked. However, the actual impact extends beyond stale state users who actively want to set their permit to false for that revoked address are denied the ability to do so.
Users who have previously called setMigrationPermit(migrator, true) are permanently denied the ability to revoke that approval if the migrator address subsequently loses its MIGRATOR_ROLE. The function enforces hasRole(MIGRATOR_ROLE, _migrator) unconditionally, causing any attempt to call setMigrationPermit(migrator, false) to revert with MigratorNotFound. This is a denial of service on a user safety action the user is blocked from managing their own permissions, and the stale true permit remains in storage indefinitely with no alternative path to clear it.
Vulnerability Details
The setMigrationPermit function applies the same hasRole validation regardless of whether the user is granting or revoking:
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);
}When _isMigrationPermitted is false, the user is attempting to protect themselves by removing a previous approval. This is a defensive action that should always be available to the user unconditionally. However, the hasRole check blocks it if the migrator no longer holds the role.
Consider the following sequence:
Admin grants
MIGRATOR_ROLEto address0xMIGAlice calls
setMigrationPermit(0xMIG, true)— succeedsAdmin revokes
MIGRATOR_ROLEfrom0xMIGAlice calls
setMigrationPermit(0xMIG, false)— reverts withMigratorNotFoundmigrationPermits[0xMIG][alice]is stuck astruepermanently
The known issue covers the existence of stale state, but does not address that the contract actively prevents users from cleaning it up. There is no admin function to batch-clear permits, no expiry mechanism, and no alternative function for Alice to remove her stale approval. The setMigrationPermit function is the only path to modify the migrationPermits mapping, and it is permanently blocked for this address.
Impact Details
Users are denied the ability to revoke their own migration permits after a migrator role rotation
Proof of Concept
Run with forge test --match-test test_CannotRevokeMigrationPermitAfterRoleRemoval -vvv. The test proves Alice's setMigrationPermit(migrator, false) reverts after the role is removed, leaving her permit permanently stuck as true.
Was this helpful?