Skip to content

Лимиты (Limits)

Что такое лимиты

Система лимитов в MultiVault защищает от мгновенного опустошения контракта при компрометации relay-нод или эксплойте уязвимости. Лимиты применяются к двум типам операций:

  1. Лимиты на депозит — ограничивают максимальное количество токенов, которое может храниться на vault
  2. Лимиты на вывод — ограничивают сумму вывода за одну транзакцию и суммарно за 24 часа

Зачем нужны лимиты?

  • Компрометация relay-нод — лимиты замедлят вывод средств и дадут время на обнаружение атаки
  • Эксплойты смарт-контрактов — лимиты блокируют крупные транзакции
  • Защита ликвидности — предотвращают моментальное опустошение пулов
  • Fraud detection window — превышение лимитов создаёт pending withdrawal, требующий одобрения

Типы лимитов

1. Лимиты на депозит (Deposit Limits)

Ограничивают максимальный баланс токена на vault.

ПараметрОписание
depositLimitМаксимальный баланс токена на vault
ПрименяетсяТолько для Alien токенов
При превышенииДепозит отклоняется (revert)

Почему только для Alien: Native токены при депозите сжигаются (burn), а не хранятся на vault. Поэтому баланс vault для native токенов всегда ~0.

2. Лимиты на вывод (Withdrawal Limits)

Ограничивают суммы вывода токенов из vault.

ПараметрОписание
undeclaredЛимит на одну транзакцию (per-transaction limit)
dailyЛимит на сумму за 24 часа (per-period limit)
enabledФлаг включения лимитов для токена
ПрименяетсяДля Alien и Native токенов
При превышенииСоздаётся Pending Withdrawal

Инвариант: daily >= undeclared

Лимиты на депозит

Формула проверки

Депозит отклоняется, если сумма текущего баланса vault и суммы депозита превысит установленный лимит. Если лимит не установлен (равен нулю), проверка пропускается.

Применение по типу токена

Тип токенаПрименяется?Причина
Alien✅ ДаТокены хранятся на vault
Native❌ Нет (фактически)Токены сжигаются, баланс vault = 0

Вывод проходит мгновенно, если выполняются оба условия одновременно. Если лимиты отключены для токена, проверка пропускается.

Условие 1: Per-transaction (undeclared)

Сумма текущей транзакции должна быть строго меньше лимита на одну транзакцию (undeclared).

Важно: Транзакция ровно на сумму лимита требует одобрения.

Условие 2: Per-period (daily)

Сумма текущей транзакции плюс уже выведенное за период минус одобренное governance должна быть строго меньше суточного лимита (daily).

  • Уже выведено (total) — суммарный вывод за текущий 24-часовой период
  • Одобрено (considered) — суммы, одобренные governance (вычитаются, чтобы не блокировать последующие легитимные выводы)

Пример расчёта

ПараметрЗначение
Undeclared limit10,000 USDT
Daily limit50,000 USDT
Уже выведено за период (total)30,000 USDT
Одобрено governance (considered)20,000 USDT
Запрос на вывод15,000 USDT

Проверка 1 (per-transaction):

15,000 < 10,000 → ❌ FAIL

Проверка 2 (per-period):

15,000 + 30,000 - 20,000 = 25,000 < 50,000 → ✅ PASS

Результат: Хотя проверка 2 прошла, проверка 1 провалилась → создаётся Pending Withdrawal со статусом Required.

Механизм 24-часовых периодов

Вычисление ID периода

ID периода вычисляется делением timestamp на длительность периода (86400 секунд = 24 часа). Все транзакции в рамках одних суток получают одинаковый ID периода.

Примеры

TimestampДата/время (UTC)Period ID
17040672002024-01-01 00:00:0019723
17041535992024-01-01 23:59:5919723
17041536002024-01-02 00:00:0019724

Параметры периода

Для каждого периода хранятся два значения:

  • total — суммарный вывод за период (включая pending)
  • considered — суммы, одобренные governance

Автоматический сброс

При переходе в новый период счётчики автоматически обнуляются (новая запись в маппинге).

Важно: Используется eventTimestamp из TVM события, а не block.timestamp. Это предотвращает манипуляции через задержку исполнения транзакции.

Причины создания Pending Withdrawal

Вывод переходит в Pending по двум причинам:

1. Превышение лимитов

