> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/web3pentesting/1.-principles-of-smart-contract-design.md).

# 1. Principles of Smart Contract Design

### Table of Contents

1. Why Design Comes Before Security
2. Principle 1 - Minimize Code Complexity
3. Principle 2 - Optimize Storage Variables
4. Principle 3 - Offload Logic Off-Chain
5. Principle 4 - Avoid Parallel Data Structures
6. Principle 5 - Use Standardized Libraries
7. Putting It All Together - The Design Philosophy
8. Full Chapter Recap Table

***

### 1. Why Design Comes Before Security

Before you can find bugs (which is what most people picture when they hear "Web3 security"), you have to understand how a contract *should* be designed in the first place. That's what this chapter is about - not exploits yet, just the philosophy that prevents most exploits from ever existing.

The core reason design matters so much more here than in normal software:

```
TRADITIONAL SOFTWARE                          SMART CONTRACTS
--------------------------------------         --------------------------------------
Deployed to a server you control       Deployed to a blockchain nobody controls
Can be patched/hotfixed instantly      Usually immutable once deployed
A bug might leak data or crash a page  A bug can directly drain real money
Rollback is possible (backups, etc.)   Transactions are permanent, no rollback
Users trust your company's servers     Users trust only the code itself
```

Because you often cannot "just push a fix," the entire mindset shifts: you want to prevent classes of bugs from being possible at all, using design decisions made before a single exploit is even considered. That is the whole point of this chapter's five principles.

***

### 2. Principle 1 - Minimize Code Complexity

#### The core statement

The instructor's phrase is to follow an ideology of having as little code as possible. This isn't about being lazy - it's a deliberate strategy. Every additional line of code is a line that:

* Has to be written correctly
* Has to be reviewed correctly (by you and by auditors)
* Has to be tested correctly
* Can interact unexpectedly with every other line of code in the contract

#### Why "attack surface" is the key concept here

"Attack surface" means the total set of entry points and logic paths an attacker could try to abuse. Think of your contract as a building. Every public/external function is effectively a door. Every internal branch (if/else, loop, external call) is a hallway inside that building an attacker could get lost in - or exploit.

```
                         ATTACK SURFACE COMPARISON

  SMALL, SIMPLE CONTRACT                  LARGE, COMPLEX CONTRACT
  =========================              ===========================

   ______________                         _________________________
  |              |                       |                         |
  |   [deposit]  |<--- door 1            |  [deposit]  [withdraw]   |
  |   [withdraw] |<--- door 2            |  [stake]    [unstake]    |
  |______________|                       |  [claim]    [migrate]    |
                                          |  [vote]     [delegate]   |
   Two entry points.                     |  [pause]    [upgrade]    |
   Two things to review.                 |  [rescue]   [flashLoan]  |
   Two things that can break.            |_________________________|

                                           Ten entry points, each one
                                           calling 3-4 internal helper
                                           functions, each one touching
                                           shared state. An auditor (or
                                           you) has to mentally trace
                                           every possible path through
                                           this maze to be sure nothing
                                           is exploitable.
```

The relationship is roughly: lines of code up leads to possible interactions up combinatorially, which leads to probability of a missed edge case up. It is not linear. Ten functions that can call each other don't create ten problems, they create potentially dozens of *combinations* of state that have to each be reasoned about.

#### A concrete example

```solidity
// MORE COMPLEX - harder to reason about
function withdraw(uint amount, bool useBonus, address referrer) external {
    if (useBonus && bonusEnabled[msg.sender]) {
        amount = applyBonus(amount, referrer);
    }
    if (referrer != address(0)) {
        payReferrerFee(referrer, amount);
    }
    _withdraw(msg.sender, amount);
}

// SIMPLER - one clear job, fewer paths to audit
function withdraw(uint amount) external {
    _withdraw(msg.sender, amount);
}
```

The first version has four different execution paths depending on `useBonus` and `referrer`. Every one of those paths needs to be independently checked for reentrancy, overflow, and logic errors. The second version has one path.

#### Beginner takeaway

Before adding a feature, ask: is this feature essential to the protocol's core purpose, or is it a "nice to have" that adds a new door to guard forever?

***

### 3. Principle 2 - Optimize Storage Variables

#### The core statement

