# Solidity Design Patterns — Best Practices 2026
Design patterns proven solutions हैं common problems के लिए। Solidity में, सही patterns use करना security, gas efficiency और maintainability के लिए critical है।
इस guide में, हम 2026 के सबसे important Solidity design patterns cover करेंगे production examples के साथ।
1. Checks-Effects-Interactions Pattern
Problem: Reentrancy attacks
Solution: Operations को सही order में organize करें
// ❌ VULNERABLE: Interaction before effect
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}(""); // Interaction
require(success);
balances[msg.sender] -= amount; // Effect (too late!)
}
// ✅ SECURE: Effects before interactions
function withdraw(uint256 amount) public {
// 1. Checks
require(balances[msg.sender] >= amount, "Insufficient balance");
// 2. Effects
balances[msg.sender] -= amount;
// 3. Interactions
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
2. Pull Over Push Pattern
Problem: Failed transfers blocking execution
Solution: Users खुद funds withdraw करें
// ❌ Push pattern: Risky
function distributeRewards(address[] calldata users) public {
for (uint i = 0; i < users.length; i++) {
(bool success,) = users[i].call{value: 1 ether}("");
// अगर एक fails, सब fail हो जाता है!
}
}
// ✅ Pull pattern: Safe
mapping(address => uint256) public pendingWithdrawals;
function creditReward(address user) internal {
pendingWithdrawals[user] += 1 ether;
}
function withdraw() public {
uint256 amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0; // Effect before interaction
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
3. Factory Pattern
Problem: Multiple similar contracts deploy करना
Solution: Factory contract जो clones create करता है
contract Token {
string public name;
string public symbol;
address public owner;
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
owner = msg.sender;
}
}
contract TokenFactory {
Token[] public tokens;
event TokenCreated(address indexed token, address indexed owner);
function createToken(string memory name, string memory symbol)
public
returns (address)
{
Token token = new Token(name, symbol);
tokens.push(token);
emit TokenCreated(address(token), msg.sender);
return address(token);
}
function getTokenCount() public view returns (uint256) {
return tokens.length;
}
}
Optimized: Clone Pattern (EIP-1167)
Deployment cost 10x reduce करें:
contract TokenFactory {
address public implementation;
constructor(address _implementation) {
implementation = _implementation;
}
function createToken() public returns (address clone) {
// Minimal proxy: ~45k gas vs ~200k gas
bytes20 targetBytes = bytes20(implementation);
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
clone := create(0, clone, 0x37)
}
return clone;
}
}
4. Proxy Pattern (Upgradeable Contracts)
Problem: Contract code immutable है
Solution: Logic को separate contract में रखें
// Storage contract (proxy)
contract Proxy {
address public implementation;
address public admin;
constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
// Logic contract
contract ImplementationV1 {
address public implementation; // Storage layout match
address public admin;
uint256 public value;
function setValue(uint256 _value) public {
value = _value;
}
}
⚠️ Warning: Storage layout proxy और implementation में match होनी चाहिए!
5. State Machine Pattern
Problem: Complex workflows manage करना
Solution: Explicit states के साथ state machine
contract Crowdfunding {
enum State { Fundraising, Expired, Successful }
State public state = State.Fundraising;
uint256 public deadline;
uint256 public goal;
uint256 public totalRaised;
modifier inState(State _state) {
require(state == _state, "Invalid state");
_;
}
modifier checkDeadline() {
if (block.timestamp > deadline && state == State.Fundraising) {
state = totalRaised >= goal ? State.Successful : State.Expired;
}
_;
}
function contribute() public payable inState(State.Fundraising) checkDeadline {
totalRaised += msg.value;
}
function withdraw() public inState(State.Successful) {
// Creator withdraws
}
function refund() public inState(State.Expired) {
// Contributors get refund
}
}
6. Access Control Pattern
Problem: Functions को restrict करना
Solution: Modifiers और role-based access
contract AccessControl {
mapping(bytes32 => mapping(address => bool)) public roles;
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN");
bytes32 public constant MINTER_ROLE = keccak256("MINTER");
modifier onlyRole(bytes32 role) {
require(roles[role][msg.sender], "Unauthorized");
_;
}
constructor() {
roles[ADMIN_ROLE][msg.sender] = true;
}
function grantRole(bytes32 role, address account)
public
onlyRole(ADMIN_ROLE)
{
roles[role][account] = true;
}
function mint(address to, uint256 amount)
public
onlyRole(MINTER_ROLE)
{
// Minting logic
}
}
7. Circuit Breaker (Pausable)
Problem: Emergency situations में contract stop करना
Solution: Pause mechanism
contract CircuitBreaker {
bool public paused;
address public owner;
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function pause() public onlyOwner {
paused = true;
}
function unpause() public onlyOwner {
paused = false;
}
function transfer(address to, uint256 amount)
public
whenNotPaused
{
// Transfer logic
}
}
8. Oracle Pattern
Problem: Off-chain data access करना
Solution: Trusted oracle contract
interface IPriceOracle {
function getPrice(string memory symbol) external view returns (uint256);
}
contract DeFiProtocol {
IPriceOracle public oracle;
constructor(address _oracle) {
oracle = IPriceOracle(_oracle);
}
function liquidate(address user) public {
uint256 ethPrice = oracle.getPrice("ETH");
// Liquidation logic using price
}
}
9. Commit-Reveal Pattern
Problem: On-chain secrets (सब public है)
Solution: Two-phase submission
contract CommitReveal {
mapping(address => bytes32) public commits;
mapping(address => uint256) public reveals;
// Phase 1: Commit hash
function commit(bytes32 hash) public {
commits[msg.sender] = hash;
}
// Phase 2: Reveal value
function reveal(uint256 value, bytes32 salt) public {
require(
commits[msg.sender] == keccak256(abi.encodePacked(value, salt)),
"Invalid reveal"
);
reveals[msg.sender] = value;
}
}
10. Eternal Storage Pattern
Problem: Upgrades में storage layout break होना
Solution: Separate eternal storage contract
contract EternalStorage {
mapping(bytes32 => uint256) uintStorage;
mapping(bytes32 => address) addressStorage;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function getUint(bytes32 key) public view returns (uint256) {
return uintStorage[key];
}
function setUint(bytes32 key, uint256 value) public onlyOwner {
uintStorage[key] = value;
}
}
contract Logic {
EternalStorage public db;
function getValue() public view returns (uint256) {
return db.getUint(keccak256("myValue"));
}
function setValue(uint256 value) public {
db.setUint(keccak256("myValue"), value);
}
}
Anti-Patterns (Avoid करें!)
❌ tx.origin for Authorization
// VULNERABLE to phishing
function withdraw() public {
require(tx.origin == owner); // ❌ BAD
}
// Use msg.sender instead
function withdraw() public {
require(msg.sender == owner); // ✅ GOOD
}
❌ Block Values as Randomness
// ❌ Predictable!
function random() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp)));
}
// ✅ Use Chainlink VRF
❌ Unbounded Loops
// ❌ Gas limit risk
function payEveryone() public {
for (uint i = 0; i < users.length; i++) { // Length unbounded!
// ...
}
}
// ✅ Use pagination
function payBatch(uint256 start, uint256 end) public {
for (uint i = start; i < end && i < users.length; i++) {
// ...
}
}
निष्कर्ष
Design patterns Solidity development का foundation हैं। सही pattern चुनना security, efficiency और maintainability ensure करता है।
Key takeaways:
- Checks-Effects-Interactions हर state-changing function में
- Pull > Push for payments
- Factory pattern for mass deployment
- Proxy pattern for upgrades (carefully!)
- State machines for complex workflows
- Access control always explicit
अगले कदम: Solingo पर इन patterns को practice करें 1000+ exercises के साथ!
---
अतिरिक्त Resources:
- OpenZeppelin Contracts (pattern implementations)