Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $36,500 USDC
Total HM: 9
Participants: 80
Period: 7 days
Judge: hansfriese
Total Solo HM: 2
Id: 332
League: ETH
Rank: 21/80
Findings: 3
Award: $165.52
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: DarkTower
Also found by: 0xJaeger, 0xJoyBoy03, 0xRiO, 0xkeesmark, 0xlemon, 0xmystery, Abdessamed, AcT3R, Afriauditor, AgileJune, Al-Qa-qa, Aymen0909, Daniel526, DanielTan_MetaTrust, Dots, FastChecker, Fitro, GoSlang, Greed, Krace, McToady, SoosheeTheWise, Tripathi, asui, aua_oo7, btk, crypticdefense, d3e4, dd0x7e8, dvrkzy, gesha17, iberry, kR1s, leegh, marqymarq10, n1punp, pa6kuda, radin100, sammy, smbv-1923, trachev, turvy_fuzz, valentin_s2304, wangxx2026, y4y, yotov721, yvuchev, zhaojie
1.4652 USDC - $1.47
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611
PrizeVault::claimYieldFeeShares
Transfers yield fee shares to the yield fee recipient. instead of decreasing transferred shares it resets the yieldFeeBalance even for partial shares
yieldFeeBalance will lost due to incorrect update
/// @notice Transfers yield fee shares to the yield fee recipient /// @param _shares The shares to mint to the yield fee recipient /// @dev Emits a `ClaimYieldFeeShares` event /// @dev Will revert if the caller is not the yield fee recipient or if zero shares are withdrawn function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient { if (_shares == 0) revert MintZeroShares(); uint256 _yieldFeeBalance = yieldFeeBalance; if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance); yieldFeeBalance -= _yieldFeeBalance; //@audit-issue it should be yieldFeeBalance -= shares _mint(msg.sender, _shares); emit ClaimYieldFeeShares(msg.sender, _shares); }
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611
Transferring _shares
should decrease yieldFeeBalance
by _shares
, while it resets the yieldFeeBalance to zero
Manual
- yieldFeeBalance -= _yieldFeeBalance; + yieldFeeBalance -= _shares;
Math
#0 - c4-pre-sort
2024-03-11T21:56:19Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-11T21:56:24Z
raymondfam marked the issue as duplicate of #10
#2 - c4-pre-sort
2024-03-13T04:38:36Z
raymondfam marked the issue as duplicate of #59
#3 - c4-judge
2024-03-15T07:38:37Z
hansfriese marked the issue as satisfactory
🌟 Selected for report: Aymen0909
Also found by: 0xmystery, Abdessamed, FastChecker, Giorgio, Tripathi, btk, cheatc0d3, trachev, turvy_fuzz
131.1445 USDC - $131.14
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L489
prizeVault::withdraw()
is used to withdraw exact number of assets by buring shares and sent to receiver.
But this function lacks maxShares burnt param, without this caller could burn more than expected amount of shares for corresponding assets.
caller will end up burning more shares than he expect
function withdraw( uint256 _assets, address _receiver, address _owner ) external returns (uint256) { uint256 _shares = previewWithdraw(_assets); _burnAndWithdraw(msg.sender, _receiver, _owner, _shares, _assets); return _shares; }
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L489
withdraw only takes _assets
the amount of underlying assets to be withdrawn by burning whatever amount of shares which is not an optimised trade for caller.
In many case caller could end up burning more than he expected
eg. before submitting transaction Alice see that everything is fine
so she submitted a tx for withdrawing assets
due to some malicious actions or inconsistent behaviour vault goes into recovery mode i.e totalAssets()< totalDebt()
Now 1 share corresponds to less than 1 asset but Alice tx will be executed as normal tx and she will end up burning more shares than she expected
Manual
Add proper slippage checks during withdrawing
Other
#0 - c4-pre-sort
2024-03-11T23:47:10Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-11T23:47:20Z
raymondfam marked the issue as duplicate of #90
#2 - c4-pre-sort
2024-03-13T05:09:53Z
raymondfam marked the issue as duplicate of #274
#3 - c4-judge
2024-03-15T08:08:52Z
hansfriese changed the severity to 2 (Med Risk)
#4 - c4-judge
2024-03-15T08:09:30Z
hansfriese marked the issue as satisfactory
32.9145 USDC - $32.91
PrizeVault->Claimable->HookManager::setHooks()
allows Unintended or Malicious Use of Prize Winners' HookThe setHooks function in Vault.sol allows users to set arbitrary hooks, potentially enabling them to make external calls with unintended consequences. This vulnerability could lead to various unexpected behaviors, such as unauthorized side transactions with gas paid unbeknownst to the claimer, reentrant calls, or denial-of-service attacks on claiming transactions.
function setHooks(VaultHooks calldata hooks) external { _hooks[msg.sender] = hooks; emit SetHooks(msg.sender, hooks); }
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/abstract/HookManager.sol#L29 it is possible for a hook to make an external call and modify the EVM state.
Handle these malicious calls
PrizeVaultFactory::deployVault
allows deployment of vaults with non-authentic claimer
, prizePool
etcA malicious vault can be deployed via the official PrizeVaultFactory contract. Users may lose funds while interacting with such vaults.
ensure proper validation or implement kind of trusted role for the deployement of new vaults
PrizeVault::setYieldFeeRecipient
doesn't check for zero addressfunction setYieldFeeRecipient(address _yieldFeeRecipient) external onlyOwner { _setYieldFeeRecipient(_yieldFeeRecipient); } function _setYieldFeeRecipient(address _yieldFeeRecipient) internal { //@audit lacks zero address check yieldFeeRecipient = _yieldFeeRecipient; emit YieldFeeRecipientSet(_yieldFeeRecipient); }
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L958
depositWithPermit
doesn't server the purpose of permit function since even after sign the tx only signer can call this functionfunction depositWithPermit( uint256 _assets, address _owner, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external returns (uint256) { if (_owner != msg.sender) { revert PermitCallerNotOwner(msg.sender, _owner); } // Skip the permit call if the allowance has already been set to exactly what is needed. This prevents // griefing attacks where the signature is used by another actor to complete the permit before this // function is executed. if (_asset.allowance(_owner, address(this)) != _assets) { IERC20Permit(address(_asset)).permit(_owner, address(this), _assets, _deadline, _v, _r, _s);/ } uint256 _shares = previewDeposit(_assets); _depositAndMint(_owner, _owner, _assets, _shares); return _shares; }
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L532 fix this so that it serves the purpose of permit function or remove the function to save deployment cost
depositWithPermit
lacks chainID which allows signatures to be re-used across forksfunction depositWithPermit( uint256 _assets, address _owner, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external returns (uint256) { /////////////////////// if (_asset.allowance(_owner, address(this)) != _assets) { IERC20Permit(address(_asset)).permit(_owner, address(this), _assets, _deadline, _v, _r, _s);/ } /////////////////////// }
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L540 eg.
Scenario :: An EIP is included in an upcoming hard fork that has split the community. After the hard fork, a significant user base remains on the old chain. On the new chain, Alice approves some tokens via a call to permit. Alice, operating on both chains, replays the permit call on the old chain
Include the chainID opcode in the permit schema. This will make replay attacks impossible in the event of a post-deployment hard fork.
#0 - raymondfam
2024-03-13T06:53:04Z
1: See #18 2.: Owner is trusted per readme 3: Bot: [L-10] Missing checks for address(0x0) when updating address state variables 4, 5: See #13
#1 - c4-pre-sort
2024-03-13T06:53:08Z
raymondfam marked the issue as insufficient quality report
#2 - c4-pre-sort
2024-03-13T06:53:13Z
raymondfam marked the issue as grade-c
#3 - c4-judge
2024-03-21T05:52:46Z
hansfriese marked the issue as grade-b