Platform: Code4rena
Start Date: 04/05/2022
Pot Size: $50,000 DAI
Total HM: 24
Participants: 71
Period: 5 days
Judge: Justin Goro
Total Solo HM: 14
Id: 119
League: ETH
Rank: 63/71
Findings: 2
Award: $22.36
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: MaratCerby
Also found by: CertoraInc, Ruhum, VAD37, berndartmueller, broccolirob, cryptphi, danb, gzeon, horsefacts, hyh, joestakey, leastwood, pedroais, peritoflores, throttle, wuwe1, z3s
19.1789 DAI - $19.18
A simple POC: https://gist.github.com/wuwe1/9eb5bf9e4b3f31c8db52f4fa7fac5b13
function fundPool(uint poolId) internal { Pool storage pool = pools[poolId]; bool success = true; uint amount; for (uint i = 0; i < pool.rewardFunding.length; i++) { amount = getMaximumRewards(poolId, i); // transfer the tokens from pool-creator to this contract success = success && IERC20(pool.rewardTokens[i]).transferFrom(msg.sender, address(this), amount);
transferFrom
will revert when pool.rewardTokens
do not return boolean value as expected
Use safeTransferFrom
#0 - illuzen
2022-05-12T05:23:03Z
Duplicate #27
🌟 Selected for report: MaratCerby
Also found by: 0x1337, 0x52, 0xYamiDancho, AuditsAreUS, CertoraInc, Dravee, GimelSec, IllIllI, PPrieditis, Ruhum, TrungOre, VAD37, berndartmueller, cccz, csanuragjain, defsec, hickuphh3, horsefacts, hyh, jayjonah8, kenzo, leastwood, mtz, p4st13r4, reassor, throttle, wuwe1, ych18
3.1753 DAI - $3.18
Since the pool.depositToken
can be any token, it is possible that pool will be created with tokens that support fee on transfer. If a fee on transfer token is chosen, other user's funds might be drained. The actual amount of tokens the contract holds could be less than receipt.amountDepositedWei
PermissionlessBasicPoolFactory.sol
has 200 token.PermissionlessBasicPoolFactory.sol
has 295 token.PermissionlessBasicPoolFactory.sol
ends up having 195 token.Alice can drain token hold by PermissionlessBasicPoolFactory.sol
by doing this multiple times.
change to
function deposit(uint poolId, uint amount) external { Pool storage pool = pools[poolId]; require(pool.id == poolId, 'Uninitialized pool'); require(block.timestamp > pool.startTime, 'Cannot deposit before pool start'); require(block.timestamp < pool.endTime, 'Cannot deposit after pool ends'); require(pool.totalDepositsWei < pool.maximumDepositWei, 'Maximum deposit already reached'); if (pool.totalDepositsWei + amount > pool.maximumDepositWei) { amount = pool.maximumDepositWei - pool.totalDepositsWei; } pool.totalDepositsWei += amount; pool.numReceipts++; Receipt storage receipt = pool.receipts[pool.numReceipts]; receipt.id = pool.numReceipts; // receipt.amountDepositedWei = amount; receipt.timeDeposited = block.timestamp; receipt.owner = msg.sender; uint256 balanceBefore = IERC20(pool.depositToken).balanceOf(address(this)); bool success = IERC20(pool.depositToken).transferFrom(msg.sender, address(this), amount); recepit.amountDepositedWei = IERC20(pool.depositToken).balanceOf(address(this)) - balanceBefore; require(success, 'Token transfer failed'); emit DepositOccurred(poolId, pool.numReceipts, msg.sender); }
Since every receipt can only be withdrawn once, this is safe from reentrance call to withdraw
#0 - illuzen
2022-05-12T05:26:01Z
Duplicate #34
#1 - gititGoro
2022-06-14T02:08:39Z
Changing severity due pool isolation, rewards tokens represent leakage not lost funds and because transfer of deposited funds would fail on deposit.