FactoryDAO contest - defsec's results

The DAO that builds DAOs.

General Information

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

FactoryDAO

Findings Distribution

Researcher Performance

Rank: 23/71

Findings: 3

Award: $306.21

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

3.1753 DAI - $3.18

Labels

bug
duplicate
2 (Med Risk)

External Links

Lines of code

https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L209 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L180

Vulnerability details

Impact

A malicious token, or one that implemented transfer hooks, could re-enter the public calling function (such as withdraw()) before proper internal accounting was completed. Because the earned reward function looks up the pool.totalDepositsWei and pool.rewardsWeiClaimed[, which is not yet updated when the transfer occurs, it would be possible for a malicious contract to re-enter _withdraw repeatedly and drain the pool. (Tokens with hooks (ERC777 and ERC677) would allow to exploit the contract and drain it in it's entirety.)

Proof of Concept

  1. Navigate to the following contract.

https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L209

https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L180

https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L261

https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L242

  1. withdraw, deposit and other functions are missing re-entrancy guard. Therefore, If pool is added with ERC777 token, the attacker can drain all pool.

Tools Used

Code Review

Consider using re-entrancy guard on all main action functions (e.g. deposit, withdraw and etc): https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol

#0 - illuzen

2022-05-12T05:55:51Z

ReentrancyGuard is not the only way to guard against re-entrance. Please read the code.

Duplicate #34

#1 - KenzoAgada

2022-06-17T15:48:46Z

Doesn't seem to be duplicate of #34 to me. 34 is about FoT tokens and checking balance before and after. This issue is a general reentrancy warning issue. There's no valid attack path outlined, and as the sponsor says, the code is protected via other means. Should probably be invalid or low severity.

#2 - gititGoro

2022-06-23T03:01:52Z

The link to 34 was made because they're both about state update issues in the presence of potential reentrancy or token state updates that disagree with the parameters. FOT is just one example of a non malicious token which is why I added that footnote comment. In both issues, there wasn't a solid attack path outlined but the sponsor was concerned with the general issue of handling unorthodox token transfers. This is why the issue remained confirmed and in fact a pull request was submitted addressing it.

ISSUE LIST

C4-001 : Missing events for only functions that change critical parameters - Non Critical

C4-002 : Critical changes should use two-step procedure - Non Critical

C4-003 : Pragma Version - Non Critical

C4-004 : Missing zero-address check in the setter functions and initiliazers - Low

C4-005 : Typo On The Variable - Non Critical

C4-006 : Use safeTransfer/safeTransferFrom consistently instead of transfer/transferFrom - Non critical

C4-007 : Deposit Token And Reward Token Should Not Be Same - LOW

ISSUES

C4-001 : Missing events for only functions that change critical parameters

Impact - Non critical

The afunctions that change critical parameters should emit events. Events allow capturing the changed parameters so that off-chain tools/interfaces can register such changes with timelocks that allow users to evaluate them and consider if they would like to engage/exit based on how they perceive the changes as affecting the trustworthiness of the protocol or profitability of the implemented financial services. The alternative of directly querying on-chain contract state for such changes is not considered practical for most users/usages.

Missing events and timelocks do not promote transparency and if such changes immediately affect users’ perception of fairness or trustworthiness, they could exit the protocol causing a reduction in liquidity which could negatively impact protocol TVL and reputation.

Proof of Concept

  1. Navigate to the following contract.
https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L314

See similar High-severity H03 finding OpenZeppelin’s Audit of Audius (https://blog.openzeppelin.com/audius-contracts-audit/#high) and Medium-severity M01 finding OpenZeppelin’s Audit of UMA Phase 4 (https://blog.openzeppelin.com/uma-audit-phase-4/)

Tools Used

None

Add events to all functions that change critical parameters.

C4-002 : Critical changes should use two-step procedure

Impact - NON CRITICAL

The critical procedures should be two step process.

Proof of Concept

  1. Navigate to the following contract.
https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/VoterID.sol#L151

Tools Used

Code Review

Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.

C4-003 : # Pragma Version

Impact

In the contracts, there are multiple version of pragmas are used. Some of them is used with 0.8.12 and some of them are 0.8.9. The contracts should be deployed with the consistent pragma.

## Proof of Concept

https://swcregistry.io/docs/SWC-103

All Contracts

Tools Used

Manual code review

Lock the pragma version: delete pragma solidity 0.8.10 in favor of pragma solidity 0.8.10.

C4-004 : # Missing zero-address check in the setter functions and initiliazers

Impact

Missing checks for zero-addresses may lead to infunctional protocol, if the variable addresses are updated incorrectly.

Proof of Concept

  1. Navigate to the following contracts.
https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L75 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/VoterID.sol#L108 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/MerkleIdentity.sol#L52 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/MerkleIdentity.sol#L60 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/MerkleIdentity.sol#L67 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/MerkleIdentity.sol#L75

Tools Used

Code Review

Consider adding zero-address checks in the discussed constructors: require(newAddr != address(0));.

C4-005 : # Typo On The Variables

Impact

During the code review, It has been observed that parameters are mistakenly named as "ooner","sywol" instead of "owner" and "symbol".

Proof of Concept

  1. Navigate to the following contracts.
https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/VoterID.sol#L109 https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/VoterID.sol#L183
_owner_ = ooner; //owner // we set it here with no resetting allowed so we cannot commit to NFTs and then reset _minter = minter; _name = nomen; //name _symbol = symbowl; //symbol

Tools Used

Code Review

Consider to fix typos.

C4-006 : Use safeTransfer/safeTransferFrom consistently instead of transfer/transferFrom

Impact - NON-CRITICAL

Impact

It is good to add a require() statement that checks the return value of token transfers or to use something like OpenZeppelin’s safeTransfer/safeTransferFrom unless one is sure the given token reverts in case of a failure. Failure to do so will cause silent failures of transfers and affect token accounting in contract.

Reference: This similar medium-severity finding from Consensys Diligence Audit of Fei Protocol: https://consensys.net/diligence/audits/2021/01/fei-protocol/#unchecked-return-value-for-iweth-transfer-call

Proof of Concept

  1. Navigate to the following contract.

  2. transfer/transferFrom functions are used instead of safe transfer/transferFrom on the following contracts.

contracts/MerkleDropFactory.sol::77 => require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); contracts/MerkleDropFactory.sol::107 => require(IERC20(tree.tokenAddress).transfer(destination, value), "ERC20 transfer failed"); contracts/MerkleResistor.sol::121 => require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); contracts/MerkleResistor.sol::204 => require(IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal), 'Token transfer failed'); contracts/MerkleVesting.sol::89 => require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); contracts/MerkleVesting.sol::173 => IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal); contracts/PermissionlessBasicPoolFactory.sol::144 => success = success && IERC20(pool.rewardTokens[i]).transferFrom(msg.sender, address(this), amount); contracts/PermissionlessBasicPoolFactory.sol::198 => bool success = IERC20(pool.depositToken).transferFrom(msg.sender, address(this), amount); contracts/PermissionlessBasicPoolFactory.sol::230 => success = success && IERC20(pool.rewardTokens[i]).transfer(receipt.owner, transferAmount); contracts/PermissionlessBasicPoolFactory.sol::233 => success = success && IERC20(pool.depositToken).transfer(receipt.owner, receipt.amountDepositedWei); contracts/PermissionlessBasicPoolFactory.sol::252 => success = success && IERC20(pool.rewardTokens[i]).transfer(pool.excessBeneficiary, rewards); contracts/PermissionlessBasicPoolFactory.sol::269 => success = success && IERC20(pool.rewardTokens[i]).transfer(globalBeneficiary, tax);

Tools Used

Code Review

Consider using safeTransfer/safeTransferFrom or require() consistently.

C4-007 : Deposit Token And Reward Token Should Not Be Same

Impact - LOW

Impact

During the code review, It has been noticed that to the contract is missing some sanity checks in the addPool function. When adding pool, deposit and reward token can be same and that can create unfair situation on the contracts.

Proof of Concept

  1. Navigate to the following contract.

  2. The contract allows to be same on the deposit and reward tokens.

https://github.com/code-423n4/2022-05-factorydao/blob/main/contracts/PermissionlessBasicPoolFactory.sol#L108

Tools Used

Code Review

Do not allow reward tokens are same as deposit Token.

#0 - illuzen

2022-05-12T09:12:58Z

all duplicates except last, which is invalid, we explicitly support simple staking (depositToken == rewardToken)

#1 - gititGoro

2022-06-12T03:32:46Z

Very nicely documented. Bonus points awarded.

ISSUE LIST

C4-001: Revert String Size Optimization

C4-002 : Adding unchecked directive can save gas

C4-003 : Check if amount > 0 before token transfer can save gas

C4-004 : There is no need to assign default values to variables

C4-005: > 0 can be replaced with != 0 for gas optimization

C4-006 : Free gas savings for using solidity 0.8.10+

C4-007 : ++i is more gas efficient than i++ in loops forwarding

C4-008 : Using operator && used more gas

C4-009 : Non-strict inequalities are cheaper than strict ones

C4-010 : Use Custom Errors instead of Revert Strings to save Gas

C4-001: Revert String Size Optimization

Impact

Shortening revert strings to fit in 32 bytes will decrease deploy time gas and will decrease runtime gas when the revert condition has been met.

Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.

Proof of Concept

Revert strings > 32 bytes are here:

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/MerkleIdentity.sol::127 => require(verifyMetadata(tree.metadataMerkleRoot, tokenId, uri, metadataProof), "The metadata proof could not be verified"); contracts/MerkleResistor.sol::171 => require(initialized[destination][treeIndex], "You must initialize your account first."); contracts/MerkleVesting.sol::141 => require(initialized[destination][treeIndex], "You must initialize your account first."); contracts/VoterID.sol::5 => import "../interfaces/IERC721Receiver.sol"; contracts/VoterID.sol::37 => // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` contracts/VoterID.sol::210 => /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. contracts/VoterID.sol::217 => require(checkOnERC721Received(from, to, tokenId, data), "Identity: transfer to non ERC721Receiver implementer"); 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");

Tools Used

Manual Review

Shorten the revert strings to fit in 32 bytes. That will affect gas optimization.

C4-002 : Adding unchecked directive can save gas

Impact

For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.

Proof of Concept

contracts/MerkleLib.sol::22 => for (uint i = 0; i < proof.length; i += 1) { contracts/MerkleResistor.sol::40 => uint minEndTime; // minimum length (offset, not absolute) of vesting schedule in seconds contracts/MerkleResistor.sol::41 => uint maxEndTime; // maximum length (offset, not absolute) of vesting schedule in seconds contracts/MerkleResistor.sol::130 => /// @param vestingTime the actual length of the vesting schedule, chosen by the user contracts/MerkleResistor.sol::211 => /// @param vestingTime user chosen length of vesting schedule contracts/MerkleResistor.sol::243 => // x axis = length of vesting schedule contracts/PermissionlessBasicPoolFactory.sol::112 => require(rewardsWeiPerSecondPerToken.length == rewardTokenAddresses.length, 'Rewards and reward token arrays must be same length'); contracts/PermissionlessBasicPoolFactory.sol::115 => for (uint i = 0; i < rewardTokenAddresses.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::141 => for (uint i = 0; i < pool.rewardFunding.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::167 => uint[] memory rewardsLocal = new uint[](pool.rewardsWeiPerSecondPerToken.length); contracts/PermissionlessBasicPoolFactory.sol::168 => for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::224 => for (uint i = 0; i < rewards.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::249 => for (uint i = 0; i < pool.rewardTokens.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::266 => for (uint i = 0; i < pool.rewardTokens.length; i++) {

Tools Used

None

Consider applying unchecked arithmetic where overflow/underflow is not possible. Example can be seen from below.

Unchecked{i++};

C4-003 : Check if amount > 0 before token transfer can save gas

Impact

Since _amount can be 0. Checking if (_amount != 0) before the transfer can potentially save an external call and the unnecessary gas cost of a 0 token transfer.

Proof of Concept

contracts/MerkleDropFactory.sol::77 => require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); contracts/MerkleDropFactory.sol::107 => require(IERC20(tree.tokenAddress).transfer(destination, value), "ERC20 transfer failed"); contracts/MerkleResistor.sol::121 => require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); contracts/MerkleResistor.sol::204 => require(IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal), 'Token transfer failed'); contracts/MerkleVesting.sol::89 => require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed"); contracts/MerkleVesting.sol::173 => IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal); contracts/PermissionlessBasicPoolFactory.sol::144 => success = success && IERC20(pool.rewardTokens[i]).transferFrom(msg.sender, address(this), amount); contracts/PermissionlessBasicPoolFactory.sol::198 => bool success = IERC20(pool.depositToken).transferFrom(msg.sender, address(this), amount); contracts/PermissionlessBasicPoolFactory.sol::230 => success = success && IERC20(pool.rewardTokens[i]).transfer(receipt.owner, transferAmount); contracts/PermissionlessBasicPoolFactory.sol::233 => success = success && IERC20(pool.depositToken).transfer(receipt.owner, receipt.amountDepositedWei); contracts/PermissionlessBasicPoolFactory.sol::252 => success = success && IERC20(pool.rewardTokens[i]).transfer(pool.excessBeneficiary, rewards); contracts/PermissionlessBasicPoolFactory.sol::269 => success = success && IERC20(pool.rewardTokens[i]).transfer(globalBeneficiary, tax);

