These are basic checks to undertake with any contract. Checklist now includes features included in 0.8.13
. Consider hiring a professional organization for a detailed audit. Forked from Cryptofin's Audit list with modifications. Credits to SWC. Written by abhinavmir.
- Function Visibility
- Ensure that all relevant functions are marked with the correct visibility
- Fix compiler warnings
- Fix linter warnings (Example set of rules can be found here)
- Avoid using problematic features - If you must, be aware of their many nuances
- Explicitly state visibilities of state variables
- Remove unencrypted private on-chain data
- External Calls - Every external contract call is a risk
- Avoid using data parameters on sub-calls with no assert checks (ref.)
delegatecall
can, for example, overwrite parent smart contract via malicious calls. Implement strong assert checks, remember even then,delegatecall
can overridemsg.value
andmsg.sender
checks.
- Check for Re-entrancy - and ensure state committed before external call
- Callstack depth can cause this as well
- Avoid using data parameters on sub-calls with no assert checks (ref.)
- Avoid
SELFDESTRUCT
- If it must be used, apply strong protection via assert checks
- Dependencies
- Use audited and trustworthy dependencies
- Ensure code minimization by using trusted libraries
- Time Manipulation - Timestamps can theoretically be manipulated by malicious miners by up to a few minutes
- Ensure important mechanisms aren't overly sensitive to timestamps
- Rounding Errors
- Check that truncation doesn't produce unexpected behavior (eg. incorrect results, locked funds)
- Randomness
- Don't rely on pseudo-randomness for important mechanisms (eg. keccak with a deterministic seed like blockhash, blocknumber, etc.)
- On-chain randomness can be predicted and misused - consider using a service such as Chainlink VRF.
- Validate inputs of external/public functions
- Ensure requires to bound and check presence of arguments
- Any functions to interact with token must implement a form of assert check
- Prevent unbounded loops
- Implement circuit breakers in case of emergency
- Avoid failed call DoS
- Don't use
tx.origin
as an authentication mechanism, always usemsg.sender
instead (ref. - Verify changes in the most recent Solidity version - (ref).
- Prevent overflow and underflow
- Change message call with hardcoded gas amount - ref
- Do not assume specific Ether balances - ref
- Implement inheritence from general to specific (ref)
- Remove transaction order dependence - will result in eventual front-running ref. Murphy's law
- Implement external checks to message calls - As such:
require(user.call())
- Use fixed pragma statements (❌ Eg. Avoid
pragma solidity >=0.4.0 <0.6.0;
) - Implement strong signature verification - ideally without alterations to accepted methods
- Verify cryptographic signatures to avoid proxy signatures
- Avoid signature malleability
- Reduce time dependence, if needed: Use oracles over
block
keyword - Malicious actors can alter block details
- Test Coverage
- Have 100% branch test coverage
- Unit Tests
- Cover all critical edge cases with unit tests
- Integrate fuzzing (tool examples: Echidna, Foundry etc)
- Integration Tests
- Have extensive integration tests
- Code Freeze
- Try not to deploy code written under manegerial pressure
- Remove irrelevant and redundant code
Auditing helps catch many bugs, but shouldn’t also be seen as a magic bullet. Your system still needs to handle failure gracefully.
- Audits
- Have code audited by (preferably) multiple external parties (in series)
- Time Management
- Allocate comfortable time after the audit to address issues
- Consider open source auditing such as Code4rena and ImmuneFi