Что сломается в вашей интеграции, если TRON изменит цену энергии или параметры сети

В TRON цена энергии (getEnergyFee, сейчас 100 sun) и потолок fee_limit (15 000 TRX) — это параметры сети, которые меняются ончейн-предложениями комитета SR без хардфорка. Если они захардкожены, любое изменение ломает пересчёт Energy в sun: fee_limit оказывается либо слишком низким (транзакции падают с OUT_OF_ENERGY), либо избыточным, а расчёт стоимости перевода расходится с реальностью. Решение — запрашивать wallet/getchainparameters и пересчитывать бюджет перед отправкой.
Короткий ответ
В TRON цена энергии — это не рыночный газ, а параметр сети: getEnergyFee (параметр №11), сейчас 100 sun = 0,0001 TRX за единицу Energy. Как и потолок getMaxFeeLimit (15 000 TRX) или лимит CPU на транзакцию (80 мс), он меняется комитетом SR через ончейн-предложение — без хардфорка и без миграции на новую ноду. Поэтому в вашей интеграции ломается не подпись и не формат транзакции, а арифметика бюджета: пересчёт Energy → sun, значение fee_limit, кэшированные оценки и расчёты себестоимости. Всё это обычно зашито в виде констант.
Параметр уже много раз менялся: в разных версиях документации TRON упоминаются 10, 100, 280 и 420 sun за единицу Energy. Любое из этих чисел, захардкоженное в код, даёт расхождение в несколько раз.
Что именно перестаёт работать
1. Захардкоженный fee_limit
Вы получаете energy_required в Energy, но передаёте fee_limit в sun: fee_limit (sun) = energy_required × getEnergyFee. Если цена энергии выросла, а ваш множитель — константа, бюджета не хватает: транзакция откатывается с OUT_OF_ENERGY, а уже потреблённая Energy списывается и не возвращается. Если цена упала, вы просто замораживаете лишние TRX на горячих кошельках. Ещё одна классика — путаница в единицах: fee_limit = 100, задуманный как «100 TRX», на деле разрешает 0,0001 TRX, и вызов падает сразу. Документация прямо называет хардкод «15 000 TRX» и «100 sun» типичной ошибкой продакшена.
2. Расчёт стоимости перевода USDT TRC-20
Перевод USDT стоит около 64 000 Energy, если у получателя ненулевой баланс токена, и около 130 000 Energy, если баланс нулевой (разница возникает из-за стоимости SSTORE, когда слот выходит из нулевого состояния). Умножать это на константную цену энергии нельзя: меняются и цена, и потребление. Мейннет работает на Dynamic Energy Model — после превышения порога нагрузки эффективная стоимость Energy популярного контракта умножается на коэффициент от 1× до 4,4×, а коэффициент пересчитывается каждый цикл обслуживания (6 часов). Стоимость отдельных опкодов TVM тоже была предметом голосования — такое предложение появилось в релизе Cleobulus.
Итог: стоимость перевода — это функция трёх переменных (потребление, energy_factor, getEnergyFee), а не число из прайс-листа.
3. Кэшированные оценки энергии
estimateenergy отражает состояние ноды на момент запроса и не гарантирует, что последующая транзакция пройдёт успешно. Кэш, переживший цикл обслуживания, обесценивается на границе цикла вместе с energy_factor, а изменение состояния контракта или параметров вызова обесценивает его ещё раньше. Числа из Shasta не годятся для продакшена: один и тот же вызов может стоить 31 000 Energy в тестнете и 90 000 в мейннете.
4. Параметры на стороне контракта
Это не сетевые параметры, но они так же тихо ломают вашу экономику. consume_user_resource_percent — доля стоимости, которую платит вызывающий; разработчик может изменить её после деплоя через wallet/updatesetting, и новое значение применяется ко всем последующим вызовам. Если у разработчика заканчивается застейканная Energy, неоплаченная часть молча переходит на сжигание TRX пользователя. origin_energy_limit хранится в ончейн-записи контракта: после UpdateEnergyLimitContract каждый вызывающий получает новое значение со следующего блока, не видя изменения.
5. Гейты форков и лимит 80 мс
Новые возможности TVM включаются гейтами сетевых параметров; пока гейт не включён, соответствующий опкод трактуется как ILLEGAL_OPERATION, а раскатка в Mainnet, Nile и Shasta происходит независимо. Лимит getMaxCpuTimeOfOneTx (80 мс, параметр №13) тоже меняется голосованием: его превышение даёт OUT_OF_TIME и списывается весь fee_limit. Если сложность вашего вызова близка к критическому порогу, он будет падать нерегулярно.
Что изменить на своей стороне
| Где | Что делать |
|---|---|
| Конфиг | Уберите константы energyPrice и maxFeeLimit; читайте wallet/getchainparameters при старте и по расписанию |
| Расчёт fee_limit | energy_required × getEnergyFee + запас на DEM (до 4,4×) либо консервативный бюджет по максимальному коэффициенту |
| Оценка | Пересчитывайте оценку прямо перед отправкой; не переиспользуйте кэш через границу 6-часового цикла |
| Обработка ошибок | Разделяйте OUT_OF_ENERGY (недостаточный бюджет) и OUT_OF_TIME (списан весь лимит) — логика повтора для них разная |
| Себестоимость | Считайте по фактическому потреблению из квитанции транзакции, а не по прайс-листу; учитывайте сценарий «у получателя нулевой баланс USDT» |
История цены энергии доступна через wallet/getenergyprices, а список идентификаторов параметров и их текущие значения — в глоссарии.
Вывод
В TRON нет EIP-1559 и приоритетных комиссий: все платят одну ставку, и именно поэтому её меняет решение комитета. Интеграция, которая читает параметры сети из цепи и считает бюджет динамически, переживает такое изменение без деплоя. Интеграция, построенная на константах, узнаёт о нём по волне ошибок OUT_OF_ENERGY и расхождению в отчёте о стоимости.