Требуемые знания:
- 04-evm-stack-memory-storage
Газ и выполнение транзакций
Зачем нужен газ
Каждая операция в EVM стоит газ — единицу вычислительной работы. Газ решает две фундаментальные проблемы:
- Halting problem: Тьюринг-полная машина может зациклиться. Газ гарантирует, что любое выполнение завершится (когда газ закончится)
- DoS protection: Без газа злоумышленник мог бы бесплатно нагружать сеть тяжелыми вычислениями
Цель урока: Вы сможете объяснить EIP-1559, рассчитать стоимость транзакции, сравнить газовую стоимость опкодов и применить основные приемы оптимизации.
Интуитивная аналогия: бензин и поездка
| Концепт | Аналогия | Ethereum |
|---|---|---|
| Gas limit | Размер бака | Максимум газа, который вы готовы потратить |
| Gas used | Сколько бензина сожгли | Фактический расход газа при выполнении |
| Base fee | Цена бензина на заправке | Цена за единицу газа, установленная протоколом |
| Priority fee (tip) | Чаевые водителю | Доплата валидатору за приоритет |
| Max fee | Максимум, который готовы заплатить | Потолок цены за единицу газа |
| Total cost | Итого за поездку | gas_used * effective_gas_price |
EIP-1559: динамическая модель комиссий
До обновления London (август 2021) Ethereum использовал аукцион первой цены: кто больше заплатил — того транзакцию включили первой. Это приводило к непредсказуемым комиссиям и переплатам.
EIP-1559 ввел новую модель с базовой комиссией (base fee), которая:
- Автоматически подстраивается под нагрузку сети
- Сжигается (burned) — не достается валидатору
- Дополняется приоритетной комиссией (priority fee / tip) — идет валидатору
Три поля комиссии в транзакции
Поля транзакции (тип 2, EIP-1559):
maxFeePerGas -- максимум за единицу газа (gwei)
maxPriorityFeePerGas -- максимальный tip валидатору (gwei)
gasLimit -- максимум единиц газа
Как вычисляется baseFee
Базовая комиссия корректируется каждый блок в зависимости от заполненности предыдущего блока:
- Target = 15M gas (50% от максимума 30M gas)
- Блок заполнен >50% → baseFee растет (до +12.5% за блок)
- Блок заполнен <50% → baseFee падает (до -12.5% за блок)
- Блок заполнен ровно 50% → baseFee не меняется
Формула:
new_base_fee = old_base_fee * (1 + (gas_used - target) / target / 8)
Попробуйте перемещать ползунок заполненности блока и наблюдайте, как меняется baseFee:
Расчет стоимости транзакции
Пошаговый расчет для конкретного примера:
Дано:
maxFeePerGas = 30 gwei
maxPriorityFeePerGas = 2 gwei
baseFeePerGas = 12 gwei (установлено протоколом)
gasLimit = 100000
gasUsed = 21000 (простой перевод ETH)
Вычисляем:
1. effectiveGasPrice = min(maxFeePerGas, baseFeePerGas + maxPriorityFeePerGas)
= min(30, 12 + 2)
= 14 gwei
2. totalCost = gasUsed * effectiveGasPrice
= 21000 * 14
= 294000 gwei
= 0.000294 ETH
3. burned = gasUsed * baseFeePerGas
= 21000 * 12
= 252000 gwei (сожжено)
4. validatorTip = gasUsed * (effectiveGasPrice - baseFeePerGas)
= 21000 * (14 - 12)
= 42000 gwei (валидатору)
5. refund = (gasLimit - gasUsed) * effectiveGasPrice
= 79000 * 14
= 1106000 gwei (возвращено отправителю)
Что происходит при высокой baseFee?
Если baseFeePerGas + maxPriorityFeePerGas > maxFeePerGas:
baseFeePerGas = 29 gwei
maxPriorityFeePerGas = 2 gwei
maxFeePerGas = 30 gwei
effectiveGasPrice = min(30, 29 + 2) = 30 gwei
Фактический tip = 30 - 29 = 1 gwei (меньше заявленных 2 gwei)
Если baseFeePerGas > maxFeePerGas — транзакция не будет включена в блок. Она ждет в мемпуле, пока baseFee не снизится.
Стоимость опкодов
Каждый опкод EVM имеет фиксированную стоимость в газе. Некоторые операции имеют переменную стоимость в зависимости от данных или контекста.
EIP-2929: Cold и Warm Access
С обновления Berlin (апрель 2021, EIP-2929) введено разделение на cold и warm доступ:
- Cold access — первое обращение к адресу или слоту хранилища в транзакции. Дороже
- Warm access — повторное обращение к тому же адресу/слоту. Дешевле
Это отражает реальную стоимость: первое чтение требует загрузки данных с диска, повторное — из кеша.
Примеры:
SLOAD slot_X (первый раз) = 2100 gas (cold)
SLOAD slot_X (второй раз) = 100 gas (warm)
Экономия при повторном доступе: 2000 gas!
Изучите стоимость всех основных опкодов:
Почему SSTORE такой дорогой?
SSTORE (запись в хранилище) — самая дорогая базовая операция EVM:
| Сценарий | Gas | Почему |
|---|---|---|
| 0 → non-0 (cold) | 22100 | Создание нового слота. Данные записываются на диск навсегда |
| non-0 → non-0 (cold) | 5000 | Обновление. Слот уже существует, но первый доступ |
| non-0 → 0 | 5000 - 4800 refund | Удаление слота. Refund стимулирует очистку |
| Warm access | 100 | Слот уже в кеше |
Главный вывод: Минимизируйте запись в хранилище. Одна операция SSTORE (22100 gas) = 7367 операций ADD (3 gas).
Жизненный цикл транзакции
Шаг 1: Проверка
1. Подпись валидна (ECDSA, см. CRYPTO-11)
2. Nonce совпадает с текущим nonce аккаунта
3. Баланс >= gasLimit * maxFeePerGas + value
4. gasLimit >= intrinsic gas (21000 для трансфера)
Шаг 2: Предварительное списание
Списание = gasLimit * maxFeePerGas
Баланс -= списание
Nonce += 1
Шаг 3: Выполнение
EVM выполняет байткод контракта (или просто перевод ETH).
Каждый опкод уменьшает gas counter.
Если gas == 0 -- выполнение откатывается (revert).
Шаг 4: Финализация
refund = min(gasUsed / 5, accumulated_refund)
effective_refund = (gasLimit - gasUsed + refund) * effectiveGasPrice
Баланс += effective_refund
burned = gasUsed * baseFeePerGas
tip = gasUsed * (effectiveGasPrice - baseFeePerGas)
Валидатор получает: tip
Протокол сжигает: burned
Intrinsic Gas
Каждая транзакция имеет базовую стоимость до выполнения байткода:
| Компонент | Gas | Описание |
|---|---|---|
| Базовая стоимость | 21000 | Любая транзакция |
| Нулевой байт calldata | 4 | За каждый 0x00 байт |
| Ненулевой байт calldata | 16 | За каждый non-zero байт |
| Создание контракта | 32000 | Дополнительно к 21000 |
| Access list entry | 2400/1900 | EIP-2930 (адрес/слот) |
Пример: простой перевод ETH
intrinsic_gas = 21000 + 0 (нет calldata)
Итого: 21000 gas
Пример: вызов функции transfer(address,uint256)
calldata = 4 байта selector + 32 байта address + 32 байта amount = 68 байт
intrinsic_gas = 21000 + 68 * 16 = 22088 gas (если нет нулевых байт)
Математическое описание
EIP-1559 формально
Пусть — baseFee блока , — gasUsed блока , — target gas:
Максимальное изменение за блок: (при или ).
При постоянной загрузке (доля от максимума):
Effective gas price
Где = maxFeePerGas, = maxPriorityFeePerGas.
Total transaction cost
Распределение:
Оптимизация газа: практические советы
1. Минимизируйте SSTORE
// Плохо: 2 записи в storage
function bad(uint256 x) external {
counter += x; // SSTORE
total += x; // SSTORE
}
// Лучше: кешируйте в memory
function better(uint256 x) external {
uint256 _counter = counter; // SLOAD
uint256 _total = total; // SLOAD
_counter += x;
_total += x;
counter = _counter; // SSTORE
total = _total; // SSTORE
}
// Экономия: если counter/total читались ранее (warm), нет разницы.
// Но если были доп. чтения -- memory дешевле.
2. Упаковка переменных (Slot Packing)
// Плохо: 3 слота
contract Bad {
uint128 a; // slot 0
uint256 b; // slot 1
uint128 c; // slot 2
}
// Хорошо: 2 слота
contract Good {
uint128 a; // slot 0 (lower)
uint128 c; // slot 0 (upper) -- packed!
uint256 b; // slot 1
}
3. Calldata вместо Memory
// Дороже: копирует массив в memory
function bad(uint256[] memory data) external { ... }
// Дешевле: читает прямо из calldata
function better(uint256[] calldata data) external { ... }
4. Custom Errors вместо require строк
// Дороже: строка хранится в байткоде
require(balance >= amount, "Insufficient balance");
// Дешевле: custom error
error InsufficientBalance(uint256 available, uint256 required);
if (balance < amount) revert InsufficientBalance(balance, amount);
5. Используйте unchecked для безопасных операций
// С проверкой переполнения (по умолчанию в Solidity 0.8+)
for (uint256 i = 0; i < length; i++) { ... }
// Без проверки (i не может overflow в реалистичном цикле)
for (uint256 i = 0; i < length; ) {
...
unchecked { i++; }
}
Практика
-
Используйте слайдер в диаграмме EIP-1559 чтобы исследовать:
- Как baseFee реагирует на 100% загрузку блока?
- При какой загрузке baseFee стабилен?
- Что происходит с tip при высокой baseFee?
-
В таблице стоимости опкодов найдите:
- Самый дешевый опкод
- Самый дорогой (без CREATE)
- Разницу между cold и warm SLOAD
-
Рассчитайте вручную стоимость транзакции:
- maxFeePerGas = 25 gwei, maxPriorityFeePerGas = 3 gwei
- baseFeePerGas = 20 gwei, gasUsed = 50000
- Сколько сожжено? Сколько получил валидатор?
Итоги
- Газ — единица вычислительной работы, решает halting problem и защищает от DoS
- EIP-1559: baseFee (сжигается, автоподстройка) + priorityFee (валидатору)
- baseFee растет при загрузке >50%, падает при <50%. Макс. изменение: 12.5% за блок
- effectiveGasPrice = min(maxFee, baseFee + priorityFee)
- Cold/warm access (EIP-2929): первый доступ к слоту/адресу дороже
- SSTORE cold (0->non-0) = 22100 gas — самая дорогая базовая операция
- Оптимизация: минимизируйте SSTORE, пакуйте переменные, используйте calldata и custom errors
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс