Ошибки логики: округление и потеря точности
Один лишний знак округления «в пользу пользователя» — и протокол медленно истекает.
Потеря точности — искажение результата из-за того, что в EVM нет дробных чисел: всякое деление округляется к нулю, отбрасывая остаток.
Откуда берётся проблема
В Solidity нет float: все вычисления целочисленные, а деление отбрасывает дробную часть. Значит, порядок операций критичен: (a * b) / c и (a / c) * b могут дать разный результат, и второй вариант теряет точность раньше. Финансовая математика (проценты, доли пула, награды) чувствительна к этому: маленькие потери, помноженные на тысячи операций, складываются в реальные деньги.
a, b, c = 7, 100, 3
# делим в конце — точнее
print("(a*b)//c =", (a * b) // c)
# делим раньше — теряем точность
print("(a//c)*b =", (a // c) * b)Вывод:
(a*b)//c = 233 (a//c)*b = 200
Разница — 33 единицы из ничего, лишь из-за порядка деления. В деньгах такая «утечка» накапливается.
Направление округления — это вопрос безопасности
Главное правило: округляйте всегда в пользу протокола, а не пользователя. Если при выдаче активов округлять вверх, а при приёме — вниз, пользователь систематически получает чуть больше и платит чуть меньше; за много операций это выкачивает контракт. Поэтому при выдаче округляют вниз, при взимании — вверх. Это не педантизм: целые «экономические» атаки строятся на накоплении округления.
// Принцип: округление НЕ в пользу пользователя
// выдаём пользователю -> округляем ВНИЗ
uint payout = (amount * rate) / SCALE; // floor
// берём с пользователя -> округляем ВВЕРХ
uint owed = (amount * rate + SCALE - 1) / SCALE; // ceilКак работает под капотом: масштабирование
Поскольку дробей нет, проценты и курсы хранят в «фикс-пойнт» виде — умноженными на масштаб (например, 1e18). Сначала умножают на большой множитель, потом делят: так точность сохраняется до последнего шага. Особое внимание — первому вкладчику пула и пустым пулам: деление на крошечные величины даёт резкие скачки и места для атак инфляции долей.
Частые ошибки
- Делить раньше, чем умножать. Теряете точность без нужды.
- Округлять в пользу пользователя. Систематическая утечка средств.
- Игнорировать крайние случаи. Пустой пул, первый депозит, ноль — частые источники багов.
Итоги
- В EVM нет дробей: деление отбрасывает остаток, порядок операций важен.
- Умножайте до деления, чтобы сохранить точность.
- Округляйте в пользу протокола: выдача — вниз, взимание — вверх.
- Особо проверяйте крайние случаи (пустой пул, первый вклад, ноль).