Smart Contract Security Checklist Before Mainnet Deployment
Deploying an unverified or vulnerable smart contract to a live EVM network is irreversible. Execute this pre-deployment checklist to audit contract logic, mitigate common vector attacks, and ensure operational readiness before running your final deployment script.
1. Reentrancy Protections
Ensure all functions making external calls resist reentrancy attacks by adhering to the Checks-Effects-Interactions (CEI) pattern and using mutex guards.
- Update internal state balances and properties before executing external transfer calls or invoking external contracts.
- Inherit OpenZeppelin's
ReentrancyGuardand apply thenonReentrantmodifier to state-changing functions with external interactions.
// Correct implementation of Checks-Effects-Interactions pattern
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance"); // Check
balances[msg.sender] -= amount; // Effect
(bool success, ) = msg.sender.call{value: amount}(""); // Interaction
require(success, "Transfer failed");
}
2. Access Control and Governance
Flawed access controls allow unauthorized actors to drain protocol treasuries or modify key operational parameters.
- Never rely on
tx.originfor authentication; usemsg.senderto prevent phishing exploits. - Explicitly declare visibility (
external,public,internal, orprivate) for all state variables and functions. - Validate critical input addresses in constructors and setter functions to prevent assigning key privileges to
address(0). - Transfer contract ownership and administrative power to a Multi-Sig wallet (e.g., Safe) or Timelock controller before launch.
3. Price Oracles and Flash Loan Defense
Direct spot-price queries from decentralised exchange pools leave smart contracts vulnerable to single-block flash loan manipulations.
- Avoid using spot reserves (e.g., Uniswap v2 pair reserves) as primary price feeds for lending or collateral logic.
- Integrate decentralized oracle networks (e.g., Chainlink) or Time-Weighted Average Prices (TWAP).
- Validate oracle return data for stale prices and incomplete rounds:
(uint80 roundId, int256 price, , uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
require(price > 0, "Invalid price");
require(updatedAt > 0 && block.timestamp - updatedAt < MAX_ORACLE_DELAY, "Stale price");
require(answeredInRound >= roundId, "Stale round");
4. Automated Tooling and Testing
Static analysis and dynamic fuzzing must be executed alongside standard unit tests.
- Achieve high test coverage across all branches and state transitions using Foundry or Hardhat.
- Run static analyzers like Slither and Mythril to identify uninitialized storage pointers, reentrancy vectors, and unused functions.
- Execute property-based fuzz tests and invariant tests to ensure protocol assumptions hold under random edge-case inputs.
5. Operational Safeguards and Verification
Prepare emergency controls and operational scripts for immediate post-deployment execution.
- Implement emergency pause capabilities (e.g., OpenZeppelin
Pausable) to temporarily freeze functionality during an exploit. - Prefer pull-over-push patterns for token and ETH transfers to avoid denial-of-service (DoS) conditions caused by failing recipient contracts.
- Dry-run full deployment scripts on a local fork (e.g., Anvil) and an EVM testnet (e.g., Sepolia).
- Automate contract source code verification via Etherscan or local compiler plugins upon deployment.
Need this done? We handle this hands-on at GuardLabs — get in touch for a quote.
I take on freelance fixes and builds in this area.