Platform: Code4rena
Start Date: 07/07/2023
Pot Size: $121,650 USDC
Total HM: 36
Participants: 111
Period: 7 days
Judge: Picodes
Total Solo HM: 13
Id: 258
League: ETH
Rank: 58/111
Findings: 2
Award: $142.55
π Selected for report: 0
π Solo Findings: 0
π Selected for report: Udsen
Also found by: 0xMirce, 0xPsuedoPandit, 0xStalin, 0xbepresent, Aymen0909, Bobface, Co0nan, GREY-HAWK-REACH, Jeiwan, John, KupiaSec, LuchoLeonel1, Nyx, Praise, RedTiger, alexweb3, bin2chen, btk, dacian, dirk_y, josephdara, keccak123, ktg, mahdirostami, markus_ether, minhtrng, ni8mare, peanuts, ptsanev, ravikiranweb3, rvierdiiev, seeques, serial-coder, shaka, teawaterwire, wangxx2026, zzzitron
2.2492 USDC - $2.25
Attacker can mint themselves free Vault shares via Vault.mintYieldFee()
when the vault is collateralized and _yieldFeeTotalSupply > 0
.
Consider Vault.mintYieldFee():
function mintYieldFee(uint256 _shares, address _recipient) external { _requireVaultCollateralized(); if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply); _yieldFeeTotalSupply -= _shares; _mint(_recipient, _shares); emit MintYieldFee(msg.sender, _recipient, _shares); }
This function:
_recipient
parameter,_shares
to that arbitrary address via _mint(_recipient, _shares)
.Whenever _yieldFeeTotalSupply > 0, an attacker can mint themselves free shares by calling mintYieldFee(_yieldFeeTotalSupply, attacker_address)
.
Manual review
The comment above the function indicates that the minted shares should go to _yieldFeeRecipient
, so the fix is simple: _mint(_yieldFeeRecipient, _shares)
Other
#0 - c4-judge
2023-07-14T22:23:06Z
Picodes marked the issue as duplicate of #396
#1 - c4-judge
2023-08-05T22:03:40Z
Picodes marked the issue as satisfactory
π Selected for report: bin2chen
Also found by: 0x11singh99, 0xWaitress, 0xbepresent, ABAIKUNANBAEV, ArmedGoose, Bauchibred, DadeKuma, GREY-HAWK-REACH, GalloDaSballo, Inspecktor, Jeiwan, Kaysoft, MohammedRizwan, Rolezn, Vagner, alexzoid, alymurtazamemon, ayden, banpaleo5, catellatech, dacian, erebus, eyexploit, fatherOfBlocks, grearlake, joaovwfreire, keccak123, kutugu, lanrebayode77, markus_ether, nadin, naman1778, rvierdiiev, squeaky_cactus, volodya, yixxas
140.2996 USDC - $140.30
https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/Claimer.sol#L80 https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L621 https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L1058 https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L473
Claimers will create unexpected & undesired tax liabilities for winning users which will result in disastrous real-world financial consequences for winning users.
From the official docs:
"PoolTogether V5 incentivizes Claimers to claim prizes on behalf of winners. This means that anyone holding prize-wrapped assets will simply receive tokens; they don't need to run a transaction."
This is reflected in the code; Claimer.claimPrizes() -> Vault.claimPrizes() -> Vault.claimPrize() -> PrizePool.claimPrize() which transfers the reward tokens to the recipient who was not the transaction initiator as the transaction was initiated by the Claimer.
In many of the world's tax jurisdictions this receipt of tokens will be considered taxable income incurring a tax liability for the recipient. The taxable amount is commonly the fiat value of the received tokens at the time they were received even if unsold. Claimers will be constantly creating unexpected & potentially undesired real-world tax liabilities for other users.
This can be especially problematic if a user doesn't immediately sell the received tokens (since they could be unaware as they didn't initiate the transaction & they are using PoolTogether as a long-term savings vehicle to HODL). When the user eventually files their tax return if their tax authority has picked up these received tokens, they'll be hit with a tax liability for them with the fiat value on the day they were received.
If by that time the value of the received tokens has decreased substantially (received in bull market, file taxes in bear market), such users would be left with a tax liability that couldn't be covered by liquidating their tokens, which would incur serious financial losses for the users.
For example if the rewards were received using PoolTogether's POOL Token, the tax liability could be very substantial as POOL is currently down -97% to around $0.70
from its all-time bull-market high around $27.65
, meaning that unexpected tax liabilities could incur life-destroying real-world financial losses for users who are caught unawares.
Manual review
Users must be allowed to opt-out of automatically receiving winning token rewards from transactions initiated by Claimers. Winning tokens could accumulate for such users in a vault contract, and users could call a function on that contract to receive their accumulated winnings. This would allow users to control both the time and the price at which tax liability is incurred on their winnings.
Currently a user could use a VaultHook that returns another address they control from beforeClaimPrize() to send their rewards to another address they control / their own vault contract, but this would likely not be sufficient to avoid tax liability since the rewards are just going to another address they control / a contract they deployed.
It would be much cleaner & more defensible if users were able to explicitly opt-out of automatically receiving rewards from Claimers and for users who had opted out, the PrizePool.claimPrize()
put these rewards into a Vault contract which PoolTogether deployed, to be redeemed by the user at a time of their choosing.
Other
#0 - c4-sponsor
2023-07-18T22:50:59Z
asselstine marked the issue as disagree with severity
#1 - asselstine
2023-07-18T22:54:11Z
This is by design; someone who is depositing into PoolTogether is expected to be aware of the fact that they'll receive prizes automatically.
However, I think there is merit in providing functionality that allows users to keep their prizes in the vault.
#2 - c4-judge
2023-08-06T21:37:51Z
Picodes changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-08-06T21:38:21Z
Picodes changed the severity to QA (Quality Assurance)
#4 - Picodes
2023-08-06T21:38:51Z
I don't think this is a security finding in the strict sense of the term, however, it's a very interesting point for an analysis or QA
#5 - c4-judge
2023-08-06T21:39:02Z
Picodes marked the issue as grade-a