← Back to list

Solidity BasicsPart 3: Advanced Patterns

Series: Web3 Security Zero se Advance 🛡️ | Article #7 By HackerMD | 26 min read

Hacker MD · 2026-06-02 14:31 · 53 claps · 16.5 min read
#solidity #web3 #infosec #bug-bounty #penetration-testing
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3 🔒 · Cybersecurity

Solidity BasicsPart 3: Advanced Patterns

Series: Web3 Security Zero se Advance 🛡️ | Article #7 By HackerMD | 26 min read

Aaj Kya Seekhenge?

  • Receive & Fallback functions ETH receive karna
  • Low-level calls call, delegatecall, staticcall
  • Assembly (Yul) inline assembly basics
  • ABI Encoding abi.encode, abi.encodePacked
  • Contract creation CREATE vs CREATE2
  • Gas optimization patterns
  • Checks-Effects-Interactions (CEI) pattern
  • ReentrancyGuard kaise kaam karta hai
  • Har concept pe security bugs aur attacks!

Hacker Note: Yeh article ka har concept directly kisi na kisi million dollar hack se linked hai! Fallback function = TheDAO hack! Low-level calls = Reentrancy! CREATE2 = Salting attacks! Ek ek cheez dhyan se padho!

PART 1: Receive & Fallback ETH Kaise Aata Hai Contract Mein?

// Contract ETH receive karne ke liye
// Special functions chahiye!

contract ETHReceiver {

    event Received(address from, uint256 amount);
    event FallbackCalled(
        address from,
        uint256 amount,
        bytes data
    );

    // ─── receive() ────────────────────────
    // Jab:
    // → Pure ETH transfer aaye (no data)
    // → msg.data EMPTY ho
    // Conditions:
    // → MUST be external
    // → MUST be payable
    // → No arguments, no return

    receive() external payable {
        emit Received(msg.sender, msg.value);
        // Simple ETH receive!
    }

    // ─── fallback() ───────────────────────
    // Jab:
    // → Koi function match nahi karta
    // → msg.data non-empty ho
    // → receive() nahi hai aur ETH aaye
    // Conditions:
    // → MUST be external
    // → payable optional

    fallback() external payable {
        emit FallbackCalled(
            msg.sender,
            msg.value,
            msg.data
        );
    }
}

// ETH receive decision tree:
//
//   ETH Transfer aaya
//         │
//   msg.data empty?
//   ┌─────┴──────┐
//  YES           NO
//   │             │
// receive()    fallback()
// exist?       exist?
// ┌──┴──┐      ┌──┴──┐
// YES   NO    YES   NO
//  │     │     │     │
// run  fallback run  REVERT!
//      run?
//      ┌┴┐
//     YES NO
//      │   │
//     run REVERT

Fallback Security Bugs:

// BUG 1: TheDAO Style — Fallback Reentrancy!
// (2016 — $60M hack ka origin)

contract VulnerableDAO {
    mapping(address => uint256) public balances;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() external {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "Nothing to withdraw!");

        // ⚠️ External call PEHLE!
        (bool ok,) = msg.sender.call{
            value: amount
        }("");
        require(ok, "Transfer failed!");

        // ⚠️ State update BAAD MEIN!
        balances[msg.sender] = 0;
        // Attacker ka fallback/receive baar baar
        // withdraw() call karta hai!
        // Balance kabhi 0 nahi hota withdraw se pehle!
    }
}

// Attacker contract:
contract Attacker {
    VulnerableDAO dao;
    uint256 public count;

    constructor(address _dao) {
        dao = VulnerableDAO(_dao);
    }

    function attack() external payable {
        dao.deposit{value: msg.value}();
        dao.withdraw();
    }

    // Yeh baar baar call hoga!
    receive() external payable {
        count++;
        if (count < 10) {
            dao.withdraw(); // Re-enter!
        }
    }
}

// ✅ FIX: CEI Pattern (baad mein detail mein!)
function withdrawSafe() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "Nothing!");

    // Effect FIRST!
    balances[msg.sender] = 0;

    // Interaction LAST!
    (bool ok,) = msg.sender.call{
        value: amount
    }("");
    require(ok, "Transfer failed!");
}

// ─────────────────────────────────────────

// BUG 2: Accidental ETH lock!
contract NoReceive {
    // receive() aur fallback() DONO missing!

    function doSomething() external {
        // Normal function
    }

    // Koi ETH bheje → REVERT!
    // ETH transfer always fails!
    // Agar protocol ETH expect karta tha:
    // → Funds stuck forever!
}

// ─────────────────────────────────────────

