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: 42/71
Findings: 2
Award: $117.06
π Selected for report: 0
π Solo Findings: 0
π Selected for report: horsefacts
Also found by: 0x1f8b, 0xYamiDancho, 0xf15ers, 0xkatana, ACai, AlleyCat, Bruhhh, Dravee, Funen, GimelSec, Hawkeye, IllIllI, MaratCerby, PPrieditis, Picodes, Ruhum, TerrierLover, VAD37, berndartmueller, csanuragjain, defsec, delfin454000, eccentricexit, ellahi, fatherOfBlocks, gzeon, hansfriese, hickuphh3, hyh, ilan, joestakey, juicy, kebabsec, oyc_109, rajatbeladiya, reassor, rfa, robee, samruna, simon135, sorrynotsorry, throttle
77.6194 DAI - $77.62
address(0)
when assigning values to address state variables.Setters of address type parameters should include a zero-address check otherwise contract functionality may become inaccessible or tokens burnt forever.
Check @audit
tags.
PermissionlessBasicPoolFactory.sol#L75-L78
constructor(address _globalBeneficiary, uint _globalTaxPerCapita) { globalBeneficiary = _globalBeneficiary; // @audit missing address(0) check. globalTaxPerCapita = _globalTaxPerCapita; }
constructor(address _mgmt) { management = _mgmt; // @audit missing address(0) check. treeAdder = _mgmt; } ... function setManagement(address newMgmt) external managementOnly { management = newMgmt; // @audit missing address(0) check. } ... function setTreeAdder(address newAdder) external managementOnly { treeAdder = newAdder; // @audit missing address(0) check. }
constructor(address ooner, address minter, string memory nomen, string memory symbowl) { _owner_ = ooner; // @audit missing address(0) check. // we set it here with no resetting allowed so we cannot commit to NFTs and then reset _minter = minter; // @audit missing address(0) check. _name = nomen; _symbol = symbowl; }
function setOwner(address newOwner) external ownerOnly { address oldOwner = _owner_; // @audit missing address(0) check. _owner_ = newOwner; emit OwnerUpdated(oldOwner, newOwner); }
Add address(0)
checks.
The @dev
comment for PermissionlessBasicPoolFactory.sol::withdraw()
states that only the receipt owner can call this function, but the assertion says otherwise.
... /// @dev Only receipt owner may call this function ... function withdraw(uint poolId, uint receiptId) external { ... require(receipt.owner == msg.sender || block.timestamp > pool.endTime, 'Can only withdraw your own deposit'); ...
If block.timestamp > pool.endTime
, then this function is callable by anyone and not just the owner.
Fix comments
The return value of an external transfer
/transferFrom
call is not checked.
MerkleVesting.sol::173 => IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal);
Use SafeERC20
, or ensure that the transfer
/transferFrom
return value is checked.
PermissionlessBasicPoolFactory.sol
uses pragma solidity 0.8.12
whereas every other contract uses pragma solidity 0.8.9
Use same compiler version.
uint
as uint256
.To favor explicitness, all instances of uint
should be declared as uint256
.
Manual
#0 - illuzen
2022-05-10T09:37:20Z
All duplicates
#1 - gititGoro
2022-06-12T02:24:56Z
For the last item, in solidity uint is shorthand for uint256
π 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
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack. Caching the array length in the stack saves around 3 gas per iteration.
MerkleLib.sol::22 => for (uint i = 0; i < proof.length; i += 1) { PermissionlessBasicPoolFactory.sol::115 => for (uint i = 0; i < rewardTokenAddresses.length; i++) { PermissionlessBasicPoolFactory.sol::141 => for (uint i = 0; i < pool.rewardFunding.length; i++) { PermissionlessBasicPoolFactory.sol::168 => for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { PermissionlessBasicPoolFactory.sol::224 => for (uint i = 0; i < rewards.length; i++) { PermissionlessBasicPoolFactory.sol::249 => for (uint i = 0; i < pool.rewardTokens.length; i++) { PermissionlessBasicPoolFactory.sol::266 => for (uint i = 0; i < pool.rewardTokens.length; i++) {
Store the arrayβs length in a variable before the for-loop.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Throughout the codebase.
Use custom errors instead of revert strings.
++i
costs less gas compared to i++
or i += 1
++i
costs less gas compared to i++
or i += 1
for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.
MerkleLib.sol::22 => for (uint i = 0; i < proof.length; i += 1) { PermissionlessBasicPoolFactory.sol::115 => for (uint i = 0; i < rewardTokenAddresses.length; i++) { PermissionlessBasicPoolFactory.sol::141 => for (uint i = 0; i < pool.rewardFunding.length; i++) { PermissionlessBasicPoolFactory.sol::168 => for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { PermissionlessBasicPoolFactory.sol::224 => for (uint i = 0; i < rewards.length; i++) { PermissionlessBasicPoolFactory.sol::249 => for (uint i = 0; i < pool.rewardTokens.length; i++) { PermissionlessBasicPoolFactory.sol::266 => for (uint i = 0; i < pool.rewardTokens.length; i++) {
Use ++i
instead of i++
to increment the value of an uint variable.
Same thing for --i
and i--
.
If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
MerkleLib.sol::22 => for (uint i = 0; i < proof.length; i += 1) { MerkleResistor.sol::176 => uint currentWithdrawal = 0; MerkleVesting.sol::150 => uint currentWithdrawal = 0; PermissionlessBasicPoolFactory.sol::115 => for (uint i = 0; i < rewardTokenAddresses.length; i++) { PermissionlessBasicPoolFactory.sol::141 => for (uint i = 0; i < pool.rewardFunding.length; i++) { PermissionlessBasicPoolFactory.sol::168 => for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { PermissionlessBasicPoolFactory.sol::224 => for (uint i = 0; i < rewards.length; i++) { PermissionlessBasicPoolFactory.sol::249 => for (uint i = 0; i < pool.rewardTokens.length; i++) { PermissionlessBasicPoolFactory.sol::266 => for (uint i = 0; i < pool.rewardTokens.length; i++) { VoterID.sol::69 => uint public numIdentities = 0;
Remove explicit zero initialization.
c4udit, manual, slither.
#0 - illuzen
2022-05-10T09:37:34Z
All duplicates