Числовые типы и арифметика без переполнения
В Solidity все числа целые и фиксированной разрядности, а с версии 0.8 переполнение само откатывает транзакцию.
До 0.8 переполнение тихо «обнуляло» баланс и сжигало миллионы. Сегодня компилятор вставляет проверки за вас — но понимать их обязан каждый.
Базовые числовые типы — uint256 (беззнаковое 256-битное) и int256 (знаковое). Можно брать меньшие размеры кратно 8 бит: uint8, uint16, ..., uint256. По умолчанию uint = uint256. Дробных чисел нет вообще: float и double в Solidity отсутствуют, потому что детерминизм важнее удобства.
Диапазоны и переполнение
uint8 хранит 0..255. Что будет, если к 255 прибавить 1? До Solidity 0.8 результат «оборачивался» в 0 — это и есть integer overflow, причина знаменитых взломов. С 0.8.x компилятор по умолчанию вставляет проверку: при переполнении транзакция откатывается с ошибкой Panic.
uint8: диапазон 0 ............ 255
|
255 + 1 v
ДО 0.8: -----------> 0 (тихая катастрофа)
0.8.x: -----------> REVERT (Panic 0x11)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Math {
function add(uint8 a, uint8 b) public pure returns (uint8) {
return a + b; // при a+b > 255 транзакция откатится
}
// если переполнение нужно осознанно (например хэши) — unchecked
function wrapAdd(uint8 a, uint8 b) public pure returns (uint8) {
unchecked { return a + b; } // вернётся «обёрнутое» значение
}
}
Как работает под капотом (EVM/газ)
Встроенная проверка — это не магия, а дополнительные опкоды, которые компилятор добавляет после каждой арифметической операции: сравнить результат и при переполнении вызвать revert. Это стоит немного газа. Блок unchecked убирает эти проверки — его используют, когда вы математически уверены, что переполнения не будет (например, счётчик цикла), чтобы сэкономить газ. Деление на ноль тоже даёт Panic-revert.
# Та же логика на Python: проверка переполнения uint8
MAX_U8 = 255
def checked_add(a, b):
r = a + b
if r > MAX_U8:
raise OverflowError("REVERT: Panic 0x11 (overflow)")
return r
def unchecked_add(a, b):
return (a + b) % (MAX_U8 + 1) # обёртка по модулю, как старая EVM
print("checked 200+50 =", checked_add(200, 50))
try:
checked_add(200, 100)
except OverflowError as e:
print(e)
print("unchecked 200+100 =", unchecked_add(200, 100)) # 44
«Та же логика на Python ▶». Питон-версия наглядно показывает разницу: checked откатывается, unchecked оборачивается по модулю — ровно как ведёт себя EVM.
Частые ошибки
- Применять
unchecked«для экономии газа», не убедившись, что переполнение невозможно — это возврат к багам 2017 года. - Ожидать дробные числа:
1/3в Solidity даёт 0, потому что это целочисленное деление. - Использовать мелкие типы (
uint8) для счётчиков, которые могут вырасти — легко упереться в потолок и поймать revert.
Best practices
- По умолчанию используйте
uint256: он самый дешёвый по газу (нативный размер EVM) и не переполнится на реальных значениях. - Для денег с дробями работайте в наименьших единицах (как копейки): у токенов это базовые единицы с учётом
decimals. - Применяйте
uncheckedтолько в проверенных местах и пишите комментарий, почему переполнение невозможно.
Контекст: почему это меняло историю Ethereum
Атаки на переполнение в 2017–2018 годах были массовыми: достаточно вспомнить серию «batchOverflow» в токенах, где функция перевода нескольким адресам перемножала количество получателей на сумму, и произведение тихо переполнялось, позволяя начеканить себе астрономический баланс из ничего. Тогда индустрия спасалась библиотекой SafeMath, которая вручную проверяла каждую операцию и откатывала транзакцию при переполнении. С приходом Solidity 0.8 эти проверки переехали прямо в компилятор, и SafeMath для базовой арифметики стал не нужен. Это хороший пример того, как язык эволюционирует, забирая частые ошибки разработчиков на себя — но понимать механику обязательно, иначе соблазн unchecked вернёт вас в 2017 год.
Итоги
Числа в Solidity — целые, фиксированной разрядности, дробей нет. С версии 0.8 переполнение откатывает транзакцию, а блок unchecked возвращает старое «оборачивающееся» поведение для оптимизаций. Дальше — адреса, булевы и строки.