// BUG 3: Payable fallback — unintended ETH accept!
contract Dangerous {
    // Developer ne sirf fallback likha
    // "proxy ke liye"
    fallback() external payable {
        // Forward to implementation...
    }
    // Ab koi bhi ETH bhej sakta hai!
    // Contract ETH hold karne ke liye
    // designed nahi tha!
    // ETH permanently stuck!
}

PART 2: Low-Level Calls Powerful aur Dangerous!

contract LowLevelCalls {

    // ─── .call() ──────────────────────────
    // Sabse flexible!
    // ETH transfer + function call
    // Returns: (bool success, bytes memory data)

    function callExample(
        address target,
        bytes memory data
    ) external payable returns (bool, bytes memory) {

        // With ETH:
        (bool ok, bytes memory ret) =
            target.call{
                value: msg.value,
                gas: 50000  // Gas limit optional
            }(data);

        return (ok, ret);
    }

    // Specific function call:
    function callTransfer(
        address token,
        address to,
        uint256 amount
    ) external returns (bool) {

        (bool ok, bytes memory data) = token.call(
            abi.encodeWithSignature(
                "transfer(address,uint256)",
                to,
                amount
            )
        );

        // Return value decode:
        if (ok && data.length > 0) {
            return abi.decode(data, (bool));
        }
        return ok;
    }

    // ─── .delegatecall() ──────────────────
    // Target ka CODE — Caller ka CONTEXT!
    // msg.sender = Original caller
    // Storage = Calling contract ka!
    // ⚠️ EXTREMELY DANGEROUS!

    function delegateExample(
        address implementation,
        bytes memory data
    ) external returns (bool, bytes memory) {

        (bool ok, bytes memory ret) =
            implementation.delegatecall(data);

        return (ok, ret);
    }

    // ─── .staticcall() ────────────────────
    // Read-only call!
    // State change → REVERT!
    // Safe for reading data

    function staticExample(
        address target,
        bytes memory data
    ) external view returns (bytes memory) {

        (bool ok, bytes memory ret) =
            target.staticcall(data);

        require(ok, "Static call failed!");
        return ret;
    }
}

Call vs DelegateCall vs StaticCall:

Feature          | call()      | delegatecall() | staticcall()
-----------------|-------------|----------------|-------------
msg.sender       | This contract| Original caller| This contract
msg.value        | Forwarded   | Caller's value | 0
Storage used     | Target's    | CALLER'S!      | Target's
State change     | Allowed     | CALLER's state!| FORBIDDEN!
ETH transfer     | Yes         | No             | No
Use case         | Normal calls| Proxy pattern  | Read only
Security risk    | Reentrancy  | Storage collision| Safe!

DELEGATECALL VISUAL:
┌──────────────────┐    delegatecall    ┌──────────────────┐
│  Contract A      │ ─────────────────→ │  Library/Impl B  │
│                  │                    │                  │
│  Storage:        │                    │  Code runs here  │
│  slot0: owner    │ ←── modifies ────  │  but A's storage │
│  slot1: balance  │                    │  is affected!    │
└──────────────────┘                    └──────────────────┘

Low-Level Call Bugs:

// BUG 1: Return value not checked!
contract Unchecked {
    function sendETH(address to) external {
        // ⚠️ Return value IGNORE!
        to.call{value: 1 ether}("");
        // Agar call fail ho?
        // No revert! Code continues!
        // ETH lost silently!
    }

    // ✅ CORRECT:
    function sendETHSafe(address to) external {
        (bool ok,) = to.call{value: 1 ether}("");
        require(ok, "ETH transfer failed!");
    }
}

// ─────────────────────────────────────────

// BUG 2: Arbitrary delegatecall!
contract ArbitraryDelegate {

    address public owner;  // slot 0

    // ⚠️ User controlled target!
    function execute(
        address target,  // Attacker controls this!
        bytes memory data
    ) external {
        target.delegatecall(data);
        // Attacker passes malicious contract:
        // contract Evil {
        //     function pwn() external {
        //         // slot 0 = owner!
        //         assembly { sstore(0, caller()) }
        //     }
        // }
        // Result: owner = attacker!
    }
}

// ─────────────────────────────────────────

// BUG 3: Gas forwarding attack!
contract GasIssue {
    function forward(address target) external {
        // All gas forward kar raha hai!
        target.call{gas: gasleft()}(
            abi.encodeWithSignature("expensive()")
        );
        // Target ko infinite gas!
        // Return bomb possible!
    }

    // ✅ CORRECT: Gas limit lagao!
    function forwardSafe(address target) external {
        target.call{gas: 50000}(
            abi.encodeWithSignature("safe()")
        );
    }
}

