Foundation recall will inflate governance power for earnest voters
| Title | Foundation recall will inflate governance power for earnest voters |
|---|---|
| Reward | $381, 14 dups |
| Contest | Summer.fi - 29 Sep 2025 on Sherlock |
| Author | Elroi |
| Context | Wrong variable tracking |
This is a straightforward example of functionality the protocol overlooked. It is often a trivial finding, but it touches business logic, so many people miss it.
The SummerVestingWalletEscrow mints xSUMR (governance power) when a user stakes, and locks the tokens in their SummerVestingWallet.
function _stakeFromFactory(
IMinimalVestingFactory _vestingFactory,
address _user
) internal {
// Prevent double-staking same factory to preserve invariant on accounting maps
address factoryAddress = address(_vestingFactory);
if (
_userStakedVestingFactoriesBalance[_user].contains(factoryAddress)
) {
revert Staking_FactoryAlreadyStaked();
}
// Resolve vesting wallet and validate escrow ownership
// vestingWallets map can't be modified, always keeps the original owner
address vestingWallet = _vestingFactory.vestingWallets(_user);
if (vestingWallet == address(0)) {
revert Staking_InvalidAddress("Vesting wallet not found");
}
_validateVestingWalletOwner(vestingWallet);
// Snapshot SUMR balance and released amount at time of stake
uint256 balance = SUMMER_TOKEN.balanceOf(vestingWallet);
if (balance == 0) {
revert Staking_ZeroBalance();
}
uint256 released = IMinimalVestingWallet(vestingWallet).released(
address(SUMMER_TOKEN)
);
// Persist stake metadata for this user/factory pair
_userStakedVestingFactoriesBalance[_user].set(factoryAddress, balance);
_userStakedVestingFactoriesReleased[_user].set(
factoryAddress,
released
);
// Mint governance power (xSUMR) equal to vesting wallet SUMR balance
STAKED_SUMMER_TOKEN.mint(_user, balance);
emit StakedVestingWallet(_user, factoryAddress, balance, released);
}
However, SummerVestingWallet offers the option to withdraw all the tokens, while the user keeps the governance tokens that were minted — which are then left unbacked.
function recallUnvestedTokens() external onlyFactoryOwner {
if (isRecalled) {
revert TokensAlreadyRecalled();
}
// Get ALL tokens from this wallet - no calculations needed
uint256 totalBalance = IERC20(token).balanceOf(address(this));
// Brick the wallet permanently
isRecalled = true;
// Transfer ALL tokens to admin (vested + unvested = everything)
if (totalBalance > 0) {
IERC20(token).safeTransfer(msg.sender, totalBalance);
}
emit UnvestedTokensRecalled(totalBalance);
}
Conclusion
This finding would earn you $381, which is fair since it is very simple: the protocol team completely overlooked the governance tokens left in circulation.