Be extremely picky about your storage variable layout. Avoid "extra cruft," meaning superfluous, unused, or duplicate storage variables. A clean storage layout is described as laying a solid foundation for the rest of the contract - it reduces both complexity and gas costs.

#### Why storage is different from a normal variable

In Solidity, "storage" variables are written permanently to the blockchain (as opposed to "memory," which is temporary and disappears after the function call ends). Every storage variable:

* Costs gas to write and to update (this is real money for users)
* Persists forever, so if it's wrong, it stays wrong until someone fixes it
* Is a piece of state that *every function which touches it* must keep consistent

#### A large diagram of what "cruft" actually looks like

```
                         A CONTRACT'S STORAGE LAYOUT

  BEFORE CLEANUP (bloated / "cruft")             AFTER CLEANUP (lean)
  ======================================        =========================

  slot 0: address owner                          slot 0: address owner
  slot 1: uint256 balance                        slot 1: uint256 balance
  slot 2: uint256 oldBalance     <- unused        slot 2: mapping(address => uint256) balances
  slot 3: bool isPaused
  slot 4: bool isPausedV2        <- duplicate
  slot 5: address backupOwner    <- never read
  slot 6: uint256 tempCalc       <- leftover from
                                    a removed feature
  slot 7: mapping(address => uint256) balances

  Every one of these extra slots:                 Every function that touches
   - costs gas on every write                      state has fewer things to
   - is a variable some future                     keep synchronized. Fewer
     function might read from by                   invariants to maintain.
     mistake, using stale data                      Fewer places for a bug
   - has to be accounted for in                     to hide.
     every single security review
```

#### Why this matters for security specifically, not just cost

Imagine `isPaused` and `isPausedV2` both exist because of a rushed patch. A function checks `isPaused`, but the actual pause logic elsewhere in the contract sets `isPausedV2`. Now the contract believes it's paused when it functionally isn't - a direct security bypass, caused entirely by sloppy storage layout, with no "hacking" involved at all.

#### Beginner takeaway

Before adding a storage variable, ask: is there already a variable that represents this same fact? Will this variable be correctly updated by *every single function* that changes related state, for the entire lifetime of the contract?

***

### 4. Principle 3 - Offload Logic Off-Chain

#### The core statement

Evaluate how much logic can be handled off-chain. If something does not absolutely need to happen on-chain, remove it from the smart contract and let the front end or "keepers" (automated bots that call your contract on a schedule or condition) handle it instead.

#### On-chain vs off-chain, explained fully

"On-chain" means the computation happens as part of a blockchain transaction - it's verified by every node in the network, it's permanent, and it costs gas proportional to how much computation it does. "Off-chain" means the computation happens somewhere else entirely: a website's JavaScript, a backend server, or an automated bot - and only the *final result* gets sent to the blockchain.

```
                    ON-CHAIN vs OFF-CHAIN DECISION FLOW

        Does this computation determine who owns/controls funds,
        or does it need to be trustlessly verifiable by anyone?
                                  |
                +-----------------+-----------------+
                |                                   |
              YES                                  NO
                |                                   |
                v                                   v
      KEEP IT ON-CHAIN                     MOVE IT OFF-CHAIN
   ------------------------            ------------------------------
   - balance transfers                  - sorting a leaderboard
   - ownership checks                   - computing display values / formatting
   - core invariant math                - price lookups that get verified
     (e.g. collateral ratio)              on-chain anyway via oracle
   - access control                     - deciding *when* a public function
                                           should be called (a keeper bot
                                           can watch conditions and call it,
                                           instead of the contract polling
                                           itself, which isn't even possible)

                    RESULT:
   ___________________________________________________________
  |                                                             |
  |   FRONT END / KEEPER BOT           SMART CONTRACT           |
  |   (off-chain, cheap, flexible)     (on-chain, expensive,    |
  |                                     permanent, trust-critical)|
  |                                                             |
  |   1. Watches conditions      ---->  2. Executes ONLY the    |
  |   2. Computes intermediate          minimal state-changing  |
  |      values                         function, e.g.:         |
  |   3. Calls the contract's           settle(finalAmount)     |
  |      minimal entrypoint                                     |
  |______________________________________________________________|
```

#### Why this reduces risk, not just cost