Тип токенаСтатусОписание
NativeRequiredВсегда устанавливается Required
AlienRequiredУстанавливается Required если лимиты превышены

2. Недостаток средств на vault

Тип токенаПроисходит?СтатусПричина
Native❌ НетТокен минтится, не хранится на vault
Alien✅ ДаNotRequiredVault может не иметь достаточного баланса

Диаграмма создания Pending



Действия с Pending Withdrawal

1. Задать новое значение для bounty

Кто может вызвать: Только recipient

Ограничения:

  • Bounty задаётся только для Alien токенов (для Native в EVM)
  • По умолчанию pending создаётся с bounty = 0

Функция setPendingWithdrawalBounty проверяет, что токен не Native и что bounty не превышает сумму вывода, затем записывает новое значение bounty.

Примечание: При вызове saveWithdrawAlien можно сразу указать bounty, если отправитель транзакции является получателем вывода.


2. Отменить полностью или частично (Cancel)

Кто может вызвать: Только recipient

Ограничения:

  • Доступно только для Alien токенов
  • Доступно только при статусе NotRequired или Approved
  • При частичной отмене можно задать новую bounty

Результат: Создаётся обратный трансфер в TVM сеть на указанный amount

Функция cancelPendingWithdrawal проверяет, что токен не Native и что сумма отмены корректна. Затем уменьшает сумму pending на указанную величину, инициирует обратный трансфер в TVM и опционально устанавливает новую bounty для оставшейся суммы.


3. Approve или Reject (для статуса Required)

Кто может вызвать: Только governance или withdrawGuardian

Требования:

  • Текущий статус должен быть Required
  • Можно установить только Approved или Rejected

Логика при Approve:

  • Если баланс vault достаточен ИЛИ это Native токен → автоматический вывод
  • Иначе просто меняется статус на Approved

Callback:НЕ вызывается

Функция setPendingWithdrawalApprove проверяет, что текущий статус Required и что устанавливается Approved или Rejected. При установке Approved, если баланс vault достаточен или это Native токен, автоматически выполняется вывод. В любом случае сумма добавляется к considered для текущего периода.


4. Протолкнуть вручную (Force Withdraw)

Кто может вызвать: Любой адрес

Требования:

  • Статус NotRequired или Approved
  • amount > 0

Логика:

  • amount переводится получателю в полном объёме
  • Bounty никому не зачисляется (идёт получателю)

Callback:Вызывается

Функция forceWithdraw принимает массив pending withdrawals и для каждого: обнуляет сумму pending, переводит получателю полную сумму (без вычета bounty) и вызывает callback.


5. Закрыть через депозит

Кто может вызвать: Любой адрес (обычно арбитражёр)

Требования:

  • Статус pending withdrawals: NotRequired или Approved
  • Токен депозита совпадает с токеном pending

Логика:

  1. Пользователь отправляет депозит с указанием pending withdrawals и минимальной суммарной bounty
  2. Для каждого pending: получателю переводится сумма за вычетом bounty
  3. Callback вызывается для каждого закрытого pending
  4. Создаётся EVM→TVM трансфер на сумму депозита плюс накопленные bounties за вычетом комиссии

Функция перебирает указанные pending withdrawals, накапливает bounties, переводит получателям их суммы за вычетом bounty и вызывает callback. Затем проверяет, что суммарная bounty не меньше ожидаемой, и создаёт трансфер в TVM.


Сводная таблица действий

ДействиеКто можетТребуемый статусCallbackBounty
Set BountyrecipientЛюбойЗадаётся новое значение
CancelrecipientNotRequired / ApprovedМожно задать новое
Approve/Rejectgovernance / withdrawGuardianRequired❌ Нет
Force WithdrawЛюбойNotRequired / Approved✅ ДаНе вычитается, идёт recipient
Close via DepositЛюбойNotRequired / Approved✅ ДаИдёт депозитору (арбитражёру)

Структура данных

WithdrawalLimits

Структура хранит лимиты на вывод для токена:

  • undeclared — лимит на одну транзакцию
  • daily — лимит за 24-часовой период
  • enabled — флаг включения лимитов

WithdrawalPeriodParams

Структура хранит параметры 24-часового периода:

  • total — суммарный вывод за период
  • considered — суммы, одобренные governance

PendingWithdrawalParams