PART 3: Inline Assembly EVM Ka Direct Access!

// Assembly = EVM Opcodes directly Solidity mein
// Use cases:
// → Gas optimization
// → Operations Solidity mein possible nahi
// → Low-level storage manipulation
// → Custom memory management

contract AssemblyExamples {

    // ─── Basic Assembly ───────────────────
    function addAssembly(uint256 a, uint256 b)
        public pure
        returns (uint256 result)
    {
        assembly {
            result := add(a, b)
            // := assignment in Yul
        }
    }

    // ─── Storage Direct Access ────────────
    function setSlot(uint256 slot, uint256 value)
        internal
    {
        assembly {
            sstore(slot, value)
        }
    }

    function getSlot(uint256 slot)
        internal view
        returns (uint256 value)
    {
        assembly {
            value := sload(slot)
        }
    }

    // ─── Address Operations ───────────────
    function getContractSize(address addr)
        public view
        returns (uint256 size)
    {
        assembly {
            size := extcodesize(addr)
        }
        // extcodesize = 0 → EOA
        // extcodesize > 0 → Contract
        // ⚠️ But: Constructor ke time = 0!
        // isContract() bypass possible!
    }

    // ─── Efficient ETH Transfer ───────────
    function efficientTransfer(
        address to,
        uint256 amount
    ) internal {
        assembly {
            // Direct ETH transfer
            let ok := call(
                gas(),      // All gas
                to,         // Recipient
                amount,     // Value
                0, 0,       // No input data
                0, 0        // No output
            )
            if iszero(ok) {
                revert(0, 0)
            }
        }
    }

    // ─── Memory Operations ────────────────
    function memoryExample()
        public pure
        returns (bytes32 result)
    {
        assembly {
            // Free memory pointer:
            let ptr := mload(0x40)
            // 0x40 = Free memory pointer location!

            // Store data:
            mstore(ptr, 0x12345678)

            // Update free memory pointer:
            mstore(0x40, add(ptr, 0x20))

            result := mload(ptr)
        }
    }
}

Assembly Security Bugs:

// BUG 1: isContract bypass via constructor!
contract ConstructorBypass {

    modifier onlyEOA() {
        uint256 size;
        assembly {
            size := extcodesize(caller())
        }
        require(size == 0, "No contracts!");
        _;
    }

    function sensitiveFunc()
        external
        onlyEOA
    {
        // "Only EOA can call" — OR CAN THEY?
    }
}

// Attack:
contract Attacker {
    constructor(address target) {
        // Constructor ke time:
        // extcodesize(this) = 0!
        // Even though we ARE a contract!
        ConstructorBypass(target).sensitiveFunc();
        // ← Check passes! We're a contract!
        // but check thinks we're EOA!
    }
}

// ─────────────────────────────────────────

// BUG 2: Memory corruption!
contract MemoryBug {
    function dangerous(bytes memory data)
        external pure
        returns (uint256)
    {
        assembly {
            // ⚠️ Free memory pointer nahi update kiya!
            let ptr := mload(0x40)
            mstore(ptr, mload(add(data, 0x20)))
            // 0x40 update bhool gaya!
            // Next allocation same ptr use karega!
            // Memory overlap = Data corruption!
        }
    }
}

// ─────────────────────────────────────────

// BUG 3: Arbitrary storage write!
contract StorageWrite {
    address public owner;     // slot 0
    uint256 public totalFunds; // slot 1

    function updateConfig(
        uint256 slot,   // ⚠️ User controlled!
        uint256 value
    ) external {
        assembly {
            sstore(slot, value)
            // Attacker → slot=0, value=attacker_addr
            // owner = attacker!
        }
    }
}

PART 4: ABI Encoding Data Kaise Pack Hota Hai!