Every calculation you move on-chain becomes part of the immutable, security-critical surface area described in Principle 1. A sorting algorithm running inside a smart contract isn't just expensive - it's also code that could contain a subtle bug, and unlike a website's JavaScript, you usually can't hotfix it. Doing the same sorting off-chain and just submitting the final, already-computed result to the contract means the contract only has to *verify* the result (cheap, small code), not *compute* it (expensive, larger code).

#### Beginner takeaway

For every piece of logic in your contract, ask: does this need the blockchain's trustless guarantees, or could a front end/bot do this and simply hand the contract a final answer to check?

***

### 5. Principle 4 - Avoid Parallel Data Structures

#### The core statement

A specific pitfall to avoid is maintaining two different data structures that track the same underlying state. This tends to cause state mismatches, which can turn into vulnerabilities.

#### A large, detailed diagram of the failure mode

```
                    THE PARALLEL DATA STRUCTURE PROBLEM

  Contract keeps balances in a mapping AND a mirrored array,
  intending them to always represent the same information:

     mapping(address => uint256) balances;
     address[] allHolders;         <- meant to "mirror" balances

  ------------------------- TIME STEP 1 -------------------------
  Alice deposits 100.

     balances:  { Alice: 100 }
     allHolders: [ Alice ]                    IN SYNC (still correct)

  ------------------------- TIME STEP 2 -------------------------
  Bob deposits 50. Developer forgets to push Bob into allHolders
  in one specific code path (e.g. a "deposit via referral" function
  that was added later and reused the balance-update logic, but
  not the array-update logic).

     balances:  { Alice: 100, Bob: 50 }
     allHolders: [ Alice ]                    OUT OF SYNC
                                               Bob is missing!

  ------------------------- TIME STEP 3 -------------------------
  Some other function relies on allHolders to decide who gets
  a reward, an airdrop, or is included in a snapshot calculation.

     rewardAll() iterates allHolders -> Bob is silently skipped,
     even though the mapping clearly shows he has a balance.

     Depending on the exact logic, this mismatch can also be
     abused in the OTHER direction - an attacker manipulating
     one structure without triggering the corresponding update
     in the other, to claim rewards twice, bypass a check, or
     read a stale value that a security check depends on.
```

```
                         THE FIX: SINGLE SOURCE OF TRUTH

     mapping(address => uint256) balances;
        (this is the ONLY place this fact lives)

     If you need a list of holders for some off-chain purpose
     (like Principle 3's off-chain logic idea), get it by reading
     events/logs off-chain, or by re-deriving it when needed  - 
     don't store a second on-chain structure just to duplicate
     what the mapping already tells you.
```

#### Why this is subtle and dangerous

This isn't a typo bug you'll catch by reading the code once. It's a *structural* bug: the two data structures are correct on day one, and only diverge later, often through an edge case, an upgrade, or a function nobody thought to update. That's exactly the kind of bug that slips through code review and only gets found (sometimes by an attacker) much later.

#### Beginner takeaway

If you find yourself writing an update to one storage variable, immediately ask: is there another variable somewhere that is supposed to represent this same fact, and did I just forget to update it too? If the answer involves yes, there's another one, the better fix is usually to delete the duplicate entirely.

***

### 6. Principle 5 - Use Standardized Libraries

#### The core statement

Leverage audited, pre-written libraries such as OpenZeppelin whenever possible, instead of writing your own implementation of common, security-critical functionality.

#### Why "don't reinvent the wheel" matters more here than in normal programming

In most software, writing your own version of a common utility is a minor inefficiency. In smart contracts, writing your own version of something like a token standard, access control, or reentrancy protection means you are personally responsible for correctly implementing something that has historically been the source of some of the largest hacks in the industry's history (integer overflow bugs, reentrancy bugs, incorrect access control checks, and so on).

```
                    CUSTOM CODE vs AUDITED LIBRARY

  WRITING YOUR OWN ACCESS CONTROL           USING OpenZeppelin's Ownable

  contract MyContract {                     import "@openzeppelin/contracts
      address public owner;                       /access/Ownable.sol";

      modifier onlyOwner() {                 contract MyContract is Ownable {
          require(msg.sender == owner);          // owner logic, transferOwnership,
          _;                                      // renounceOwnership, and the
      }                                           // onlyOwner modifier are all
                                                   // already written, tested, and
      // Did you remember to:                     // reviewed by thousands of
      //  - emit an event on transfer?             // developers across thousands
      //  - handle transferring to                 // of production deployments.
      //    address(0) safely?
      //  - protect against a
      //    reentrancy edge case if
      //    ownership changes mid-call?
  }

      Every one of these questions                Already answered, already
      is now YOUR responsibility to                battle-tested. You inherit
      get right, and get right on                  the security review work of
      the FIRST try, since it's                    the entire library's history,
      likely immutable once live.                  instead of starting from zero.
```

