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: 17/71
Findings: 3
Award: $451.07
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: AuditsAreUS
For the beneficiary to withdraw excess rewards of the pool, pool.totalDepositsWei
should be equal to zero. An attacker can always deposit minimum amount to restrict withdraw of rewards to the beneficiary.
require(pool.totalDepositsWei == 0, 'Cannot withdraw until all deposits are withdrawn');
Manual Analayis
#0 - illuzen
2022-05-10T09:40:23Z
duplicate
#1 - gititGoro
2022-06-15T00:07:42Z
Duplicate of #54
🌟 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
85.6 DAI - $85.60
tokenBalance
before depositTokens()
-In MerkleDropFactory.sol#L59, tokenBalance
can be check with require(tokenBalance) > 0
to stop zero depoist.
# Before IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal); # After require(IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal), "fail message");
#0 - illuzen
2022-05-10T09:43:06Z
#1 - gititGoro
2022-06-05T10:20:35Z
Note on 2. Not all ERC20 tokens return a boolean. It's safer to perform a low level call and assert that it passed. And even safer to use a battle tested library to do this for you.
🌟 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
44.8039 DAI - $44.80
rewards.length
is calculed in each iteration. Gas can be saved if the length is cached.The following is recommended.
uint length = rewards.length for (uint i = 0; i < length; i++) { .... }
./contracts//PermissionlessBasicPoolFactory.sol:125: for (uint i = 0; i < rewardTokenAddresses.length; i++) { ./contracts//PermissionlessBasicPoolFactory.sol:151: for (uint i = 0; i < pool.rewardFunding.length; i++) { ./contracts//PermissionlessBasicPoolFactory.sol:180: for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { ./contracts//PermissionlessBasicPoolFactory.sol:237: for (uint i = 0; i < rewards.length; i++) { ./contracts//PermissionlessBasicPoolFactory.sol:269: for (uint i = 0; i < pool.rewardTokens.length; i++) { ./contracts//PermissionlessBasicPoolFactory.sol:286: for (uint i = 0; i < pool.rewardTokens.length; i++) { ./contracts//MerkleLib.sol:22: for (uint i = 0; i < proof.length; i += 1) {
balances[thisOwner]
can also be cached to save twice sload operation.# Before ownershipMapIndexToToken[thisOwner][balances[thisOwner]] = thisToken; ownershipMapTokenToIndex[thisOwner][thisToken] = balances[thisOwner]; # After uint tokenNumber = balances[thisOwner] ownershipMapIndexToToken[thisOwner][tokenNumber] = thisToken; ownershipMapTokenToIndex[thisOwner][thisToken] = tokenNumber;
numIdentities = numIdentities + 1
++numIdentities
costs less gas than numIdentities = numIdentities + 1
0
can be omitted to save gas.uint public numTrees = 0
is by default initialized to 0balances[thisOwner]
can save later operationsownershipMapTokenToIndex[thisOwner][thisToken] = balances[thisOwner]; // set owner of new token owners[thisToken] = thisOwner; // increment balances for owner balances[thisOwner] = balances[thisOwner] + 1;
can be simmpliifed to
ownershipMapTokenToIndex[thisOwner][thisToken] = balances[thisOwner]++; // set owner of new token owners[thisToken] = thisOwner;
/contracts/MerkleEligibility.sol:86: require(msg.sender == gateMaster, "Only gatemaster may call this."); ./contracts/MerkleEligibility.sol:89: require(isEligible(index, recipient, proof), "Address is not eligible"); ./contracts/MerkleIdentity.sol:97: require(msg.sender == treeAdder, 'Only treeAdder can add trees'); ./contracts/MerkleIdentity.sol:124: require(merkleIndex <= numTrees, 'merkleIndex out of range'); ./contracts/MerkleIdentity.sol:127: require(verifyMetadata(tree.metadataMerkleRoot, tokenId, uri, metadataProof), "The metadata proof could not be verified"); ./contracts/MerkleDropFactory.sol:77: require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); ./contracts/MerkleDropFactory.sol:90: require(treeIndex <= numTrees, "Provided merkle index doesn't exist"); ./contracts/MerkleDropFactory.sol:92: require(!withdrawn[destination][treeIndex], "You have already withdrawn your entitled token."); ./contracts/MerkleDropFactory.sol:98: require(tree.merkleRoot.verifyProof(leaf, proof), "The proof could not be verified."); ./contracts/MerkleDropFactory.sol:107: require(IERC20(tree.tokenAddress).transfer(destination, value), "ERC20 transfer failed"); ./contracts/MerkleVesting.sol:89: require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); ./contracts/MerkleVesting.sol:106: require(!initialized[destination][treeIndex], "Already initialized"); ./contracts/MerkleVesting.sol:113: require(tree.rootHash.verifyProof(leaf, proof), "The proof could not be verified."); ./contracts/MerkleVesting.sol:141: require(initialized[destination][treeIndex], "You must initialize your account first."); ./contracts/MerkleVesting.sol:145: require(block.timestamp > tranche.lockPeriodEndTime, 'Must wait until after lock period'); ./contracts/MerkleVesting.sol:147: require(tranche.currentCoins > 0, 'No coins left to withdraw'); ./contracts/VoterID.sol:123: require(msg.sender == _minter, 'Only minter may create identity'); ./contracts/VoterID.sol:124: require(owners[thisToken] == address(0), 'Token already exists'); ./contracts/VoterID.sol:184: require(ooner != address(0), 'No such token'); ./contracts/VoterID.sol:199: require(isApproved(msg.sender, tokenId), 'Identity: Unapproved transfer'); ./contracts/VoterID.sol:217: require(checkOnERC721Received(from, to, tokenId, data), "Identity: transfer to non ERC721Receiver implementer"); ./contracts/VoterID.sol:239: require(isApproved(msg.sender, tokenId), 'Identity: Not authorized to approve'); ./contracts/VoterID.sol:240: require(holder != approved, 'Identity: Approving self not allowed'); ./contracts/VoterID.sol:262: require(holder != address(0), 'Identity: Invalid tokenId'); ./contracts/VoterID.sol:305: require(owners[tokenId] == from, "Identity: Transfer of token that is not own"); ./contracts/VoterID.sol:306: require(to != address(0), "Identity: transfer to the zero address"); ./contracts/VoterID.sol:437: require(_index < numIdentities, 'Invalid token index'); ./contracts/VoterID.sol:449: require(_index < balances[_address], 'Index out of range'); ./contracts/VoterID.sol:450: require(_address != address(0), 'Cannot query zero address'); ./contracts/SpeedBumpPriceGate.sol:67: require(msg.value >= price, 'Please send more ETH'); ./contracts/SpeedBumpPriceGate.sol:80: require(sent, 'ETH transfer failed'); ./contracts/MerkleResistor.sol:82: require(pctUpFront < 100, 'pctUpFront >= 100'); ./contracts/MerkleResistor.sol:83: require(minEndTime < maxEndTime, 'minEndTime must be less than maxEndTime'); ./contracts/MerkleResistor.sol:121: require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); ./contracts/MerkleResistor.sol:136: require(msg.sender == destination, 'Can only initialize your own tranche'); ./contracts/MerkleResistor.sol:138: require(!initialized[destination][treeIndex], "Already initialized"); ./contracts/MerkleResistor.sol:144: require(tree.merkleRoot.verifyProof(leaf, proof), "The proof could not be verified."); ./contracts/MerkleResistor.sol:149: require(valid, 'Invalid vesting schedule'); ./contracts/MerkleResistor.sol:171: require(initialized[destination][treeIndex], "You must initialize your account first."); ./contracts/MerkleResistor.sol:175: require(tranche.currentCoins > 0, 'No coins left to withdraw'); ./contracts/MerkleResistor.sol:204: require(IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal), 'Token transfer failed'); ./contracts/FixedPricePassThruGate.sol:48: require(msg.value >= gate.ethCost, 'Please send more ETH'); ./contracts/FixedPricePassThruGate.sol:54: require(sent, 'ETH transfer failed'); ./contracts/PermissionlessBasicPoolFactory.sol:112: require(rewardsWeiPerSecondPerToken.length == rewardTokenAddresses.length, 'Rewards and reward token arrays must be same length'); ./contracts/PermissionlessBasicPoolFactory.sol:148: require(success, 'Token deposits failed'); ./contracts/PermissionlessBasicPoolFactory.sol:159: require(pool.id == poolId, 'Uninitialized pool'); ./contracts/PermissionlessBasicPoolFactory.sol:160: require(receipt.id == receiptId, 'Uninitialized receipt'); ./contracts/PermissionlessBasicPoolFactory.sol:182: require(pool.id == poolId, 'Uninitialized pool'); ./contracts/PermissionlessBasicPoolFactory.sol:183: require(block.timestamp > pool.startTime, 'Cannot deposit before pool start'); ./contracts/PermissionlessBasicPoolFactory.sol:184: require(block.timestamp < pool.endTime, 'Cannot deposit after pool ends'); ./contracts/PermissionlessBasicPoolFactory.sol:185: require(pool.totalDepositsWei < pool.maximumDepositWei, 'Maximum deposit already reached'); ./contracts/PermissionlessBasicPoolFactory.sol:199: require(success, 'Token transfer failed'); ./contracts/PermissionlessBasicPoolFactory.sol:211: require(pool.id == poolId, 'Uninitialized pool'); ./contracts/PermissionlessBasicPoolFactory.sol:213: require(receipt.id == receiptId, 'Can only withdraw real receipts'); ./contracts/PermissionlessBasicPoolFactory.sol:214: require(receipt.owner == msg.sender || block.timestamp > pool.endTime, 'Can only withdraw your own deposit'); ./contracts/PermissionlessBasicPoolFactory.sol:215: require(receipt.timeWithdrawn == 0, 'Can only withdraw once per receipt'); ./contracts/PermissionlessBasicPoolFactory.sol:234: require(success, 'Token transfer failed'); ./contracts/PermissionlessBasicPoolFactory.sol:244: require(pool.id == poolId, 'Uninitialized pool'); ./contracts/PermissionlessBasicPoolFactory.sol:245: require(pool.totalDepositsWei == 0, 'Cannot withdraw until all deposits are withdrawn'); ./contracts/PermissionlessBasicPoolFactory.sol:246: require(block.timestamp > pool.endTime, 'Contract must reach maturity'); ./contracts/PermissionlessBasicPoolFactory.sol:254: require(success, 'Token transfer failed'); ./contracts/PermissionlessBasicPoolFactory.sol:263: require(pool.id == poolId, 'Uninitialized pool'); ./contracts/PermissionlessBasicPoolFactory.sol:271: require(success, 'Token transfer failed'); ./contracts/PermissionlessBasicPoolFactory.sol:315: require(msg.sender == globalBeneficiary, 'Only globalBeneficiary can set tax'); ./contracts/PermissionlessBasicPoolFactory.sol:316: require(newTaxPerCapita < 1000, 'Tax too high');
external
is cheaper than public
external
to save gas#MerkleDropFactory.sol function addMerkleTree(bytes32 newRoot, bytes32 ipfsHash, address tokenAddress, uint tokenBalance) public { #MerkleDropFactory.sol function withdraw(uint treeIndex, address destination, uint value, bytes32[] memory proof) public { #MerkleVesting.sol addMerkleRoot(bytes32 newRoot, bytes32 ipfsHash, address tokenAddress, uint tokenBalance) public { #MerkleVesting.sol function withdraw(uint treeIndex, address destination) public { #MerkleResistor.sol function addMerkleTree(bytes32 newRoot, bytes32 ipfsHash, uint minEndTime, uint maxEndTime, uint pctUpFront, address tokenAddress, uint tokenBalance) public { ..........
#0 - illuzen
2022-05-10T09:41:26Z
All duplicates, except for 5