Структура хранит параметры отложенного вывода:

  • token — адрес токена
  • amount — сумма к выводу (после комиссии)
  • bounty — награда для арбитражёра
  • timestamp — timestamp TVM события
  • approveStatus — статус одобрения
  • chainId — Chain ID источника
  • callback — данные для callback после вывода

ApproveStatus

Возможные статусы одобрения pending withdrawal:

  • NotRequired (0) — одобрение не требуется (недостаток средств на vault)
  • Required (1) — требуется одобрение (превышение лимитов)
  • Approved (2) — одобрено governance или withdrawGuardian
  • Rejected (3) — отклонено

Storage маппинги

Данные хранятся в следующих маппингах:

  • tokens_ — информация о токенах, включая depositLimit
  • withdrawalLimits_ — лимиты на вывод по токенам
  • withdrawalPeriods_ — параметры периодов (токен → ID периода → параметры)
  • pendingWithdrawals_ — отложенные выводы (пользователь → ID → параметры)
  • pendingWithdrawalsPerUser — счётчик pending для каждого пользователя (используется как ID)
  • pendingWithdrawalsTotal — суммарный pending по токену

Длительность периода — 86400 секунд (24 часа).

Управление лимитами

Установка лимитов

Все функции требуют модификатор onlyGovernance.

Deposit Limit

Функция setDepositLimit устанавливает максимальный баланс токена на vault.

Withdrawal Limits

Доступны следующие функции управления:

  • setDailyWithdrawalLimits — устанавливает суточный лимит (проверяет, что он не меньше undeclared)
  • setUndeclaredWithdrawalLimits — устанавливает лимит на транзакцию (проверяет, что он не больше daily)
  • enableWithdrawalLimits — включает лимиты для токена
  • disableWithdrawalLimits — отключает лимиты для токена

События

СобытиеПараметрыКогда эмитится
UpdateDailyWithdrawalLimitstoken, limitИзменён daily limit
UpdateUndeclaredWithdrawalLimitstoken, limitИзменён undeclared limit
UpdateWithdrawalLimitStatustoken, statusВключены/отключены лимиты
PendingWithdrawalCreatedrecipient, id, token, amount, payloadIdСоздан pending
PendingWithdrawalUpdateApproveStatusrecipient, id, approveStatusИзменён статус
PendingWithdrawalUpdateBountyrecipient, id, bountyИзменена bounty
PendingWithdrawalWithdrawrecipient, id, amountАвтовывод при approve
PendingWithdrawalForcerecipient, idForce withdraw
PendingWithdrawalCancelrecipient, id, amountОтмена pending
PendingWithdrawalFillrecipient, idЗакрыт через депозит

Права доступа

ДействиеТребуемая роль
Установить deposit limitgovernance
Установить withdrawal limitsgovernance
Включить/отключить лимитыgovernance
Approve/Reject pendinggovernance ИЛИ withdrawGuardian
Set bountyrecipient pending withdrawal
Cancel pendingrecipient pending withdrawal
Force withdrawЛюбой адрес
Close via depositЛюбой адрес

Риски и Edge Cases

1. Обход лимитов через split-транзакции

Риск: Атакующий разбивает крупный вывод на множество мелких.

Защита: Daily limit отслеживает суммарный вывод за 24 часа.

2. daily < undeclared

Риск: Некорректная настройка.

Защита: Валидация require(daily >= undeclared) в обоих setter-функциях.

3. Лимиты отключены (enabled = false)

Риск: Если лимиты не установлены для токена, защита не действует.

Митигация: Governance должен устанавливать лимиты для каждого нового токена.

4. Манипуляция timestamp

Риск: Атакующий задерживает транзакцию до нового периода.

Защита: Используется eventTimestamp из TVM события, а не block.timestamp.

5. Race condition при approve

Риск: Governance одобряет pending, но баланс vault недостаточен.

Результат: Статус меняется на Approved, но токены не переводятся. Требуется forceWithdraw().

6. Cancel недоступен для Native токенов

Ограничение: cancelPendingWithdrawal() доступен только для Alien токенов.

Причина: Native токены существуют только в TVM сети. Отменить pending невозможно.

7. Строгое неравенство в проверке

Особенность: Транзакция ровно на сумму лимита требует одобрения (amount < limit, а не <=).

ChainConnect Bridge Documentation