contract ABIEncoding {

    // ─── abi.encode() ─────────────────────
    // Padded encoding — 32 bytes per element
    // Safe for all types!

    function encodeExample()
        external pure
        returns (bytes memory)
    {
        return abi.encode(
            uint256(100),
            address(0xABC...),
            bool(true)
        );
        // Each value padded to 32 bytes:
        // 0x0000...0064  (100)
        // 0x0000...ABC   (address)
        // 0x0000...0001  (true)
        // Total: 96 bytes
    }

    // ─── abi.encodePacked() ───────────────
    // Compact encoding — NO padding!
    // Gas efficient
    // ⚠️ COLLISION POSSIBLE!

    function encodePackedExample()
        external pure
        returns (bytes memory)
    {
        return abi.encodePacked(
            uint256(100),
            address(0xABC...)
        );
        // No padding! Tight packing!
        // 32 bytes + 20 bytes = 52 bytes
    }

    // ─── abi.encodeWithSelector() ─────────
    // Function call data encode karo!

    function encodeCall()
        external pure
        returns (bytes memory)
    {
        return abi.encodeWithSelector(
            bytes4(keccak256(
                "transfer(address,uint256)"
            )),
            address(0xBob),
            uint256(1000)
        );
        // = 0xa9059cbb + encoded args
    }

    // ─── abi.encodeWithSignature() ────────
    // String signature use karo

    function encodeWithSig()
        external pure
        returns (bytes memory)
    {
        return abi.encodeWithSignature(
            "transfer(address,uint256)",
            address(0xBob),
            uint256(1000)
        );
        // Same as above — cleaner syntax!
    }

    // ─── Decoding ─────────────────────────
    function decodeExample(bytes memory data)
        external pure
        returns (uint256, address, bool)
    {
        return abi.decode(
            data,
            (uint256, address, bool)
        );
    }
}

ABI Encoding Bugs:

// BUG: encodePacked Collision Attack!
// Hash signing mein bahut common!

contract VulnerableSig {

    function getHash(
        address user,
        uint256 amount,
        string memory message
    ) public pure returns (bytes32) {

        // ⚠️ encodePacked collision!
        return keccak256(
            abi.encodePacked(user, amount, message)
        );
    }
}

// ATTACK:
// Normal call:
// user=0xAlice, amount=100, message="hello"
// Packed: [alice_20bytes][100_32bytes][hello_5bytes]

// Attacker:
// user=0xAlice, amount=100, message="hell"
// + different amount in message!
// Packed bytes can be SAME!
// Hash collision! Signature reuse!

// ✅ FIX: Use abi.encode() for dynamic types!
function getHashSafe(
    address user,
    uint256 amount,
    string memory message
) public pure returns (bytes32) {
    return keccak256(
        abi.encode(user, amount, message)
        // Padded encoding = No collision!
    );
}

// ─────────────────────────────────────────

// REAL EXAMPLE: Signature Replay
contract VulnPayment {
    mapping(bytes32 => bool) public usedHashes;

    function pay(
        address to,
        uint256 amount,
        uint8 v, bytes32 r, bytes32 s
    ) external {
        bytes32 hash = keccak256(
            abi.encodePacked(to, amount)
            // ⚠️ No chainId! No nonce!
        );

        address signer = ecrecover(hash, v, r, s);
        require(signer == owner, "Invalid sig!");

        // ⚠️ No hash usage tracking!
        payable(to).transfer(amount);
        // Replay attack!
        // Same signature → Infinite payments!
    }

    // ✅ CORRECT:
    function paySafe(
        address to,
        uint256 amount,
        uint256 nonce,
        uint8 v, bytes32 r, bytes32 s
    ) external {
        bytes32 hash = keccak256(
            abi.encode(
                to,
                amount,
                nonce,
                block.chainid,  // Chain specific!
                address(this)   // Contract specific!
            )
        );

        require(!usedHashes[hash], "Replayed!");
        usedHashes[hash] = true;  // Mark used!

        address signer = ecrecover(hash, v, r, s);
        require(signer == owner, "Invalid!");

        payable(to).transfer(amount);
    }
}

PART 5: Contract Creation CREATE vs CREATE2!

// ─── CREATE (Regular) ─────────────────────
// Address = keccak256(deployer_address + nonce)
// Nonce se predictable — lekin pehle se predict karna mushkil

contract Factory {

    event ContractCreated(address newContract);

    function createChild() external
        returns (address)
    {
        // Regular CREATE:
        ChildContract child = new ChildContract(
            msg.sender
        );
        emit ContractCreated(address(child));
        return address(child);
    }
}

// ─────────────────────────────────────────

// ─── CREATE2 (Deterministic) ──────────────
// Address = keccak256(0xFF + deployer + salt + bytecodeHash)
// SALT control karo → Address predict karo!
// Deploy karo → Destroy karo → Same address pe re-deploy!

contract Factory2 {

    event ContractCreated(
        address newContract,
        bytes32 salt
    );

    function createWithSalt(
        bytes32 salt
    ) external returns (address) {

        ChildContract child = new ChildContract{
            salt: salt
        }(msg.sender);

        emit ContractCreated(address(child), salt);
        return address(child);
    }

    // Address predict karo DEPLOY se pehle!
    function predictAddress(
        bytes32 salt
    ) external view returns (address) {

        bytes memory bytecode = abi.encodePacked(
            type(ChildContract).creationCode,
            abi.encode(msg.sender)
        );

        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xFF),
                address(this),
                salt,
                keccak256(bytecode)
            )
        );

        return address(uint160(uint256(hash)));
    }
}

