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

1

Add the following test cases to test/Staking.t.sol.

2

Run the following command:

3

Output

4

Observe the results:

  • test_CannotRevokePermitAfterMigratorRoleRevoked shows that once the migrator loses MIGRATOR_ROLE, the user can no longer revoke the stored approval because setMigrationPermit(migrator, false) reverts with MigratorNotFound.

  • test_StalePermitBecomesActiveAgainIfRoleReGranted shows that the stale approval remains stored and becomes active again if the same address later regains MIGRATOR_ROLE.

Was this helpful?