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: 23/111
Findings: 3
Award: $1,149.42
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: wvleak
Also found by: 0xbepresent, 3docSec, DadeKuma, Jeiwan, Udsen, dirk_y, keccak123, rvierdiiev, serial-coder, shaka, wvleak
40.0854 USDC - $40.09
Prize claims can be executed by external actors to get some fees as a reward. These fees are dynamic and are limited in range, and some hooks will be executed before and after the claim.
As the fees are deducted from the prizeTotal
received by the winner, they could be incentivized to create an afterClaimPrize
hook that reverts the transaction when fees are too high, to maximize the total profit.
As the transaction is executed by the claimer, the winner doesn't lose anything by doing so, and at most, they can wait until the last moment before their prize expires to disable the hook.
This results in a very bad experience for claimers as their transaction will revert and they will waste gas, possibly eroding trust in the claiming system.
claimPrize
is executed between two hooks:
if (hooks.useBeforeClaimPrize) { recipient = hooks.implementation.beforeClaimPrize(_winner, _tier, _prizeIndex); } else { recipient = _winner; } uint prizeTotal = _prizePool.claimPrize( _winner, _tier, _prizeIndex, recipient, _fee, _feeRecipient ); if (hooks.useAfterClaimPrize) { hooks.implementation.afterClaimPrize( _winner, _tier, _prizeIndex, prizeTotal - _fee, recipient ); }
A vault can check the fee paid for this claim by calculating the delta between prizeTotal
and prizeTotal - _fee
, as they are part of the same transaction.
If this delta is too high, the vault might revert the transaction inside the afterClaimPrize
hook until the market conditions are better.
Manual review
Consider removing the afterClaimPrize
hook to avoid this scenario.
DoS
#0 - c4-judge
2023-07-18T15:32:34Z
Picodes marked the issue as duplicate of #465
#1 - c4-judge
2023-08-07T15:17:26Z
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
15.9228 USDC - $15.92
https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L960 https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L873
When users deposit their assets, the resulting shares might be zero due to rounding.
If this happens the transaction will not revert and no shares will be minted, but users will still lose their funds and gain nothing.
There are no checks for zero shares minted in deposit
, nor in _deposit
:
function deposit(uint256 _assets, address _receiver) public virtual override returns (uint256) { if (_assets > maxDeposit(_receiver)) revert DepositMoreThanMax(_receiver, _assets, maxDeposit(_receiver)); uint256 _shares = _convertToShares(_assets, Math.Rounding.Down); _deposit(msg.sender, _receiver, _assets, _shares); return _shares; }
The issue can be replicated with the following steps:
deposit
function._assets.mulDiv(_assetUnit, _exchangeRate, _rounding)
Resulting shares are zero as _rounding
is down (this is correct and EIP-4626 compliant), but then the transaction doesn't revert.
Bob transfered his _assets
but he gains zero shares, losing his funds.
Manual review
Consider reverting the transaction if a deposit would result in zero shares minted.
Invalid Validation
#0 - c4-judge
2023-07-16T22:29:10Z
Picodes changed the severity to QA (Quality Assurance)
#1 - c4-judge
2023-08-08T14:34:11Z
Picodes marked the issue as grade-b
#2 - PierrickGT
2023-08-10T22:30:14Z
Fixed in the following PR: https://github.com/GenerationSoftware/pt-v5-vault/pull/17
🌟 Selected for report: 0xStalin
Also found by: 0xSmartContract, 0xWaitress, 3docSec, ABAIKUNANBAEV, DadeKuma, Jeiwan, K42, catellatech, dirk_y, keccak123, peanuts, squeaky_cactus
1093.4075 USDC - $1,093.41
Id | Title |
---|---|
01 | High-level architecture |
02 | Analysis of the codebase |
03 | Architecture feedback |
04 | Centralization risks |
05 | Systemic risks |
The separation of concerns between Vaults, Prize Pool, TWAB, and other components allows for modularity and an easier code review.
The codebase quality is extremely high, with extensive comments that explain every step in detail.
Test quality is extremely high, with high coverage and usage of advanced testing techniques (e.g. fuzzing).
The architecture of PoolTogether V5 is well thought out and designed for flexibility. It allows for the inclusion of any ERC20-compliant asset and the customization of any ERC-4626 Vault.
Some of the new features include a novel hand-off approach where users don't need to manually claim their prizes as there are incentives for third parties to do so.
The use of a weighted average to smooth contributions over time provides a fair distribution of prizes and encourages participation from Vaults with a periodic yield generation.
The architecture of the TWAB Controller appears well-designed to fulfill its purpose of accurately tracking and calculating time-weighted average balances.
The utilization of Observations to record relevant data points and the approach of overwriting Observations within periods for gas efficiency are clever design choices. This is ensured by aligning periods with draws, as the TWAB Controller ensures accurate tracking of balances during draw periods.
The PoolTogether V5 Protocol aims to be autonomous and permissionless, reducing centralization risks.
It's important to evaluate the dependencies and external services used, such as the RNG service to extract the winners, to ensure they are decentralized and reliable.
Claimers should always have a high incentive to claim winner prizes as the new V5 version is marketed as a hand-off approach. Failure to do so might result in an expired prize, as (non-claimer) users don't expect to claim manually.
The random number generation process for Draw completion is critical, and reliance on external RNG services introduces risks if those services are compromised or manipulated.
Periods are not considered finalized until the full period length has passed. Balances and average balances within a period that has not ended may still be subject to changes due to Observations being overwritten. This can lead to inaccurate balances and average balances for that period.
Historic balances and average balances are only guaranteed to be accurate within specific conditions. Historic balances are accurate for timestamps falling between the newest Observation recorded before the end of a finalized period and the end time of that period. Average balances across time ranges are accurate if temporary Observations can be accurately recreated on both ends of the range and if time-weighted balances have been captured throughout the range.
Loss of historic information: due to the compression of information and overwriting of Observations, specific timestamps at which balances were held throughout a period may be lost, so external actors should be aware of it.
12 hours
#0 - c4-judge
2023-07-18T18:47:15Z
Picodes marked the issue as grade-a
#1 - c4-sponsor
2023-07-20T22:13:44Z
asselstine marked the issue as sponsor confirmed