contract ChildContract {
    address public owner;

    constructor(address _owner) {
        owner = _owner;
    }
}

CREATE2 Security Bugs:

// BUG: Metamorphic Contract Attack!
// Same address pe different code deploy!

// Step 1: Malicious factory deploy karo
contract MetamorphicFactory {

    mapping(bytes32 => address) public contracts;

    function deploy(
        bytes32 salt,
        bytes memory initCode
    ) external returns (address addr) {

        assembly {
            addr := create2(
                0,
                add(initCode, 0x20),
                mload(initCode),
                salt
            )
        }
    }

    function destroy(bytes32 salt) external {
        address target = contracts[salt];
        // Selfdestruct the deployed contract
        IDestructible(target).destroy();
        delete contracts[salt];
    }
}

// ATTACK SCENARIO:
// 1. Audit request: Deploy "safe" contract at addr X
//    Auditors verify code at X → Clean! ✅
//
// 2. SELFDESTRUCT the contract at X!
//    (Same address pe code wipe!)
//
// 3. CREATE2 same salt → Same address X!
//    Deploy MALICIOUS code at same X!
//
// 4. Users trust "audited" address X
//    Reality: Malicious code!
//
// Defense:
// → SELFDESTRUCT wale contracts trust mat karo!
// → Code hash verify karo har interaction pe!
// → Immutable contracts prefer karo!

PART 6: CEI Pattern Sabse Important Security Pattern!

// CEI = Checks → Effects → Interactions
//
// Checks     = All validations first
// Effects    = State changes second
// Interactions = External calls LAST!
//
// Kyun? Reentrancy prevent karne ke liye!

// ⚠️ WRONG ORDER (Vulnerable):
contract WrongOrder {
    mapping(address => uint256) balances;

    function withdraw(uint256 amount) external {
        // Check ✅
        require(balances[msg.sender] >= amount);

        // Interaction ← WRONG POSITION!
        (bool ok,) = msg.sender.call{
            value: amount
        }("");
        require(ok);

        // Effect ← TOO LATE!
        balances[msg.sender] -= amount;
        // Attacker re-entered before this!
        // Balance never decremented in time!
    }
}

// ✅ CORRECT ORDER (CEI):
contract CorrectCEI {
    mapping(address => uint256) balances;

    function withdraw(uint256 amount) external {

        // ─── CHECKS ───────────────────────
        require(amount > 0, "Zero amount!");
        require(
            balances[msg.sender] >= amount,
            "Insufficient!"
        );

        // ─── EFFECTS ──────────────────────
        // State update PEHLE!
        balances[msg.sender] -= amount;
        // Ab re-entry karo toh:
        // balance = 0 → require fail!

        // ─── INTERACTIONS ─────────────────
        // External call BAAD MEIN!
        (bool ok,) = msg.sender.call{
            value: amount
        }("");
        require(ok, "Transfer failed!");
    }
}

PART 7: ReentrancyGuard Defense in Depth!

// ReentrancyGuard = Mutex lock for contracts
// Ek function execute ho raha hai →
// Same function dobara call → REVERT!

// OpenZeppelin implementation:
abstract contract ReentrancyGuard {

    // Status values:
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = NOT_ENTERED;
    }

    modifier nonReentrant() {
        // Check: Already entered?
        require(
            _status != ENTERED,
            "ReentrancyGuard: reentrant call"
        );

        // Set: Entered!
        _status = ENTERED;

        _;  // Function body

        // Reset: Not entered!
        _status = NOT_ENTERED;
    }
}

// Usage:
contract SecureVault is ReentrancyGuard {

    mapping(address => uint256) balances;

    function withdraw(uint256 amount)
        external
        nonReentrant  // ← One call at a time!
    {
        require(balances[msg.sender] >= amount);
        balances[msg.sender] -= amount;

        (bool ok,) = msg.sender.call{
            value: amount
        }("");
        require(ok);
    }
}

// ⚠️ ReentrancyGuard bypass:
// Cross-function reentrancy!

contract CrossReentrancy is ReentrancyGuard {

    mapping(address => uint256) balances;
    mapping(address => bool) public locked;

    // nonReentrant protect karta hai
    // Same function ko!
    // Lekin DIFFERENT function?

    function withdrawA(uint256 amount)
        external
        nonReentrant
    {
        require(balances[msg.sender] >= amount);
        balances[msg.sender] -= amount;
        // External call:
        msg.sender.call{value: amount}("");
        // Attacker re-enters withdrawB!
    }

    function withdrawB(uint256 amount)
        external
        nonReentrant
        // ← Different nonReentrant status!
        // withdrawA ka ENTERED alag hai!
        // withdrawB ka apna fresh status hai!
    {
        // ⚠️ Balance already decremented in A
        // But if logic here is different...
        require(balances[msg.sender] >= amount);
        balances[msg.sender] -= amount;
        // Double withdrawal possible in some cases!
    }
}

