Reentrancy via Ownership Transfer Before Stake State Update in StakingPool
_transferOwnership calls ownershipNFT.transferFrom (firing onERC721Received) before migrating stakes[newOwner] to selfStake, opening a reentrancy window where the new owner is recognised as the pool owner with stale stake balances.
Description
In StakingPool.sol, the _transferOwnership function is overridden to support ownership handoff via ERC721 transfer of the ownershipNFT token. However, the current implementation introduces a serious reentrancy vulnerability due to the incorrect ordering of operations.
Here is the function in question:
function _transferOwnership(address newOwner) internal virtual override {address oldOwner = owner();if (oldOwner == newOwner) return;ownershipNFT.transferFrom(oldOwner, newOwner, uint160(address(this)));if (stakes[newOwner] > 0) {selfStake += stakes[newOwner];stakes[newOwner] = 0;}emit OwnershipTransferred(oldOwner, newOwner);}
The issue arises from the call to ownershipNFT.transferFrom(...), which occurs before updating the internal staking state via:
if (stakes[newOwner] > 0) {selfStake += stakes[newOwner];stakes[newOwner] = 0;}
This ordering violates the checks-effects-interactions pattern because transferFrom may trigger arbitrary logic, particularly the onERC721Received hook, on the newOwner if it is a smart contract. At this point in execution:
- The
newOwneralready owns the pool NFT. - The
stakes[newOwner]balance is not yet migrated toselfStake. - The
owner()has not been officially updated yet either (depending on how ownership is managed), potentially introducing inconsistencies.
Impact
A malicious contract receiving ownership can re-enter the StakingPool through onERC721Received while still holding the pre-migration stakes[newOwner] balance, allowing double-counted operations or unintended state reads.
Recommendation
- Reorder the logic to update staking state before transferring the NFT:
function _transferOwnership(address newOwner) internal virtual override {address oldOwner = owner();if (oldOwner == newOwner) return;// Migrate stake to selfStake before transferring NFTif (stakes[newOwner] > 0) {selfStake += stakes[newOwner];stakes[newOwner] = 0;}ownershipNFT.transferFrom(oldOwner, newOwner, uint160(address(this)));emit OwnershipTransferred(oldOwner, newOwner);}
- Add
nonReentrantmodifier.

