🧱 Solidity Data Structures & Core Patterns

The Complete Beginner's Reference + Tutorial

Version: Solidity ^0.8.20 | Reading Time: 50 minutes | Prerequisites: None

Value Types Reference Types

📋 TABLE OF CONTENTS

  1. Data Types: Value vs Reference
  2. Data Locations Explained
  3. Complex Data Structures
  4. All Solidity Keywords
  5. Core Security Patterns
  6. The Complete TaskManager Contract
  7. Common Beginner Mistakes
  8. Quick Reference Cheat Sheet

PART 1: DATA TYPES - VALUE VS REFERENCE

📊 Fundamental Distinction

Solidity has two fundamental categories of data types with completely different behaviors:

Category Stored Passed By Modification Behavior Data Location Needed?
Value Types Stack (cheap) Value (copy) Modifying copy doesn't affect original ❌ No
Reference Types Storage/Memory/Calldata Reference (pointer) Modifying reference affects all pointers ✅ Yes

1.1 Value Types (Elementary/Primitive)

Value types are the basic building blocks. They are always copied when assigned or passed to functions.

Boolean

Boolean.sol
bool public isActive = true;
bool public isPaused = false;

// Operations
bool result = true && false;  // false
bool result2 = true || false; // true
bool result3 = !true;         // false

Key Point: bool is 1 byte, stored on stack, copied by value.

Integers (Signed & Unsigned)

// Unsigned integers (positive only) - VALUE TYPES
uint256 public count = 0;           // 32 bytes, 0 to 2^256-1
uint8 public small = 255;           // 1 byte, 0 to 255
uint16 public medium = 65535;       // 2 bytes
uint32 public large = 4294967295;   // 4 bytes

// Signed integers (positive & negative) - VALUE TYPES
int256 public temperature = -10;    // 32 bytes, -2^255 to 2^255-1
int8 public tiny = -128;            // 1 byte, -128 to 127

⚠️ CRITICAL: Always use explicit sizes (uint256 not uint) per official style guide.

Address

// Regular address - VALUE TYPE
address public wallet = 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb;

// Payable address (can receive ETH) - VALUE TYPE
address payable public recipient;

// Special addresses
address public caller = msg.sender;           // Who called function
address public thisContract = address(this);  // This contract

Key Point: address is 20 bytes, always copied by value.

Fixed-Size Byte Types (bytes1-32) - VALUE TYPES!

bytes1 public singleByte = 0x01;      // 1 byte - VALUE TYPE
bytes2 public twoBytes = 0x1234;      // 2 bytes - VALUE TYPE
bytes4 public selector;               // 4 bytes - VALUE TYPE
bytes8 public shortHash;                // 8 bytes - VALUE TYPE
bytes32 public fullHash;                // 32 bytes - VALUE TYPE

// Common use: function selectors
bytes4 public transferSelector = bytes4(keccak256("transfer(address,uint256)"));

Key Point: bytes1 through bytes32 are value types - fixed size, stored on stack.

Enum - VALUE TYPE

enum Status { Pending, Active, Completed, Cancelled }  // VALUE TYPE
enum Priority { Low, Medium, High, Urgent }            // VALUE TYPE

Status public currentStatus = Status.Pending;
Priority public taskPriority = Priority.High;

// Internally stored as uint8
uint8 statusNumber = uint8(Status.Active);  // 1

Key Point: Enums are internally uint8, making them value types.

1.2 Reference Types (Complex Types)

Reference types store a pointer/address to data. Multiple variables can point to the same data. Must specify data location (storage/memory/calldata).

Dynamic Bytes Array - REFERENCE TYPE!

bytes public data;  // Dynamic size - REFERENCE TYPE!

function manipulateBytes(bytes calldata input) external {
    // input is in calldata (read-only)
    // Can convert to memory if needed
    bytes memory copy = bytes(input);  // Explicit copy to memory
}

⚠️ CRITICAL DISTINCTION:
- bytes1 to bytes32 = Value types (fixed size)
- bytes (no number) = Reference type (dynamic array)

String - REFERENCE TYPE!

string public name = "Solidity";  // Dynamic UTF-8 - REFERENCE TYPE!

// Limitations:
// - Cannot get length directly
// - Cannot index characters directly
// - Expensive to manipulate