// ✅ Complete Defense:
// 1. CEI pattern use karo
// 2. nonReentrant lagao
// 3. Cross-function bhi consider karo!

PART 8: Gas Optimization Patterns!

contract GasOptimization {

    // ─── Tip 1: uint256 > uint8 ───────────
    // Counterintuitive! uint8 MORE gas in some cases!
    uint8  public small;   // More gas! (padding needed)
    uint256 public large;  // Less gas in isolation!

    // ─── Tip 2: Storage Packing ───────────
    // WRONG: 3 slots!
    uint256 a;  // slot 0
    uint8   b;  // slot 1 (wasted space!)
    uint256 c;  // slot 2

    // CORRECT: 2 slots!
    uint256 d;  // slot 0
    uint8   e;  // slot 1 (packed!)
    uint8   f;  // slot 1 (same slot!)
    uint8   g;  // slot 1 (same slot!)
    uint256 h;  // slot 2

    // ─── Tip 3: Memory vs Storage ─────────
    function expensive(uint256[] storage arr)
        internal view
        returns (uint256 sum)
    {
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i]; // SLOAD every time!
        }
    }

    function cheap(uint256[] memory arr)
        internal pure
        returns (uint256 sum)
    {
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i]; // MLOAD — cheaper!
        }
    }

    // ─── Tip 4: Cache Array Length ────────
    // WRONG:
    function wrongLoop(uint256[] memory arr)
        external pure
    {
        for (uint i = 0; i < arr.length; i++) {
            // arr.length har iteration compute!
        }
    }

    // CORRECT:
    function rightLoop(uint256[] memory arr)
        external pure
    {
        uint256 len = arr.length; // Cache once!
        for (uint i = 0; i < len; i++) {
            // len = just a variable read
        }
    }

    // ─── Tip 5: Immutable vs Constant ─────
    address public constant CONST_ADDR =
        address(0x123);  // Compile-time replacement
    address public immutable IMMUT_ADDR;
    // Deploy-time — cheaper than storage!

    // ─── Tip 6: Custom Errors > Strings ───
    // WRONG: String error message
    function wrongError() external pure {
        require(false, "This is a long error!");
        // String stored in bytecode = Size + Gas!
    }

    // CORRECT: Custom error
    error MyError();
    function rightError() external pure {
        revert MyError();
        // Just 4 bytes selector!
    }

    // ─── Tip 7: Events > Storage ──────────
    // Historical data ke liye events use karo
    // Events = Logs = Cheaper than storage!
    // Storage: 20,000 gas (new slot)
    // Event:   375+ gas

    // ─── Tip 8: Short-circuit evaluation ──
    function shortCircuit(
        uint256 x,
        address addr
    ) external view returns (bool) {
        // Cheap check PEHLE!
        return x > 0 &&
               addr != address(0) &&
               addr.code.length == 0;
               // Expensive check BAAD MEIN!
    }
}

