Use this checklist before deploying any DeFi smart contract to mainnet
Last Updated: January 2024
Based on: 1,384 Solidity files analyzed, 180 exploit attempts studied
- All state-changing functions have
nonReentrantmodifier - Critical view functions (price calculations, share values) have
nonReentrantViewmodifier - Checks-Effects-Interactions pattern followed in all functions
- Input validation first (Checks)
- State updates second (Effects)
- External calls last (Interactions)
- Reentrancy attack tests pass (minimum 100% coverage)
- Read-only reentrancy tests pass
Test Command:
forge test --match-test testReentrancyExample Test:
function testReentrancyProtection() public {
ReentrancyAttacker attacker = new ReentrancyAttacker(address(vault));
vm.expectRevert(ReentrantCall.selector);
attacker.attack();
}- NEVER using spot prices for critical operations
- TWAP oracle implemented (minimum 30-minute period)
- Chainlink price feed configured with validation:
- Staleness check (< 1 hour)
- Round completion check
- Positive price check
- Deviation limits (< 10%)
- Multi-oracle consensus if TVL > $10M
- At least 2 independent price sources
- Deviation threshold ≤ 5%
- Circuit breaker on disagreement
- Oracle manipulation tests pass
- Flash loan price manipulation tests pass
Test Command:
forge test --match-test testOracle- Role-based access control implemented
- ADMIN_ROLE assigned to multi-signature wallet (not EOA)
- Minimum 3/5 multi-sig
- All signers documented
- PAUSER_ROLE assigned to trusted addresses
- OPERATOR_ROLE (if used) has limited permissions
- All admin functions restricted with
onlyRolemodifier - Role grant/revoke emits events
- Access control tests pass (unauthorized access blocked)
Test Command:
forge test --match-test testAccessControl- Same-block action detection on deposit/withdraw functions
- Flash loan tests pass
- Price manipulation during flash loans prevented
Test Command:
forge test --match-test testFlashLoan- Slippage protection on all swaps/trades
- User can specify
minAmountOut - Maximum slippage capped (≤ 5%)
- User can specify
- Deadline enforcement on time-sensitive operations
-
deadlineparameter required - Validation:
block.timestamp <= deadline
-
- Price impact limits to prevent sandwich attacks
- Single transaction impact ≤ 1%
- Block cumulative impact ≤ 3%
- MEV protection tests pass
Test Command:
forge test --match-test testMEV
forge test --match-test testSlippage- Pause functionality implemented
- Pause tests verify:
- Only PAUSER_ROLE can pause
- Critical functions blocked when paused
- Unpause requires same role
- Emergency procedures documented
- Recovery mechanisms tested
- All state changes emit events
- Events include relevant parameters
- Indexed parameters for filtering
- Events tested in test suite
- NatSpec comments on all public/external functions
- Security assumptions documented
- Known limitations documented
- Upgrade procedures documented (if upgradeable)
- No TODO comments in production code
- No hardcoded addresses (use constructor/initialization)
- Unit tests for all functions
- Integration tests for user flows
- Fuzz testing (≥ 256 runs)
forge test --fuzz-runs 256 - Invariant testing with handlers
- Code coverage ≥ 95%
forge coverage
- Gas optimization tests (if relevant)
- Slither analysis passed
slither . --filter-paths "test|mock"
- Mythril symbolic execution (no critical issues)
myth analyze src/MyContract.sol
- Solhint linting passed
solhint 'src/**/*.sol' - Aderyn static analysis (if available)
Verification Steps:
-
Find all functions making external calls
grep -r "\.call\|\.transfer\|\.send" src/ -
Verify each has
nonReentrantmodifier -
Find all view functions reading critical state
grep -r "function.*view.*returns" src/ -
Verify price/share calculations have
nonReentrantView
Pass Criteria: ✅ All external-call functions protected, all critical views protected
Verification Steps:
-
Find all price read operations
grep -r "getPrice\|price()\|latestRoundData" src/ -
Verify using TWAP (not spot price)
-
Check Chainlink validation:
require(answeredInRound >= roundId, "Stale"); require(block.timestamp - updatedAt < 3600, "Stale"); require(answer > 0, "Invalid");
-
If TVL > $10M, verify multi-oracle consensus
Pass Criteria: ✅ No spot prices, TWAP enabled, Chainlink validated, consensus if needed
Verification Steps:
-
Find all admin functions
grep -r "onlyOwner\|onlyAdmin\|onlyRole" src/ -
Verify multi-sig for critical roles
-
Test unauthorized access blocked
vm.prank(attacker); vm.expectRevert(Unauthorized.selector); vault.pause();
Pass Criteria: ✅ All admin functions restricted, multi-sig configured, tests pass
Verification Steps:
-
Review all state-changing functions
-
Verify order:
function withdraw(uint256 amount) external { // 1. CHECKS require(amount > 0); require(balances[msg.sender] >= amount); // 2. EFFECTS balances[msg.sender] -= amount; totalDeposits -= amount; emit Withdrawal(msg.sender, amount); // 3. INTERACTIONS (bool success, ) = msg.sender.call{value: amount}(""); require(success); }
Pass Criteria: ✅ All functions follow CEI pattern
Use this to determine which protections are critical for your protocol:
| Protocol Type | Reentrancy | Oracle | MEV | Flash Loan | Access Control |
|---|---|---|---|---|---|
| Vault/Staking | 🔴 Critical | 🟡 High | 🟢 Medium | 🔴 Critical | 🔴 Critical |
| Lending Protocol | 🔴 Critical | 🔴 Critical | 🟡 High | 🔴 Critical | 🔴 Critical |
| DEX/AMM | 🔴 Critical | 🔴 Critical | 🔴 Critical | 🔴 Critical | 🟡 High |
| Derivatives | 🔴 Critical | 🔴 Critical | 🔴 Critical | 🔴 Critical | 🔴 Critical |
| Bridge | 🔴 Critical | 🔴 Critical | 🟡 High | 🟡 High | 🔴 Critical |
Legend:
- 🔴 Critical: Must implement, deployment blocked without
- 🟡 High: Strongly recommended, document if not implemented
- 🟢 Medium: Recommended, case-by-case basis
# Must pass all of these
forge test --match-test testReentrancy
forge test --match-test testAccessControl
forge test --match-test testFlashLoan
forge test --match-test testOracle
forge test --match-test testMEV
forge test --match-test testSlippage# Minimum 256 runs
forge test --fuzz-runs 256
# CI: 10,000 runs
FOUNDRY_PROFILE=ci forge test# Generate coverage report
forge coverage
# Must achieve:
# - Overall coverage: ≥ 95%
# - Function coverage: 100%
# - Branch coverage: ≥ 90%- All checklist items above completed
- Professional audit completed (for mainnet with funds)
- Audit findings addressed or documented
- Testnet deployment successful
- Multi-sig wallets configured
- Oracle feeds tested on target network
-
Deploy to testnet first
forge script script/Deploy.s.sol --rpc-url $TESTNET_RPC --broadcast -
Verify all protections work
- Test deposits/withdrawals
- Test emergency pause
- Test oracle price feeds
- Test access control
-
Run final security checks
slither . --filter-paths "test" forge test --fuzz-runs 10000
-
Deploy to mainnet
forge script script/Deploy.s.sol --rpc-url $MAINNET_RPC --broadcast --verify -
Verify contract on Etherscan
forge verify-contract $CONTRACT_ADDRESS MyContract --chain-id 1 -
Configure roles
// Grant roles to multi-sig grantRole(ADMIN_ROLE, MULTISIG_ADDRESS); grantRole(PAUSER_ROLE, EMERGENCY_MULTISIG); // Revoke deployer roles revokeRole(ADMIN_ROLE, DEPLOYER_ADDRESS);
-
Initialize oracles
- Configure Chainlink feeds
- Set TWAP pools
- Test price queries
-
Final verification
# Verify contract state cast call $CONTRACT_ADDRESS "paused()(bool)" cast call $CONTRACT_ADDRESS "hasRole(bytes32,address)(bool)" $ADMIN_ROLE $MULTISIG
- Monitor for unusual activity (first 24 hours critical)
- Verify oracle prices updating
- Test user deposits/withdrawals
- Document all contract addresses
- Share audit report (if public protocol)
- Set up monitoring/alerts
- Prepare incident response plan
- ❌ Using spot prices for critical operations
- ❌ No reentrancy protection on external calls
- ❌ View functions reading state without protection
- ❌ Admin functions controlled by single EOA
- ❌ No test coverage for security patterns
- ❌ Failing any security tests
- ❌ Critical findings from audit unresolved
- ❌ No emergency pause functionality
- ❌ No multi-sig for admin operations
- ❌ No slippage protection on swaps
If any red flag is present: STOP and fix before deployment
Print this and check off before mainnet deployment:
- Reentrancy protection (state-changing + view functions)
- Oracle security (TWAP + Chainlink, no spot prices)
- Access control (multi-sig for admin)
- Flash loan detection (same-block prevention)
- All security tests pass
- MEV protection (slippage + deadline)
- Emergency pause functionality
- Events on all state changes
- Fuzz testing (≥ 256 runs)
- Code coverage ≥ 95%
- Professional audit completed (if public/mainnet with funds)
- Testnet deployment successful
- Multi-sig configured correctly
- Oracle feeds tested
- Contract verified on Etherscan
- Monitoring set up
- NatSpec on all functions
- Security assumptions documented
- Known limitations documented
- Emergency procedures documented
If a vulnerability is discovered:
-
Pause the contract immediately
// Call from PAUSER_ROLE contract.pause();
-
Notify users (Twitter, Discord, etc.)
-
Assess the vulnerability
- How critical?
- Funds at risk?
- Already exploited?
-
Coordinate response
- Security researchers
- Auditors
- Multi-sig signers
-
Deploy fix (if possible)
- Upgrade contract (if upgradeable)
- Deploy new contract
- Migrate users
-
Post-mortem
- Document what happened
- How it was fixed
- Lessons learned
Before deploying to mainnet, the following must sign off:
- Lead Developer: All code reviewed, tests pass
- Security Lead: All security patterns implemented
- Auditor: Audit complete, critical findings resolved
- Protocol Lead: Deployment approved
Deployment Date: ________________
Contract Address: ________________
Deployed By: ________________
Multi-Sig Address: ________________
Audit Report: ________________
- Templates:
/docs/testing-examples/ - Quick Start: DEFI_SECURITY_QUICKSTART.md
- Full Guide: DEFI_EXPLOIT_PROTECTION_GUIDE.md
- Tests:
/docs/testing-examples/DeFiSecurityTests.t.sol
💰 Cost of proper security: $10k-$100k
Cost of getting hacked: $MILLIONS + reputation damage
Choose wisely. Deploy safely. 🛡️