function getStringLength(string calldata str) external pure returns (uint256) {
    // Must convert to bytes to get length
    return bytes(str).length;
}

Key Point: string is essentially bytes with UTF-8 validation. Always a reference type.

Arrays - REFERENCE TYPES!

// Dynamic array - REFERENCE TYPE
uint256[] public numbers;
string[] public names;
address[] public participants;

// Fixed-size array - ALSO REFERENCE TYPE!
uint256[5] public fixedArray;
uint256[3] public initialized = [1, 2, 3];

Key Point: Both dynamic and fixed-size arrays are reference types requiring data location.

Struct - REFERENCE TYPE!

struct User {              // Defines a structure
    string name;           // string is reference
    uint256 age;           // uint256 is value
    address wallet;        // address is value
}

User public user;          // user is a reference type variable

function createUser(string calldata _name, uint256 _age) external {
    // Must specify storage or memory
    User memory newUser = User(_name, _age, msg.sender);  // memory
    user = newUser;  // Copy to storage
}

Key Point: Structs can contain both value and reference types. The struct itself is a reference type.

Mapping - REFERENCE TYPE!

// Mapping - always in storage, always reference type
mapping(address => uint256) public balances;
mapping(address => User) public users;
mapping(address => mapping(address => uint256)) public allowances;

Key Point: Mappings are always in storage. You cannot have a memory mapping.

1.3 Summary: Is It Value or Reference?

TypeCategorySizeStorageExample
bool✅ Value1 bitStacktrue
uint/int✅ Value8-256 bitsStackuint256
address✅ Value160 bitsStackmsg.sender
bytes1-32✅ Value1-32 bytesStackbytes32
enum✅ Value8 bitsStackStatus.Pending
bytes🔵 ReferenceDynamics/m/cbytes calldata
string🔵 ReferenceDynamics/m/cstring memory
T[]🔵 ReferenceDynamic/Fixeds/m/cuint256[] memory
struct🔵 ReferenceSum memberss/m/cUser storage
mapping🔵 ReferenceDynamicstorage onlymapping(k => v)

1.4 Practical Demonstration: Value vs Reference Behavior

contract TypeBehavior {
    // VALUE TYPE EXAMPLE
    function demonstrateValue() external pure returns (uint256, uint256) {
        uint256 a = 100;  // Value type
        uint256 b = a;    // b gets COPY of a (100)
        b = 999;          // Changing b does NOT affect a

        return (a, b);  // Returns (100, 999)
    }

    // REFERENCE TYPE EXAMPLE
    uint256[] public storageArray;

    function demonstrateReference() external {
        storageArray.push(100);
        storageArray.push(200);

        // Get reference to storage
        uint256[] storage ref = storageArray;
        ref[0] = 999;  // Changes storageArray[0] too!

        // Now storageArray is [999, 200]
    }

    function demonstrateMemoryCopy() external pure returns (uint256, uint256) {
        uint256[] memory arr1 = new uint256[](2);
        arr1[0] = 100;
        arr1[1] = 200;

        uint256[] memory arr2 = arr1;  // Copy, not reference!
        arr2[0] = 999;

        // arr1[0] is still 100 because memory creates copies
        return (arr1[0], arr2[0]);  // Returns (100, 999)
    }
}

PART 2: DATA LOCATIONS EXPLAINED

2.1 The Three Data Locations (For Reference Types Only!)

LocationPersistenceGas CostWhen to Use
storagePermanent💰💰💰 HighestState variables
memoryTemporary💰 CheapCalculations, return values
calldataTemporary✅ CheapestExternal inputs (read-only)

⚠️ Only reference types need data location! Value types are always on the stack.

2.2 Storage - The Blockchain Database

contract StorageExample {
    // These are automatically in storage (state variables)
    uint256[] public numbers;                    // storage
    mapping(address => uint256) public balances; // storage
    string public message;                       // storage
    bytes public data;                           // storage

    struct User {
        string name;
        uint256 age;
    }
    User public user;                            // storage

    function writeToStorage() external {
        // Writing to storage is expensive (~20,000 gas for SSTORE)
        numbers.push(100);  // storage write
        message = "Hello";  // storage write
    }
}

Key Point: State variables are always in storage. Writing to storage costs significant gas.

2.3 Memory - Temporary Workspace

contract MemoryExample {
    uint256[] public numbers;  // storage (state variable)

    function process(uint256[] memory tempArray) public pure returns (uint256) {
        // tempArray is in memory (temporary)
        // modifications here don't affect the original

        uint256 sum = 0;  // sum is on stack (value type)

        for (uint256 i = 0; i < tempArray.length; i++) {
            sum += tempArray[i];
        }

        // Create new memory array
        uint256[] memory newArray = new uint256[](3);
        newArray[0] = 100;
        newArray[1] = 200;
        newArray[2] = 300;

        return sum;  // All memory is cleared after function ends
    }

    function getNumbers() external view returns (uint256[] memory) {
        // Return storage array as memory copy
        return numbers;  // Copies from storage to memory
    }

    function demonstrateMemory() external pure returns (uint256, uint256) {
        uint256[] memory arr1 = new uint256[](3);
        arr1[0] = 100;
        uint256[] memory arr2 = arr1;
        arr2[0] = 999;
        return (arr1[0], arr2[0]);  // Returns (100, 999) - Copy!
    }
}

2.4 Calldata - Read-Only External Input

contract CalldataExample {
    // ✅ CORRECT: Use calldata for external function inputs
    function processData(string calldata input) external pure returns (bytes32) {
        // input is read-only, lives in calldata (cheapest)
        return keccak256(bytes(input));
    }

    // ❌ WRONG: memory costs more gas for external functions
    function processExpensive(string memory input) external pure returns (bytes32) {
        // This copies calldata to memory unnecessarily
        return keccak256(bytes(input));
    }

    // ✅ Array example with calldata
    function sumArray(uint256[] calldata arr) external pure returns (uint256) {
        uint256 sum = 0;
        for (uint256 i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
}

2.5 Visual Comparison: Memory vs Calldata

contract Comparison {
    function withMemory(string memory data) external pure returns (uint256) {
        // 1. data comes in as calldata (external function)
        // 2. Solidity copies it to memory
        // 3. You work with the memory copy
        // 4. Costs extra gas for the copy
        return bytes(data).length;
    }

    function withCalldata(string calldata data) external pure returns (uint256) {
        // 1. data stays in calldata
        // 2. You work directly with it
        // 3. No copy needed
        // 4. Cheapest option
        return bytes(data).length;
    }
}

Gas Savings: calldata can save 2000+ gas per string/array parameter compared to memory.

2.6 Decision Tree: When to Use What

contract DecisionTree {
    uint256[] public stateArray;  // storage (automatic)

    // Rule 1: State variables → storage (automatic)

    // Rule 2: External function params → calldata (cheapest)
    function externalFunc(uint256[] calldata input) external { }

    // Rule 3: Public function params → memory
    function publicFunc(uint256[] memory input) public { }

    // Rule 4: Return values → memory
    function getArray() external view returns (uint256[] memory) {
        return stateArray;
    }

    // Rule 5: Temporary variables → memory
    function tempArray() external pure {
        uint256[] memory temp = new uint256[](5);
    }

    // Rule 6: Value types → no location needed (always stack)
    function valueTypes(uint256 num, address addr) external pure { }
}

2.7 Common Mistakes with Data Locations

contract DataLocationMistakes {
    uint256[] public arr;

    // ❌ MISTAKE 1: Trying to assign storage to memory directly
    function mistake1() external view {
        uint256[] memory mem = arr;  // Creates copy, not reference
    }

    // ❌ MISTAKE 2: Returning storage reference
    function mistake2() external view returns (uint256[] storage) {
        // return arr;  // ❌ Can't return storage reference to external
    }

    // ✅ CORRECT: Return memory copy
    function correct2() external view returns (uint256[] memory) {
        return arr;  // Returns copy in memory
    }

    // ❌ MISTAKE 4: Forgetting that memory creates copies
    function mistake4() external pure returns (uint256[] memory) {
        uint256[] memory a = new uint256[](1);
        a[0] = 100;
        uint256[] memory b = a;
        b[0] = 999;
        return a;  // Returns [100], not [999]!
    }
}

PART 3: COMPLEX DATA STRUCTURES

3.1 Arrays (Reference Type)

uint256[] public numbers;           // Dynamic array
string[] public names;              // Array of strings

// Adding/removing
numbers.push(100);                  // Add to end
uint256 popped = numbers.pop();     // Remove last

// Access
uint256 length = numbers.length;    // Get size
numbers[0] = 999;                   // Update

// Delete (sets to zero, doesn't remove index)
delete numbers[0];  // numbers[0] is now 0, array length unchanged

Fixed-Size Arrays

uint256[5] public fixedArray;       // Always 5 elements
uint256[3] public initialized = [1, 2, 3];

// Cannot push/pop on fixed arrays
fixedArray[0] = 100;

3.2 The Array Problem: Looping is Expensive!

⚠️ WARNING: Looping through large arrays can exceed block gas limit (30M gas) and brick your contract!

BadArray.sol
contract BadArray {
    address[] public users;
    mapping(address => uint256) public balances;

    function distributeRewards() external {
        // DANGER: If users.length > 1000, this will fail!
        for (uint256 i = 0; i < users.length; i++) {
            balances[users[i]] += 1; // Each iteration costs ~50k gas
        }
    }
}

10,000 users × 50,000 gas = 500M gas ❌ EXCEEDS BLOCK LIMIT (30M)!

3.3 ✅ SOLUTIONS: Alternatives to Looping

Solution 1: Pull Instead of Push

contract PullPattern {
    mapping(address => uint256) public pendingRewards;

    function claimReward() external {
        uint256 amount = pendingRewards[msg.sender];
        require(amount > 0, "No reward");
        pendingRewards[msg.sender] = 0;
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Transfer failed");
    }
}

Benefits: Gas paid by claimer, unlimited users, safe.

Solution 2: Pagination (Batch Processing)

contract Pagination {
    address[] public users;
    uint256 public lastProcessedIndex;

    function processBatch(uint256 batchSize) external {
        uint256 end = lastProcessedIndex + batchSize;
        if (end > users.length) end = users.length;

        for (uint256 i = lastProcessedIndex; i < end; i++) {
            // Process users[i]
        }
        lastProcessedIndex = end;
    }
}

Solution 3: Merkle Proofs (For Airdrops)

contract MerkleAirdrop {
    bytes32 public merkleRoot;
    function claim(uint256 amount, bytes32[] calldata merkleProof) external {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
        require(verify(merkleProof, merkleRoot, leaf), "Invalid proof");
        // ... Logic
    }
}

Benefits: Handless millions of users, only 1 storage slot needed.

Solution 4: Off-Chain Computation

contract OffChainComputation {
    function updateBalances(address[] calldata users, uint256[] calldata balances, bytes calldata sig) external {
        require(verifySignature(users, balances, sig), "Invalid sig");
        for (uint256 i = 0; i < users.length; i++) {
            // ... Logic
        }
    }
}

3.4 Summary: When to Use Each Solution

ScenarioUsersSolutionWhy
Rewards< 100Direct loopSimple
Rewards100-1000Pull patternScalable
Rewards> 1000Merkle proofsMinimal storage
Data procAnyPaginationControlled gas
MathAnyOff-chainFree computation

3.5 Array Best Practices

contract ArrayBestPractices {
    // ✅ Track size manually for safety
    uint256 public userCount;
    mapping(uint256 => address) public userAtIndex;

    // ✅ Prefer mappings for lookups (O(1) vs O(n))
    mapping(address => bool) public isUser;

    // ❌ AVOID: Deleting middle elements (requires shifting)
    function removeMiddleExpensive(uint256 index) external {
        for (uint256 i = index; i < numbers.length - 1; i++) {
            numbers[i] = numbers[i + 1];
        }
        numbers.pop();
    }

    // ✅ BETTER: Swap with last and pop (O(1) but unordered)
    function removeFast(uint256 index) external {
        numbers[index] = numbers[numbers.length - 1];
        numbers.pop();
    }
}

3.6 Mappings (The Most Important Structure)

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

    // ❌ CANNOT: Check if key exists directly
    // Scores[user] != 0 is wrong if 0 is a valid score!

    // ✅ SOLUTION: Track existence separately
    mapping(address => bool) public hasScore;

    // ❌ CANNOT: Iterate over mappings
    // ✅ SOLUTION: Track keys in array
    address[] public allPlayers;
}

3.7 Structs with Reference and Value Types

contract StructExamples {
    struct Task {
        string description;     // Reference
        uint256 deadline;       // Value
        address assignee;       // Value
        bool completed;         // Value
    }

    Task[] public tasks;

    function updateTask(uint256 index) external {
        // Get storage reference (modifies original)
        Task storage task = tasks[index];
        task.completed = true;

        // vs memory (creates copy, doesn't save)
        Task memory taskCopy = tasks[index];
        taskCopy.completed = false;  // Changes copy only!
    }
}

PART 4: ALL SOLIDITY KEYWORDS

4.1 Value Type Keywords

bool, int, int8...int256, uint, uint8...uint256
address, address payable
bytes1, bytes2...bytes32, enum

4.2 Reference Type Keywords

bytes, string, T[], T[k], struct, mapping

⚠️ DISTINCTION: bytes32 is value type. bytes is reference type.

4.3 Function Keywords

function, view, pure, returns, return
virtual, override, modifier, constructor
receive, fallback

4.4 Visibility Keywords

public, private, internal, external

Order: visibilitymutabilityvirtualoverride.

4.5 Data Location Keywords

storage, memory, calldata

4.6 Control Flow Keywords

if, else, else if, for, while, do, break, continue, return, try, catch

4.7 Error Handling Keywords

require(cond, "msg");
assert(cond);
revert("msg");
revert CustomError();
error CustomError();

Modern Best Practice: Use custom errors with revert.

4.8 Contract & Inheritance Keywords

contract, abstract, interface, library, using, import, is

4.9 Special Variables (Global)

msg.sender, msg.value, msg.data, msg.sig
tx.origin, tx.gasprice, gasleft()
block.timestamp, block.number, block.difficulty, block.gaslimit, block.coinbase, blockhash()
address(this), this

4.10 Modifiers & 4.11 Units

constant, immutable
wei, gwei, szabo, finney, ether
seconds, minutes, hours, days, weeks

⚠️ Avoid "years" - doesn't account for leap years.

PART 5: CORE SECURITY PATTERNS

5.1 Access Control (Ownership)

contract ModernOwnable {
    address public immutable owner;
    error NotOwner();

    constructor() { owner = msg.sender; }

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }
}

immutable saves ~2,100 gas per read.

5.2 Reentrancy Guard

contract ReentrancyGuard {
    bool private _locked;
    error ReentrantCall();

    modifier nonReentrant() {
        if (_locked) revert ReentrantCall();
        _locked = true;
        _;
        _locked = false;
    }
}

5.3 Checks-Effects-Interactions (Critical!)

contract SafeWithdrawal {
    function withdraw(uint256 amount) external nonReentrant {
        // 1. CHECKS
        if (balances[msg.sender] < amount) revert Insufficient();

        // 2. EFFECTS (State updates FIRST)
        balances[msg.sender] -= amount;

        // 3. INTERACTIONS (External calls LAST)
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        if (!success) revert TransferFailed();
    }
}

5.4 Safe ETH Transfer & 5.6 Events

// ✅ MODERN: Use call() with reentrancy protection
(bool success, ) = to.call{value: amount}("");

// Events (Past Tense, SubjectVerb)
event OwnerUpdated(address indexed prev, address indexed next);

transfer() is limited to 2300 gas and can break with contract wallets.

PART 6: COMPLETE TASKMANAGER CONTRACT

TaskManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract TaskManager {
    enum Priority { Low, Medium, High, Urgent }
    enum Status { Pending, InProgress, Completed, Cancelled }

    struct Task {
        string description;
        uint256 deadline;
        Status status;
        Priority priority;
        address assignee;
        bool exists;
    }

    address public immutable owner;
    uint256 private _nextTaskId;
    mapping(uint256 => Task) private _tasks;
    mapping(address => uint256[]) private _userTaskIds;

    event TaskCreated(uint256 indexed taskId, address indexed creator, string description, uint256 deadline);
    event TaskStatusUpdated(uint256 indexed taskId, Status newStatus, address updater);
    event TaskAssigned(uint256 indexed taskId, address indexed newAssignee);

    error Unauthorized();
    error TaskNotFound(uint256 taskId);
    error InvalidDeadline();
    error InvalidStatusTransition();
    error ZeroAddress();

    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }

    modifier taskExists(uint256 taskId) {
        if (!_tasks[taskId].exists) revert TaskNotFound(taskId);
        _;
    }

    constructor() {
        owner = msg.sender;
        _nextTaskId = 1;
    }

    function createTask(string calldata description, uint256 deadline, Priority priority) external returns (uint256 taskId) {
        if (deadline <= block.timestamp) revert InvalidDeadline();
        unchecked { taskId = _nextTaskId++; }

        _tasks[taskId] = Task({
            description: description,
            deadline: deadline,
            status: Status.Pending,
            priority: priority,
            assignee: msg.sender,
            exists: true
        });
        _userTaskIds[msg.sender].push(taskId);
        emit TaskCreated(taskId, msg.sender, description, deadline);
    }

    function updateStatus(uint256 taskId, Status newStatus) external taskExists(taskId) {
        Task storage task = _tasks[taskId];
        if (task.assignee != msg.sender) revert Unauthorized();
        if (!_isValidTransition(task.status, newStatus)) revert InvalidStatusTransition();

        task.status = newStatus;
        emit TaskStatusUpdated(taskId, newStatus, msg.sender);
    }

    function assignTask(uint256 taskId, address newAssignee) external onlyOwner taskExists(taskId) {
        if (newAssignee == address(0)) revert ZeroAddress();
        _tasks[taskId].assignee = newAssignee;
        emit TaskAssigned(taskId, newAssignee);
    }

    function getMyTasks() external view returns (uint256[] memory) {
        return _userTaskIds[msg.sender];
    }

    function getTask(uint256 taskId) external view taskExists(taskId) returns (Task memory) {
        return _tasks[taskId];
    }

    function _isValidTransition(Status current, Status next) internal pure returns (bool) {
        if (current == Status.Pending && next == Status.InProgress) return true;
        if (current == Status.InProgress && next == Status.Completed) return true;
        if (current == Status.Pending && next == Status.Cancelled) return true;
        return false;
    }
}