All Contracts

Tools Used

None

Consider checking amount != 0.

C4-004 : There is no need to assign default values to variables

Impact - Gas Optimization

When a variable is declared solidity assigns the default value. In case the contract assigns the value again, it costs extra gas.

Example: uint x = 0 costs more gas than uint x without having any different functionality.

Proof of Concept

contracts/MerkleLib.sol::22 => for (uint i = 0; i < proof.length; i += 1) { contracts/MerkleResistor.sol::40 => uint minEndTime; // minimum length (offset, not absolute) of vesting schedule in seconds contracts/MerkleResistor.sol::41 => uint maxEndTime; // maximum length (offset, not absolute) of vesting schedule in seconds contracts/MerkleResistor.sol::130 => /// @param vestingTime the actual length of the vesting schedule, chosen by the user contracts/MerkleResistor.sol::211 => /// @param vestingTime user chosen length of vesting schedule contracts/MerkleResistor.sol::243 => // x axis = length of vesting schedule contracts/PermissionlessBasicPoolFactory.sol::112 => require(rewardsWeiPerSecondPerToken.length == rewardTokenAddresses.length, 'Rewards and reward token arrays must be same length'); contracts/PermissionlessBasicPoolFactory.sol::115 => for (uint i = 0; i < rewardTokenAddresses.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::141 => for (uint i = 0; i < pool.rewardFunding.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::167 => uint[] memory rewardsLocal = new uint[](pool.rewardsWeiPerSecondPerToken.length); contracts/PermissionlessBasicPoolFactory.sol::168 => for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::224 => for (uint i = 0; i < rewards.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::249 => for (uint i = 0; i < pool.rewardTokens.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::266 => for (uint i = 0; i < pool.rewardTokens.length; i++) {

Tools Used

Code Review

uint x = 0 costs more gas than uint x without having any different functionality.

C4-005: > 0 can be replaced with != 0 for gas optimization

Impact

!= 0 is a cheaper operation compared to > 0, when dealing with uint. (Before Pragma 0.8.13)

Proof of Concept

  1. Navigate to the following contracts.
contracts/FixedPricePassThruGate.sol::51 => if (msg.value > 0) { contracts/MerkleResistor.sol::156 => startTime, // start time will usually be in the past, if pctUpFront > 0 contracts/SpeedBumpPriceGate.sol::77 => if (msg.value > 0) { contracts/VoterID.sol::52 => * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ contracts/VoterID.sol::208 => /// checks if `to` is a smart contract (code size > 0). If so, it calls contracts/VoterID.sol::348 => return size > 0;

Tools Used

Code Review

Use "!=0" instead of ">0" for the gas optimization.

C4-006 : Free gas savings for using solidity 0.8.10+

Impact

Using newer compiler versions and the optimizer gives gas optimizations and additional safety checks are available for free.

Proof of Concept

All Contracts

Solidity 0.8.10 has a useful change which reduced gas costs of external calls which expect a return value: https://blog.soliditylang.org/2021/11/09/solidity-0.8.10-release-announcement/

Solidity 0.8.13 has some improvements too but not well tested.

Code Generator: Skip existence check for external contract if return data is expected. In this case, the ABI decoder will revert if the contract does not exist

All Contracts

Tools Used

None

Consider to upgrade pragma to at least 0.8.10.

C4-007 : ++i is more gas efficient than i++ in loops forwarding

Impact

++i is more gas efficient than i++ in loops forwarding.

Proof of Concept

  1. Navigate to the following contracts.
contracts/PermissionlessBasicPoolFactory.sol::115 => for (uint i = 0; i < rewardTokenAddresses.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::141 => for (uint i = 0; i < pool.rewardFunding.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::168 => for (uint i = 0; i < pool.rewardsWeiPerSecondPerToken.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::224 => for (uint i = 0; i < rewards.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::249 => for (uint i = 0; i < pool.rewardTokens.length; i++) { contracts/PermissionlessBasicPoolFactory.sol::266 => for (uint i = 0; i < pool.rewardTokens.length; i++) {

Tools Used

Code Review

It is recommend to use unchecked{++i} and change i declaration to uint256.

C4-008 : Using operator && used more gas

Impact

Using double require instead of operator && can save more gas.

Proof of Concept

  1. Navigate to the following contracts.
contracts/PermissionlessBasicPoolFactory.sol::144 => success = success && IERC20(pool.rewardTokens[i]).transferFrom(msg.sender, address(this), amount); contracts/PermissionlessBasicPoolFactory.sol::198 => bool success = IERC20(pool.depositToken).transferFrom(msg.sender, address(this), amount); contracts/PermissionlessBasicPoolFactory.sol::230 => success = success && IERC20(pool.rewardTokens[i]).transfer(receipt.owner, transferAmount); contracts/PermissionlessBasicPoolFactory.sol::233 => success = success && IERC20(pool.depositToken).transfer(receipt.owner, receipt.amountDepositedWei); contracts/PermissionlessBasicPoolFactory.sol::252 => success = success && IERC20(pool.rewardTokens[i]).transfer(pool.excessBeneficiary, rewards); contracts/PermissionlessBasicPoolFactory.sol::269 => success = success && IERC20(pool.rewardTokens[i]).transfer(globalBeneficiary, tax);

Tools Used

Code Review

Example

using &&: function check(uint x)public view{ require(x == 0 && x < 1 ); } // gas cost 21630 using double require: require(x == 0 ); require( x < 1); } } // gas cost 21622

C4-009 : Non-strict inequalities are cheaper than strict ones

Impact

Strict inequalities add a check of non equality which costs around 3 gas.

Proof of Concept

contracts/FixedPricePassThruGate.sol::51 => if (msg.value > 0) { contracts/MerkleResistor.sol::156 => startTime, // start time will usually be in the past, if pctUpFront > 0 contracts/SpeedBumpPriceGate.sol::77 => if (msg.value > 0) { contracts/VoterID.sol::52 => * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ contracts/VoterID.sol::208 => /// checks if `to` is a smart contract (code size > 0). If so, it calls contracts/VoterID.sol::348 => return size > 0;

Tools Used

Code Review

Use >= or <= instead of > and < when possible.

C4-010 : Use Custom Errors instead of Revert Strings to save Gas

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)

Source Custom Errors in Solidity:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).

Instances include:

All require Statements

Tools Used

Code Review

Recommended to replace revert strings with custom errors.

#0 - illuzen

2022-05-12T09:09:20Z

all duplicates except c4-006

#1 - gititGoro

2022-06-05T00:48:35Z

C4-008 is not correctly reported: there's no need for a double require. But the principle of avoiding && will be taken into account.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter