Tutorial·10 मिनट का पठन·Solingo द्वारा

Solidity Design Patterns — Best Practices 2026

सबसे important Solidity design patterns: Factory, Proxy, State Machine, Pull Payment और अधिक। Production code examples के साथ।

# 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:

Practice में लगाने के लिए तैयार हैं?

Solingo पर interactive exercises के साथ इन concepts को apply करें।

मुफ्त में शुरू करें