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: 16/111
Findings: 3
Award: $1,665.52
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 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
Anyone can call mintYieldFee and steal funds.
if (_yieldFeePercentage != 0) { _increaseYieldFeeBalance( (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut ); }
function _increaseYieldFeeBalance(uint256 _shares) internal { _yieldFeeTotalSupply += _shares; }
After using liquidate() function, _yieldFeeTotalSupply can be increased. The yield fee can serve as a buffer in case of undercollateralization of the Vault.
function mintYieldFee(uint256 _shares, address _recipient) external { //@audit anyone can call this ? _requireVaultCollateralized(); if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply); _yieldFeeTotalSupply -= _shares; _mint(_recipient, _shares); emit MintYieldFee(msg.sender, _recipient, _shares); }
But when undercollateralization is over, anyone can call mintYieldFee() function and steal the funds.
Manual Review
Access Control
Access Control
#0 - c4-judge
2023-07-14T22:20:06Z
Picodes marked the issue as duplicate of #396
#1 - c4-judge
2023-08-05T22:04:46Z
Picodes marked the issue as satisfactory
🌟 Selected for report: Udsen
Also found by: 0x11singh99, 0xPsuedoPandit, Daniel526, Darwin, Inspecktor, Jorgect, Nyx, Praise, Tripathi, YY, catellatech, namx05, squeaky_cactus, xuwinnie
19.2867 USDC - $19.29
https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L296-L306 https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L332-L342
Anyone can call setDrawManager() and be drawManager.
constructor( ConstructorParams memory params ) TieredLiquidityDistributor( params.numberOfTiers, params.tierShares, params.canaryShares, params.reserveShares ) { if (unwrap(params.smoothing) >= unwrap(UNIT)) { revert SmoothingGTEOne(unwrap(params.smoothing)); } prizeToken = params.prizeToken; twabController = params.twabController; smoothing = params.smoothing; claimExpansionThreshold = params.claimExpansionThreshold; drawPeriodSeconds = params.drawPeriodSeconds; _lastClosedDrawStartedAt = params.firstDrawStartsAt; drawManager = params.drawManager; //@audit here if (params.drawManager != address(0)) { emit DrawManagerSet(params.drawManager); } }
If drawManager is not set in the constructor, anyone can call setDrawManager() function and can be drawManager.
function setDrawManager(address _drawManager) external { if (drawManager != address(0)) { revert DrawManagerAlreadySet(); } drawManager = _drawManager; emit DrawManagerSet(_drawManager); }
After became drawManager, attacker can steal funds with withdrawReserve() function.
function withdrawReserve(address _to, uint104 _amount) external onlyDrawManager { if (_amount > _reserve) { revert InsufficientReserve(_amount, _reserve); } _reserve -= _amount; _transfer(_to, _amount); emit WithdrawReserve(_to, _amount); }
Manual Review
Access Control
Access Control
#0 - c4-judge
2023-07-16T22:24:39Z
Picodes marked the issue as duplicate of #356
#1 - c4-judge
2023-08-06T10:32:14Z
Picodes marked the issue as satisfactory
🌟 Selected for report: Nyx
1643.9812 USDC - $1,643.98
Users can alter the record of their balances.
If users delegateBalance has changed, observations will be recorded. If there is a need for new observation, new observation will be created otherwise will be overwritten.
And new observation creation will be decided in the _getNextObservationIndex() function.
if (currentPeriod == 0 || currentPeriod > newestObservationPeriod) { return ( uint16(RingBufferLib.wrap(_accountDetails.nextObservationIndex, MAX_CARDINALITY)), newestObservation, true ); }
newestObservationPeriod is the last observations period. currentPeriod is a period that calculated with uint32 currentTime = uint32(block.timestamp).
uint32 currentPeriod = _getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, currentTime); uint32 newestObservationPeriod = _getTimestampPeriod( PERIOD_LENGTH, PERIOD_OFFSET, newestObservation.timestamp );
function _getTimestampPeriod( uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint32 _timestamp ) private pure returns (uint32 period) { if (_timestamp <= PERIOD_OFFSET) { return 0; } // Shrink by 1 to ensure periods end on a multiple of PERIOD_LENGTH. // Increase by 1 to start periods at # 1. return ((_timestamp - PERIOD_OFFSET - 1) / PERIOD_LENGTH) + 1; }
The problem is, If someone deposits a small amount frequently, currentPeriod and newestObservationPeriod will always be the same and new observation won't be created. Attackers can keep doing this until closeDraw and manipulate their balances.
According to the docs : If a draw were to start and end within a period a user would be able to alter the record of their balance for that draw by overwriting an Observation.
It is important to note that due to Observation overwriting, average balances for a period are not finalized until a period ends. Therefore if a draw ends but a period has not, a user would be able to manipulate their average balance for that final period of time after the draw ends. This would result in an inaccurate record of their balance held during the draw.
Manual Review
Other
#0 - c4-sponsor
2023-07-20T21:56:25Z
asselstine marked the issue as sponsor confirmed
#1 - c4-judge
2023-08-06T22:20:37Z
Picodes changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-08-06T22:23:43Z
Picodes changed the severity to 3 (High Risk)
#3 - c4-judge
2023-08-06T22:23:47Z
Picodes marked the issue as satisfactory
#4 - c4-judge
2023-08-06T22:25:35Z
Picodes changed the severity to 2 (Med Risk)
#5 - Picodes
2023-08-06T22:29:34Z
If someone deposits a small amount frequently, currentPeriod and newestObservationPeriod will always be the same and new observation won't be created
-> this only works for small deposit within the same PERIOD_LENGTH
, otherwise timestamp roundings will differ.
This reports shows how by leveraging the fact that new observations are not created if the previous observations falls within the same period, an attacker could modify its average balance for the final period if a draw ends within a period.
#6 - asselstine
2023-08-17T21:35:28Z
Added safe boundary checks: https://github.com/GenerationSoftware/pt-v5-twab-controller/pull/5