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: 57/71
Findings: 3
Award: $61.80
🌟 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
The ERC20 functions transfer
and transferFrom
are assumed to return a boolean value, but this is not the case all the time. There are some tokens that their transfer functions don't return anything, and the system won't support them (it'll revert when trying to transfer them because the function that returns boolean doesn't exist)
I added a link to a specific usage, but you use transfer and transferFrom in many more places in the code.
Remix and VSCode
Use safeTransfer
and safeTransferFrom
function instead of the regular transfer
and transferFrom
functions
#0 - illuzen
2022-05-12T04:51:08Z
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
The system won't treat fee on transfer tokens correctly, and some actions can be executed wrongly.
For example, the rewards contract PermissionlessBasicPoolFactory
will calculate the deposited amounts in a wrong way, because it uses the amount
parameter and not the actual amount of tokens that the contract received.
There are more places where fee on transfer tokens can fail, like the vesting and resistor contracts and more.
Remix and VS Code
Calculate the deposit amount by the difference between the balance after and before the transfer instead of using the amount
parameter
#0 - illuzen
2022-05-12T04:50:14Z
duplicate #34
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xYamiDancho, 0xf15ers, 0xkatana, ACai, CertoraInc, Dravee, Funen, GimelSec, Hawkeye, PPrieditis, Picodes, Ruhum, TerrierLover, Tomio, VAD37, Waze, csanuragjain, defsec, delfin454000, eccentricexit, ellahi, fatherOfBlocks, gzeon, hansfriese, horsefacts, ilan, joestakey, juicy, minhquanym, oyc_109, rajatbeladiya, reassor, rfa, robee, samruna, simon135, z3s
39.4366 DAI - $39.44
Use prefix incrementation instead of postfix incrementation in line 190 in the PermissionlessBasicPoolFactory
contract (++pool.numReceipts
instead of pool.numReceipts++
). It'll save gas and will have the same effect as pool.numReceipts++
.
The for loop in the fundPool
function of the PermissionlessBasicPoolFactory
contract can be optimized in several ways:
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); // bookkeeping to make sure pools don't share tokens pool.rewardFunding[i] += amount; } require(success, 'Token deposits failed'); }
uint i;
instead of uint i = 0;
.After the changes, the function will look something like this:
function fundPool(uint poolId) internal { Pool storage pool = pools[poolId]; uint amount; uint len = pool.rewardFunding.length; for (uint i; i < len; ) { amount = getMaximumRewards(poolId, i); // transfer the tokens from pool-creator to this contract if (!IERC20(pool.rewardTokens[i]).transferFrom(msg.sender, address(this), amount)) { revert('Token deposits failed'); } // bookkeeping to make sure pools don't share tokens pool.rewardFunding[i] += amount; unchecked { ++i; } } }
There are multiple for loops in the code that can be optimized using the same optimizations, it'll cost less gas if you'll use these optimizations.
Save the pool id in the addPool
function of the PermissionlessBasicPoolFactory
contract to prevent multiple SLOADs.
The new implementation will look something like this:
function addPool ( uint startTime, uint maxDeposit, uint[] memory rewardsWeiPerSecondPerToken, uint programLengthDays, address depositTokenAddress, address excessBeneficiary, address[] memory rewardTokenAddresses, bytes32 ipfsHash, bytes32 name ) external { uint256 poolId = ++numPools; Pool storage pool = pools[poolId]; pool.id = poolId; pool.rewardsWeiPerSecondPerToken = rewardsWeiPerSecondPerToken; pool.startTime = startTime > block.timestamp ? startTime : block.timestamp; pool.endTime = pool.startTime + (programLengthDays * 1 days); pool.depositToken = depositTokenAddress; pool.excessBeneficiary = excessBeneficiary; pool.taxPerCapita = globalTaxPerCapita; require(rewardsWeiPerSecondPerToken.length == rewardTokenAddresses.length, 'Rewards and reward token arrays must be same length'); // fill out the arrays with zeros for (uint i = 0; i < rewardTokenAddresses.length; i++) { pool.rewardTokens.push(rewardTokenAddresses[i]); pool.rewardsWeiClaimed.push(0); pool.rewardFunding.push(0); taxes[poolId].push(0); } pool.maximumDepositWei = maxDeposit; // this must be after pool initialization above fundPool(poolId); { Metadata storage metadata = metadatas[poolId]; metadata.ipfsHash = ipfsHash; metadata.name = name; } emit PoolAdded(poolId, name, depositTokenAddress); }
#0 - illuzen
2022-05-12T08:46:15Z
all duplicates except last