Collected fees cannot be claimed after withdrawing the locked amount
Description
The collected fees are distributed in the FeeDistributor and Voter contracts. They use ownerToId
of the VotingEscrow
function to process claims for the collected fees:
// FeeDistributor:
function claim() external returns (uint256) {
_checkpointToken();
address _for = msg.sender;
uint256 _lockerId = IVotingEscrow(votingEscrow).ownerToId(_for);
require(_lockerId != 0, "No lock associated with address");
// ...
}
// Voter:
function claim() external returns (uint256[] memory) {
address _owner = msg.sender;
uint256 _lockerId = Ve(_ve).ownerToId(_owner);
require(_lockerId != 0, "No lock associated with address");
// ...
}
However, when the locked asset is withdrawn from the locker, the ownerToId
variable of the address is cleared:
// VotingEscrow:
function withdraw() external nonreentrant {
// ...
_removeLockerId(_lockerId);
// ...
}
function _removeLockerId(uint256 _lockerId) internal {
// ...
_removeLockerIdFrom(msg.sender, _lockerId);
}
function _removeLockerIdFrom(address _from, uint256 _lockerId) internal {
// ...
idToOwner[_lockerId] = address(0);
ownerToId[_from] = 0;
}
Impact
After a user withdraws their asset from the locker, they cannot claim the collected fees for the locker.
Recommendations
Consider refactoring the contracts not to depend on ownerToId
after the first vote, such as saving the list of lockers of a user in the FeeDistributor and Voter contracts.
Remediation
This issue has been acknowledged by Familia Labs Ltd.. Familia Labs Ltd. stated they plan to document this behavior and encourage users to claim the collected fees before withdrawing assets from the locker in their UI.