Структуры и перечисления

Структуры группируют связанные поля в одну сущность, а перечисления делают состояния читаемыми. Вместе с маппингом это «таблица» в блокчейне.
Хотите хранить пользователей, заказы или голоса? Это всегда struct внутри mapping — рабочая лошадка любого протокола.

Структура (struct) — пользовательский тип из нескольких полей, как запись в таблице. Перечисление (enum) — именованный набор состояний (например Pending, Active, Closed), под капотом это маленькое целое, но читать код гораздо приятнее.

Главный паттерн хранения сущностей: mapping(uint256 => Struct) или mapping(address => Struct). Ключ — идентификатор, значение — вся запись.

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

contract Campaigns {
    enum Status { Pending, Active, Closed }

    struct Campaign {
        address creator;
        uint256 goal;
        uint256 raised;
        Status status;
    }

    mapping(uint256 => Campaign) public campaigns; // id => запись
    uint256 public nextId;

    function create(uint256 goal) external returns (uint256 id) {
        id = nextId++;
        campaigns[id] = Campaign({
            creator: msg.sender,
            goal: goal,
            raised: 0,
            status: Status.Active
        });
    }

    function pledge(uint256 id) external payable {
        Campaign storage c = campaigns[id]; // ссылка на storage
        require(c.status == Status.Active, "not active");
        c.raised += msg.value;
        if (c.raised >= c.goal) c.status = Status.Closed;
    }
}
   mapping(uint256 => Campaign)
   ===========================
   id=0 -> { creator, goal, raised, status }
   id=1 -> { creator, goal, raised, status }
   id=2 -> { creator, goal, raised, status }
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
          struct = «строка таблицы», ключ = «первичный ключ»

Как работает под капотом (EVM/газ)

Поля структуры раскладываются по слотам storage подряд и подчиняются той же упаковке: мелкие поля (enum, bool, address) компилятор может уложить в один слот. Ключевой момент — слово storage в Campaign storage c = ...: это ссылка на данные в блокчейне, и изменение c.raised сразу пишет в storage. Если бы было memory, вы бы меняли временную копию, и изменения не сохранились бы. enum хранится как uint8, поэтому очень дёшев.

# Та же логика на Python: struct внутри mapping
from dataclasses import dataclass

PENDING, ACTIVE, CLOSED = 0, 1, 2  # enum как маленькие целые

@dataclass
class Campaign:
    creator: str
    goal: int
    raised: int = 0
    status: int = ACTIVE

campaigns = {}      # id -> Campaign  (mapping)
next_id = 0

def create(creator, goal):
    global next_id
    cid = next_id; next_id += 1
    campaigns[cid] = Campaign(creator, goal)
    return cid

def pledge(cid, amount):
    c = campaigns[cid]            # ссылка на запись (storage)
    assert c.status == ACTIVE, "not active"
    c.raised += amount
    if c.raised >= c.goal:
        c.status = CLOSED

cid = create("alice", goal=100)
pledge(cid, 60); pledge(cid, 50)
print(campaigns[cid])  # raised=110, status=CLOSED

«Та же логика на Python ▶». Объект-ссылка ведёт себя как storage-ссылка: меняя поля, мы меняем саму запись в «маппинге».

Частые ошибки

  • Взять Campaign memory c = campaigns[id], изменить поле и удивляться, что в блокчейне ничего не поменялось — нужна storage-ссылка.
  • Класть в структуру динамические массивы/строки без нужды — это раздувает газ и усложняет копирование.
  • Сравнивать enum с «магическими числами» вместо именованных значений — теряется читаемость.

Best practices

  • Объявляйте поля структуры так, чтобы мелкие типы шли подряд — компилятор упакует их и сэкономит слоты.
  • Используйте enum для конечных состояний вместо «волшебных» чисел или строк.
  • Помните разницу storage vs memory при работе со структурами — это частый источник тихих багов.

Итоги

struct группирует поля сущности, enum делает состояния явными, а mapping от id к структуре — это «таблица» протокола. Различие storage/memory здесь критично. Дальше — массивы и циклы.

Проверьте себя
1. Вы пишете Campaign memory c = campaigns[id]; c.raised += 10; Что сохранится в блокчейне?
AПоле raised увеличится в storage
BНичего — изменена временная memory-копия
CТранзакция откатится
DУдалится вся запись
2. Как под капотом хранится enum из трёх значений?
AКак строка
BКак маленькое целое (например uint8)
CКак массив байт
DКак отдельный контракт