#### What kinds of things libraries typically cover

* Token standards (ERC-20, ERC-721, ERC-1155) - the exact interface and edge-case behavior expected by wallets, exchanges, and other contracts
* Access control (Ownable, role-based permissions)
* Safe math (less critical since Solidity 0.8+ has built-in overflow checks, but still relevant for older code)
* Reentrancy guards
* Pausable contracts (emergency stop patterns)

#### The trade-off to be aware of

Using a library isn't a free pass - you still need to understand what it does, since misusing even an audited library incorrectly can reintroduce exactly the same bugs it was meant to prevent. The point is you are no longer responsible for writing the *low-level* logic yourself, only for wiring it together correctly.

#### Beginner takeaway

Before writing custom logic for something common (ownership, tokens, pausing, reentrancy protection), check whether an established, audited library already solves it.

***

### 7. Putting It All Together - The Design Philosophy

All five principles are really one philosophy expressed five different ways: brainstorm thoroughly to find the simplest implementation that still satisfies the protocol's actual requirements, instead of over-engineering.

```
                    THE FULL DESIGN PHILOSOPHY, VISUALIZED

                         Start: "What does this protocol
                                 actually need to do?"
                                       |
                                       v
                  Brainstorm multiple possible implementations
                                       |
                                       v
        For each candidate design, apply all five filters below:
        __________________________________________________________
       |                                                            |
       |  FILTER 1: Can any code be removed and still work?         |
       |            -> Principle 1: Minimize Code Complexity        |
       |                                                            |
       |  FILTER 2: Can any storage variable be removed, merged,    |
       |            or avoided entirely?                            |
       |            -> Principle 2: Optimize Storage Variables      |
       |                                                            |
       |  FILTER 3: Does this logic truly need to run on-chain?     |
       |            -> Principle 3: Offload Logic Off-Chain         |
       |                                                            |
       |  FILTER 4: Does this duplicate a fact tracked elsewhere?   |
       |            -> Principle 4: Avoid Parallel Data Structures  |
       |                                                            |
       |  FILTER 5: Does an audited library already solve this?     |
       |            -> Principle 5: Use Standardized Libraries      |
       |____________________________________________________________|
                                       |
                                       v
                 The design that survives all five filters
                    is smaller, cheaper, and has fewer
                       places for vulnerabilities to hide
                                       |
                                       v
                        Secure, maintainable contract
```

The opposite mindset - adding features because they seem powerful, tracking extra data just in case, or writing custom implementations to feel more in control - is exactly what the instructor calls over-engineering, and it is the root cause behind a large share of real-world smart contract exploits.

***

### 8. Full Chapter Recap Table

| # | Principle                      | What it means                                    | What goes wrong if ignored                                                                  |
| - | ------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| 1 | Minimize Code Complexity       | Write only the code strictly needed              | Larger attack surface, more paths to audit, more places for logic bugs                      |
| 2 | Optimize Storage Variables     | Keep storage lean, no unused/duplicate variables | Wasted gas, inconsistent state, security checks reading stale data                          |
| 3 | Offload Logic Off-Chain        | Only put trust-critical logic on-chain           | Expensive, immutable, larger code surface for things that didn't need blockchain guarantees |
| 4 | Avoid Parallel Data Structures | One single source of truth per fact              | Structures fall out of sync, leading to double-counting, missed updates, or bypassed checks |
| 5 | Use Standardized Libraries     | Reuse audited code like OpenZeppelin             | Reinventing security-critical logic and reintroducing already-solved bugs                   |

#### The one question to carry into every future chapter

What is the simplest possible design that still fully satisfies what this protocol needs to do?

Every principle in this chapter is a different lens for answering that same question.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/web3pentesting/1.-principles-of-smart-contract-design.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
