# Smart Contract Audit Checklist — Deployment से पहले 20 चीज़ें Check करें
Smart contract को mainnet पर deploy करना permanent है। कोई "undo" button नहीं, कोई hotfixes नहीं, कोई patches नहीं। एक vulnerability millions drain कर सकती है।
यह checklist 20 critical security checks को cover करता है जो हर developer को deployment से पहले perform करने चाहिए। चाहे आप self-auditing कर रहे हों या professional audit के लिए prepare कर रहे हों, ये points आपको dangerous issues early catch करने में help करेंगे।
Access Control (1-3)
1. ✅ सभी Privileged Functions Protected हैं
Critical state modify करने वाले हर function में access control होना चाहिए।
Check:
// BAD: कोई भी withdraw कर सकता है
function withdraw(uint256 amount) external {
payable(msg.sender).transfer(amount);
}
// GOOD: केवल owner withdraw कर सकता है
function withdraw(uint256 amount) external onlyOwner {
payable(msg.sender).transfer(amount);
}
Tools: Slither --detect unprotected-upgrade के साथ unprotected functions detect करता है
2. ✅ Constructor में Owner Correctly Set है
Ownership properly initialize किया गया है ensure करें।
// BAD: Owner zero address पर default होता है
address public owner;
// GOOD: Constructor में owner set होता है
constructor() {
owner = msg.sender;
}
Test: Deployment के बाद verify करें कि owner correctly set है।
3. ✅ Two-Step Ownership Transfer
Accidental ownership loss prevent करें।
// BAD: Single-step, irreversible
function transferOwnership(address newOwner) external onlyOwner {
owner = newOwner; // Typo = permanent loss
}
// GOOD: Two-step process
address public pendingOwner;
function transferOwnership(address newOwner) external onlyOwner {
pendingOwner = newOwner;
}
function acceptOwnership() external {
require(msg.sender == pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
}
Use: OpenZeppelin का Ownable2Step
Reentrancy (4-5)
4. ✅ Checks-Effects-Interactions Pattern
External calls से पहले state updates।
// BAD: State update से पहले external call
function withdraw() external {
uint256 amount = balances[msg.sender];
(bool success,) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0; // बहुत LATE
}
// GOOD: पहले state update
function withdraw() external {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0; // Call से BEFORE update
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
5. ✅ External Calls पर ReentrancyGuard
Defense in depth।
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Safe is ReentrancyGuard {
function withdraw() external nonReentrant {
// Protected
}
}
Test: Protection verify करने के लिए reentrancy attack tests लिखें।
Integer Operations (6-7)
6. ✅ Unchecked Math नहीं (Unless Intentional)
Solidity 0.8+ में built-in overflow protection है। बिना reason के इसे disable न करें।
// BAD: बिना justification के unchecked
unchecked {
balance += amount; // Overflow हो सकता है
}
// GOOD: केवल safe होने पर unchecked उपयोग करें
unchecked {
// Loop counter realistically overflow नहीं होगा
for (uint256 i = 0; i < items.length; ++i) {
// ...
}
}
7. ✅ Multiplication से पहले Division
Rounding errors minimize करें।
// BAD: पहले division (precision खोता है)
uint256 result = (value / 100) * 25; // value का 0.25%
// GOOD: पहले multiplication
uint256 result = (value * 25) / 100;
Input Validation (8-10)
8. ✅ सभी Inputs Validated हैं
User input पर कभी trust न करें।
function setFee(uint256 _fee) external onlyOwner {
require(_fee <= 1000, "Fee too high"); // Max 10%
fee = _fee;
}
function transfer(address to, uint256 amount) external {
require(to != address(0), "Invalid address");
require(amount > 0, "Invalid amount");
// ...
}
9. ✅ Array Length Checks
Out-of-gas errors prevent करें।
function batchTransfer(address[] calldata recipients, uint256 amount) external {
require(recipients.length <= 100, "Too many recipients");
for (uint256 i = 0; i < recipients.length; i++) {
_transfer(msg.sender, recipients[i], amount);
}
}
10. ✅ Zero Address Checks
Accidentally tokens/ETH burn करना prevent करें।
function setTreasury(address _treasury) external onlyOwner {
require(_treasury != address(0), "Zero address");
treasury = _treasury;
}
Gas Optimization (11-13)
11. ✅ Storage Access Minimized है
Storage reads को memory में cache करें।
// BAD: Multiple storage reads
function calculateReward() external view returns (uint256) {
return userBalance[msg.sender] * rewardRate * stakingPeriod / 1e18;
// हर variable read storage से 2100 gas cost करता है
}
// GOOD: Memory में cache
function calculateReward() external view returns (uint256) {
uint256 balance = userBalance[msg.sender]; // One storage read
uint256 rate = rewardRate;
uint256 period = stakingPeriod;
return balance * rate * period / 1e18;
}
12. ✅ Loop Gas Limits Considered हैं
Unbounded loops gas से बाहर हो सकते हैं।
// BAD: Unbounded loop
function distributeRewards() external {
for (uint256 i = 0; i < users.length; i++) {
// अगर users.length = 10000, यह fail होगा
_transfer(users[i], rewardAmount);
}
}
// GOOD: Batch processing
function distributeRewards(uint256 start, uint256 end) external {
require(end <= users.length);
require(end - start <= 100, "Batch too large");
for (uint256 i = start; i < end; i++) {
_transfer(users[i], rewardAmount);
}
}
13. ✅ Efficient Data Types
Appropriate sizes उपयोग करें।
// BAD: Wasteful storage
struct User {
uint256 id; // uint32 हो सकता है
uint256 timestamp; // uint32 हो सकता है
bool active; // 1 byte लेकिन full slot लेता है
}
// GOOD: Packed storage
struct User {
uint32 id; // 4 bytes
uint32 timestamp; // 4 bytes
bool active; // 1 byte
// सभी एक 32-byte slot में fit
}
Events (14-15)
14. ✅ Critical Actions Events Emit करते हैं
Off-chain tracking के लिए essential।
event Transfer(address indexed from, address indexed to, uint256 amount);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event Paused(address account);
function transfer(address to, uint256 amount) external {
// ...
emit Transfer(msg.sender, to, amount);
}
15. ✅ Filtering के लिए Indexed Parameters
हर event में up to 3 indexed parameters।
// GOOD: Key fields indexed
event Deposit(
address indexed user,
address indexed token,
uint256 amount,
uint256 timestamp
);
External Interactions (16-17)
16. ✅ External Calls Safely Handled हैं
हमेशा return values check करें।
// BAD: Return value ignore करना
token.transfer(recipient, amount);
// GOOD: Return value check करें
bool success = token.transfer(recipient, amount);
require(success, "Transfer failed");
// BETTER: SafeERC20 उपयोग करें
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
token.safeTransfer(recipient, amount); // Failure पर reverts
17. ✅ msg.sender Used है (tx.origin नहीं)
इस पर हमारा dedicated article देखें।
// BAD: Phishing vulnerable
require(tx.origin == owner);
// GOOD: Secure
require(msg.sender == owner);
Upgradability (18-19)
18. ✅ Storage Layout Preserved है (Upgradeable Contracts)
Upgrades में variables rearrange न करें।
// V1
contract TokenV1 {
address public owner;
uint256 public totalSupply;
}
// BAD V2: Inserted variable storage break करता है
contract TokenV2 {
address public owner;
uint256 public newVariable; // totalSupply location BREAKS करता है
uint256 public totalSupply;
}
// GOOD V2: केवल append
contract TokenV2 {
address public owner;
uint256 public totalSupply;
uint256 public newVariable; // End पर appended
}
19. ✅ Proxies के लिए Initialization (Constructor नहीं)
Proxies constructors execute नहीं करते।
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract TokenUpgradeable is Initializable {
address public owner;
// BAD: Constructor run नहीं होगा
constructor() {
owner = msg.sender;
}
// GOOD: Initialize function
function initialize() public initializer {
owner = msg.sender;
}
}
Testing (20)
20. ✅ Comprehensive Test Coverage
100% line coverage का aim करें, लेकिन साथ ही test करें:
Unit Tests:
function testOwnerCanWithdraw() public {
vm.prank(owner);
contract.withdraw(100);
assertEq(owner.balance, 100);
}
Negative Tests:
function testNonOwnerCannotWithdraw() public {
vm.prank(user);
vm.expectRevert("Not owner");
contract.withdraw(100);
}
Edge Cases:
function testWithdrawZero() public {
vm.prank(owner);
vm.expectRevert("Amount must be > 0");
contract.withdraw(0);
}
Fuzzing:
function testWithdrawFuzz(uint256 amount) public {
vm.assume(amount > 0 && amount <= address(contract).balance);
vm.prank(owner);
contract.withdraw(amount);
}
Tools to Run
Deployment से पहले, ये automated tools run करें:
# Static analysis
slither .
# Formal verification (अगर applicable हो)
certora-cli
# Test coverage
forge coverage
# Gas optimization
forge test --gas-report
Professional Audit
यह checklist common issues catch करता है, लेकिन professional audits high-value contracts के लिए irreplaceable हैं।
कब audit कराएँ:
- >$100k TVL manage कर रहे हों
- Complex DeFi logic (DEX, lending, derivatives)
- Upgradeable contracts
- Multi-signature या DAO governance
- किसी भी public protocol की mainnet launch से पहले
Top audit firms:
- Trail of Bits
- OpenZeppelin
- ConsenSys Diligence
- Certora
- ChainSecurity
Final Checklist
Mainnet पर deploy करने से पहले:
✅ इस article के सभी 20 points addressed
✅ कोई high/medium issues नहीं के साथ Slither run
✅ 100% test coverage achieved
✅ Fuzzing tests passed
✅ Gas optimizations applied
✅ Professional audit completed (अगर high-value)
✅ Testnet पर deployment tested
✅ Emergency pause mechanism in place
✅ Bug bounty program prepared
✅ Documentation complete
Conclusion
Security एक checkbox नहीं है — यह एक mindset है। यह checklist एक solid foundation provide करता है, लेकिन secure रहने के लिए चाहिए:
- Continuous learning (attacks evolve करते हैं)
- Conservative coding (simplicity > cleverness)
- Defense in depth (multiple protections)
- Humility (assume करें कि आपने कुछ miss किया)
Stakes real हैं। एक single vulnerability millions cost कर सकती है। Thoroughly audit करने के लिए time लें।
Solingo पर secure development practice करें — हमारे platform में automated security checks, real-world vulnerability simulations, और इस checklist की सभी items के लिए step-by-step remediation guides शामिल हैं।