PART 9: Complete Pattern Production Ready Contract!

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title AdvancedVault
/// @notice All advanced patterns applied!
/// @dev CEI + ReentrancyGuard + Assembly optimize

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract AdvancedVault is
    ReentrancyGuard,
    Ownable
{
    // ─── Storage (Optimized Packing) ──────
    address public immutable token;      // 20 bytes
    bool    public paused;               // 1 byte  ↕ Packed!
    uint96  public totalFees;            // 12 bytes ↕ Packed!
    // Slot 0: token(20) + paused(1) + totalFees(12) = 33? No!
    // Actually: address = 20 bytes, next slot

    uint256 public totalDeposited;
    uint256 public feeRate; // basis points (100 = 1%)

    mapping(address => uint256) private _balances;
    mapping(address => uint256) private _lastAction;

    // ─── Events ───────────────────────────
    event Deposited(
        address indexed user,
        uint256 amount,
        uint256 fee
    );
    event Withdrawn(
        address indexed user,
        uint256 amount
    );

    // ─── Custom Errors ────────────────────
    error Paused();
    error ZeroAmount();
    error ZeroAddress();
    error InsufficientBalance(
        uint256 have,
        uint256 need
    );
    error CooldownActive(uint256 remaining);
    error InvalidFeeRate(uint256 rate);

    // ─── Constants ────────────────────────
    uint256 public constant MAX_FEE_RATE = 1000; // 10%
    uint256 public constant COOLDOWN = 1 minutes;
    uint256 private constant FEE_DENOMINATOR = 10000;

    // ─── Modifiers ────────────────────────
    modifier whenActive() {
        if (paused) revert Paused();
        _;
    }

    modifier cooldownPassed() {
        uint256 lastAction = _lastAction[msg.sender];
        if (lastAction != 0) {
            uint256 elapsed = block.timestamp - lastAction;
            if (elapsed < COOLDOWN) {
                revert CooldownActive(COOLDOWN - elapsed);
            }
        }
        _;
    }

    // ─── Constructor ──────────────────────
    constructor(
        address _token,
        uint256 _feeRate
    ) Ownable(msg.sender) {
        if (_token == address(0)) revert ZeroAddress();
        if (_feeRate > MAX_FEE_RATE)
            revert InvalidFeeRate(_feeRate);

        token   = _token;
        feeRate = _feeRate;
    }

    // ─── Core Functions ───────────────────

    function deposit(uint256 amount)
        external
        whenActive
        nonReentrant
        cooldownPassed
    {
        // ─── CHECKS ───────────────────────
        if (amount == 0) revert ZeroAmount();

        // ─── EFFECTS ──────────────────────
        uint256 fee = (amount * feeRate) /
                       FEE_DENOMINATOR;
        uint256 netAmount = amount - fee;

        _balances[msg.sender] += netAmount;
        totalDeposited        += netAmount;
        totalFees             += uint96(fee);
        _lastAction[msg.sender] = block.timestamp;

        // ─── INTERACTIONS ─────────────────
        _safeTransferFrom(
            token,
            msg.sender,
            address(this),
            amount
        );

        emit Deposited(msg.sender, netAmount, fee);
    }

    function withdraw(uint256 amount)
        external
        whenActive
        nonReentrant
    {
        // ─── CHECKS ───────────────────────
        if (amount == 0) revert ZeroAmount();

        uint256 bal = _balances[msg.sender];
        if (bal < amount)
            revert InsufficientBalance(bal, amount);

        // ─── EFFECTS ──────────────────────
        _balances[msg.sender] -= amount;
        totalDeposited        -= amount;

        // ─── INTERACTIONS ─────────────────
        _safeTransfer(token, msg.sender, amount);

        emit Withdrawn(msg.sender, amount);
    }

    // ─── Internal Helpers ─────────────────

    function _safeTransfer(
        address _token,
        address to,
        uint256 amount
    ) internal {
        (bool ok, bytes memory data) =
            _token.call(
                abi.encodeWithSignature(
                    "transfer(address,uint256)",
                    to,
                    amount
                )
            );
        require(
            ok && (data.length == 0 ||
                   abi.decode(data, (bool))),
            "Transfer failed!"
        );
    }

    function _safeTransferFrom(
        address _token,
        address from,
        address to,
        uint256 amount
    ) internal {
        (bool ok, bytes memory data) =
            _token.call(
                abi.encodeWithSignature(
                    "transferFrom(address,address,uint256)",
                    from,
                    to,
                    amount
                )
            );
        require(
            ok && (data.length == 0 ||
                   abi.decode(data, (bool))),
            "TransferFrom failed!"
        );
    }

    // ─── View Functions ───────────────────
    function balanceOf(address user)
        external view returns (uint256)
    {
        return _balances[user];
    }

    // ─── Admin ────────────────────────────
    function setPause(bool _paused)
        external onlyOwner
    {
        paused = _paused;
    }

    function setFeeRate(uint256 _feeRate)
        external onlyOwner
    {
        if (_feeRate > MAX_FEE_RATE)
            revert InvalidFeeRate(_feeRate);
        feeRate = _feeRate;
    }
}

PART 10: Final Bug Hunt Challenge!

// ─── 6 bugs dhundho! ──────────────────────

contract AdvancedBuggy {

    mapping(address => uint256) balances;
    address public implementation;
    bool private locked;

    // Bug #1:
    receive() external {
        balances[msg.sender] += msg.value;
    }

    // Bug #2:
    function delegateTo(bytes memory data)
        external
    {
        implementation.delegatecall(data);
    }

    // Bug #3:
    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount);
        (bool ok,) = msg.sender.call{
            value: amount
        }("");
        require(ok);
        balances[msg.sender] -= amount;
    }

    // Bug #4:
    function getHash(
        address user,
        uint256 amount
    ) public pure returns (bytes32) {
        return keccak256(
            abi.encodePacked(user, amount)
        );
    }

    // Bug #5:
    modifier noReentrant() {
        require(!locked, "Locked!");
        locked = true;
        _;
    }

    // Bug #6:
    function create2Deploy(
        bytes32 salt,
        bytes memory code
    ) external returns (address addr) {
        assembly {
            addr := create2(
                0,
                add(code, 0x20),
                mload(code),
                salt
            )
        }
    }
}