PART 7: COMMON BEGINNER MISTAKES

1. Confusing Value and Reference Types

// ❌ WRONG
function bad(string memory str) external {
    string memory copy = str;
    str = "changed"; // Doesn't affect copy
}

2. Using tx.origin for Authentication

// ❌ WRONG: Vulnerable to phishing
require(tx.origin == owner);

// ✅ CORRECT
require(msg.sender == owner);

3. Division Truncation

// ❌ WRONG
uint256 result = 5 / 2; // Result is 2

// ✅ CORRECT: Multiply first
uint256 result = (5 * 100) / 2; // 250

4. Unchecked External Call Results

// ❌ WRONG
msg.sender.call{value: 1 ether}(""); // Silent failure!

// ✅ CORRECT
(bool success, ) = msg.sender.call{value: 1 ether}("");
require(success);

5. Wrong Location & 6. Missing Payable

// ❌ WRONG: Wastes gas
function process(string memory data) external { }

// ✅ CORRECT: Use calldata
function process(string calldata data) external { }

// ❌ WRONG: Cannot receive ETH
function deposit() external { }

// ✅ CORRECT
function deposit() external payable { }

PART 8: QUICK REFERENCE CHEAT SHEET

Type Quick Reference

TypeSizeDefaultCategory
bool1 bitfalseValue
uint25632 bytes0Value
address20 bytes0x0Value
bytes3232 bytes0x0Value
stringDynamic""Reference
mappingDynamic-Reference

Gas Cost Quick Reference

OperationGas
calldata read3
storage read (cold)2100
storage write20,000
Custom error~30

Decision Trees

Visibility:

Is it state var? → public/private/internal
Called internally? → public/internal
Called externally? → external

Location:

State var? → storage
External param? → calldata
Return value? → memory

Naming Conventions

ElementConventionExample
ContractsPascalCaseTaskManager
EventsPascalCaseTaskCreated
FunctionscamelCasecreateTask
ConstantsUPPER_CASEMAX_VAL

Final Security Checklist

  • [ ] Use msg.sender not tx.origin
  • [ ] Checks-Effects-Interactions pattern?
  • [ ] call() used for ETH transfers?
  • [ ] Inputs validated (zero addr, etc.)?
  • [ ] Custom errors used?
  • [ ] Events added for state changes?
  • [ ] immutable for constructor values?
  • [ ] Explicit sizes (uint256)?

THE END 🚀