Bribe.sol is not meant to handle fee-on-transfer tokens #222
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
sponsor acknowledged
Technically the issue is correct, but we're not going to resolve it for XYZ reasons
Lines of code
https://github.com/code-423n4/2022-05-velodrome/blob/main/contracts/contracts/Bribe.sol#L50-L51
https://github.com/code-423n4/2022-05-velodrome/blob/main/contracts/contracts/Bribe.sol#L83-L90
Vulnerability details
Impact
Should a fee-on-transfer token be added as a reward token and deposited, the tokens will be locked in the
Bribe
contract. Voters will be unable to withdraw their rewards.Proof of Concept
Tokens are deposited into the
Bribe
contract usingnotifyRewardAmount()
, whereamount
of tokens are transferred, then added directly totokenRewardsPerEpoch[token][adjustedTstamp]
:Tokens are transferred out of the
Bribe
contract usingdeliverReward()
, which attempts to transfertokenRewardsPerEpoch[token][epochStart]
amount of tokens out.If
token
happens to be a fee-on-transfer token,deliverReward()
will always fail. For example:notifyRewardAmount()
, withtoken
as token that charges a 2% fee upon any transfer, andamount = 100
:_safeTransferFrom()
only transfers 98 tokens to the contract due to the 2% feeepochRewards = 0
,tokenRewardsPerEpoch[token][adjustedTstamp]
becomes100
deliverReward()
is called with the sametoken
andepochStart
:rewardPerEpoch = tokenRewardsPerEpoch[token][epochStart] = 100
_safeTransfer
attempts to transfer 100 tokens out of the contractdeliverReward()
revertsThe following test, which implements a MockERC20 with fee-on-transfer, demonstrates this:
Additional Impact
On a larger scale, a malicious attacker could temporarily DOS any
Gauge
contract. This can be done by:Bribe
contract, usingnotifyRewardAmount()
, and adding it as a reward token.deliverBribes()
to fail whenever it is called, thus no one would be able to withdraw any reward tokens from theGauge
contract.The only way to undo the DOS would be to call
swapOutBribeRewardToken()
and swap out the fee-on-transfer token for another valid token.Recommended Mitigation
epochRewards
and stored intokenRewardsPerEpoch[token][adjustedTstamp]
, instead of the amount stated for transfer. For example:The text was updated successfully, but these errors were encountered: