Platform: Code4rena
Start Date: 31/08/2023
Pot Size: $55,000 USDC
Total HM: 5
Participants: 30
Period: 6 days
Judge: hickuphh3
Total Solo HM: 2
Id: 282
League: ETH
Rank: 12/30
Findings: 1
Award: $695.61
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ADM
Also found by: HChang26, rvierdiiev, twicek
695.6101 USDC - $695.61
https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L273-L278 https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L130-L133 https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1667-L1673 https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1500-L1554
The withdrawFees
function is calling autoClaimEarnings
which will update the bondedAmount
of the msg.sender
delegator's account with new rewards.
Here is the execution path:
function withdrawFees(address payable _recipient, uint256 _amount) external whenSystemNotPaused currentRoundInitialized autoClaimEarnings(msg.sender) {
modifier autoClaimEarnings(address _delegator) { _autoClaimEarnings(_delegator); _; }
BondingManager.sol#L1667-L1673
function _autoClaimEarnings(address _delegator) internal { uint256 currentRound = roundsManager().currentRound(); uint256 lastClaimRound = delegators[_delegator].lastClaimRound; if (lastClaimRound < currentRound) { updateDelegatorWithEarnings(_delegator, currentRound, lastClaimRound); } }
(updateDelegatorWithEarnings
summarized)
BondingManager.sol#L1500-L1554
function updateDelegatorWithEarnings( address _delegator, uint256 _endRound, uint256 _lastClaimRound ) internal { Delegator storage del = delegators[_delegator]; uint256 startRound = _lastClaimRound.add(1); uint256 currentBondedAmount = del.bondedAmount; uint256 currentFees = del.fees; // Only will have earnings to claim if you have a delegate // If not delegated, skip the earnings claim process if (del.delegateAddress != address(0)) { (currentBondedAmount, currentFees) = pendingStakeAndFees(_delegator, _endRound); ... } ... del.lastClaimRound = _endRound; // Rewards are bonded by default del.bondedAmount = currentBondedAmount; del.fees = currentFees; }
As you can see above, updateDelegatorWithEarnings
will update the delegator's bondedAmount
with currentBondedAmount
which is being updated with rewards in pendingStakeAndFees
. Any update of a checkpointed field requires that autoCheckpoint
is called, which is not done in withdrawFees
.
Voting power determined by the checkpointed bondedAmount
will not be up to date with the current bondedAmount
which will reduce voting power for delegators.
Manual Review
Consider using the autoCheckpoint
modifier in withdrawFees
.
function withdrawFees(address payable _recipient, uint256 _amount) external whenSystemNotPaused currentRoundInitialized autoClaimEarnings(msg.sender) autoCheckpoint(msg.sender) {
Other
#0 - c4-pre-sort
2023-09-07T11:34:11Z
141345 marked the issue as duplicate of #104
#1 - c4-judge
2023-09-18T02:17:36Z
HickupHH3 marked the issue as satisfactory