69505 sc low user cannot revoke migration permit after migrator role is revoked
Submitted on Mar 15th 2026 at 09:35:04 UTC by @hcrlen for Audit Comp | Folks Finance: Staking Contracts
Report ID: #69505
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
The setMigrationPermit function enforces that the target address must currently hold MIGRATOR_ROLE for both granting and revoking permits. This means if an admin revokes a migrator's role, any user who previously granted that migrator a permit is permanently unable to revoke it, breaking the protocol's explicit documentation guarantee.
README.md:
Grant permission to a specific migrator contract:
staking.setMigrationPermit(migratorAddress, true);The migrator must hold the
MIGRATOR_ROLEin the staking contract. The permission can be revoked at any time by callingsetMigrationPermit(migratorAddress, false).
Vulnerability Details
setMigrationPermit checks the migrator role unconditionally:
The role check makes sense when granting a permit. You don't want users granting permits to random addresses. But when revoking, the check is unnecessary and harmful. If the admin revokes MIGRATOR_ROLE from a migrator, any user attempting to call setMigrationPermit(migrator, false) will revert with MigratorNotFound.
The protocol documentation explicitly states:
"The permission can be revoked at any time by calling
setMigrationPermit(migratorAddress, false)."
Impact Details
The stale true permit persists in migrationPermits[migrator][user] mapping. If the admin ever re-grants MIGRATOR_ROLE to the same migrator address, that migrator can immediately call migratePositionsFrom(user) and migrate the user's positions, without the user's current consent, since they were unable to revoke during the window when the role was inactive.
References
Add any relevant links to documentation or code
Proof of Concept
Paste the following code snippet to staking.t.sol:
Was this helpful?