// ─── ANSWERS ──────────────────────────────
// Bug #1: receive() not payable!
//   ETH receive karna hai → payable zaroori!
//   receive() external payable { ... }

// Bug #2: Arbitrary delegatecall!
//   Koi bhi implementation set kar sakta hai
//   + Koi bhi data pass kar sakta hai
//   = Storage corruption + owner takeover!
//   Fix: onlyOwner + whitelist allowed calls!

// Bug #3: CEI violation!
//   External call pehle → State update baad
//   Reentrancy attack possible!
//   Fix: balances -= amount PEHLE!

// Bug #4: encodePacked collision!
//   Dynamic types ke saath collision possible
//   abi.encode() use karo!
//   Plus: No chainId, no nonce = Replay attack!

// Bug #5: nonReentrant lock reset missing!
//   locked = true set kiya
//   Lekin function ke baad locked = false
//   KABHI NAHI HOGA!
//   Contract permanently locked after first call!
//   Fix: locked = false; after _;

// Bug #6: create2Deploy access control missing!
//   Koi bhi arbitrary code deploy kar sakta hai
//   Factory contract ke naam pe!
//   Trust issue + Potential attack surface
//   Fix: onlyOwner modifier!

Quick Revision

📥 receive()     = Pure ETH receive (no data)
                   MUST be payable!
📥 fallback()    = Fallback (data ya no receive)
                   Reentrancy entry point! ⚠️

📞 .call()       = ETH + function, return check karo!
📞 .delegatecall= Code borrow, caller storage ⚠️
📞 .staticcall() = Read only, safe!

⚙️  Assembly      = Direct EVM access
                   isContract bypass possible! ⚠️
                   Storage arbitrary write! ⚠️

🔡 abi.encode    = Padded, safe, collision-free ✅
🔡 encodePacked  = Compact, collision possible ⚠️
                   Dynamic types ke saath dangerous!

🏭 CREATE        = Nonce-based address
🏭 CREATE2       = Salt-based deterministic address
                   Metamorphic contract attack! ⚠️

🛡️  CEI Pattern   = Checks → Effects → Interactions
                   MOST IMPORTANT security pattern!

🔒 ReentrancyGuard= Mutex lock
                   Cross-function bypass possible! ⚠️

⛽ Gas Tips:
   Storage pack karo
   Custom errors use karo
   Array length cache karo
   Immutable/Constant prefer karo

Meri Baat…

Teen articles mein Solidity cover ki। Ab tum jaante ho:

Article #5: Variables, Functions, Types
Article #6: OOP, Inheritance, Interfaces
Article #7: Advanced Patterns, Assembly, CEI

Yeh sab combine karo:

Ek Solidity contract open karo →
   → Variables dekho (overflow possible?)
   → Functions dekho (modifier missing?)
   → Inheritance chain dekho (bypass?)
   → receive/fallback dekho (reentrancy?)
   → Low-level calls dekho (CEI?)
   → Assembly dekho (storage write?)
   → ABI encoding dekho (collision?)

Yeh hai ek BASIC audit checklist!

Real auditors yahi karte hain —
Sirf zyada systematically!

Tumne 7 articles mein woh seekha
jo bahut log 6 months mein seekhte hain!

Agle phase: TOOLS!
Foundry se exploit likhenge —
Real contracts pe test karenge —
Automated bugs dhundhenge!

Taiyar ho? 🚀

Article #8 mein: Smart Contract Lifecycle Deploy se Exploit tak poora journey! ⚡

HackerMD Web3 Security Researcher GitHub: BotGJ16 | Medium: @HackerMD

Previous: Article #6 Solidity OOP & Inheritance Next: Article #8 Smart Contract Lifecycle

#Solidity #AdvancedPatterns #CEI #Reentrancy #Web3Security #BugBounty #Hinglish #HackerMD


메타데이터
post_id
9f9e8a946baa
slug
solidity-basicspart-3-advanced-patterns-9f9e8a946baa
url
https://medium.com/@HackerMD/solidity-basicspart-3-advanced-patterns-9f9e8a946baa
canonical_url
https://medium.com/@HackerMD/solidity-basicspart-3-advanced-patterns-9f9e8a946baa
author_url
https://medium.com/@HackerMD
status
ok
fetched_at
2026-06-16 19:09:56