Лимиты (Limits)
Что такое лимиты
Система лимитов в MultiVault защищает от мгновенного опустошения контракта при компрометации relay-нод или эксплойте уязвимости. Лимиты применяются к двум типам операций:
- Лимиты на депозит — ограничивают максимальное количество токенов, которое может храниться на MultiVault
- Лимиты на вывод — ограничивают сумму вывода за одну транзакцию и суммарно за 24 часа
Важно
Лимиты существуют только на стороне EVM сети.
Зачем нужны лимиты?
- Компрометация relay-нод — лимиты замедлят вывод средств и дадут время на обнаружение атаки
- Эксплойты смарт-контрактов — лимиты блокируют крупные транзакции
- Защита ликвидности — предотвращают моментальное опустошение пулов
- Fraud detection window — превышение лимитов создаёт pending withdrawal, требующий одобрения
Race condition при approve
Governance может одобрить pending, но баланс MultiVault окажется недостаточен — статус изменится на Approved, но токены не будут переведены. Для завершения потребуется forceWithdraw() или Fill через LP (см. Liquidity Request).
Лимиты на депозит (Deposit Limits)
Ограничивают максимальный баланс токена на MultiVault.
| Параметр | Описание |
|---|---|
depositLimit | Максимальный баланс токена на MultiVault |
| Применяется | Только для Alien токенов |
| При превышении | Депозит отклоняется (revert) |
Депозит отклоняется, если сумма текущего баланса MultiVault и суммы депозита превысит установленный лимит. Если лимит не установлен (равен нулю), проверка пропускается.
Почему только для Alien
Native токены при депозите (трансфере EVM→TVM) сжигаются (burn), а не хранятся на MultiVault. Поэтому баланс MultiVault для native токенов всегда ~0.
Лимиты на вывод (Withdrawal Limits)
Ограничивают суммы вывода токенов из MultiVault.
| Параметр | Описание |
|---|---|
undeclared | Лимит на одну транзакцию (per-transaction limit) |
daily | Лимит на сумму за 24 часа (per-period limit) |
enabled | Флаг включения лимитов для токена |
| Применяется | Для Alien и Native токенов |
| При превышении | Создаётся Pending Withdrawal |
Инвариант: daily >= undeclared
Вывод проходит мгновенно, если выполняются оба условия одновременно. Если лимиты отключены для токена, проверка пропускается.
Условие 1: Per-transaction (undeclared)
Сумма текущей транзакции должна быть строго меньше лимита на одну транзакцию (undeclared).
Важно: Транзакция ровно на сумму лимита требует одобрения.
Условие 2: Per-period (daily)
Сумма текущей транзакции плюс уже выведенное за период минус одобренное governance должна быть строго меньше суточного лимита (daily).
- Уже выведено (total) — суммарный вывод за текущий 24-часовой период
- Одобрено (considered) — суммы, одобренные governance (вычитаются, чтобы не блокировать последующие легитимные выводы)
Пример расчёта
| Параметр | Значение |
|---|---|
| Undeclared limit | 10,000 USDT |
| Daily limit | 50,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 |
|---|---|---|
| 1704067200 | 2024-01-01 00:00:00 | 19723 |
| 1704153599 | 2024-01-01 23:59:59 | 19723 |
| 1704153600 | 2024-01-02 00:00:00 | 19724 |
Автоматический сброс
При переходе в новый период счётчики (total, considered) автоматически обнуляются (новая запись в маппинге).
Важно: Используется eventTimestamp из TVM события, а не block.timestamp. Это предотвращает манипуляции через задержку исполнения транзакции.
Поведение при превышении лимитов
Когда вывод не проходит проверку лимитов, создаётся Pending Withdrawal со статусом Required — тот же объект, что и при нехватке ликвидности (см. Liquidity Request), но с другим статусом и набором действий.
Доступные действия
Pending Withdrawal со статусом Required ждёт решения governance:
| Действие | Кто может | Описание |
|---|---|---|
| Approve | governance / withdrawGuardian | Одобрить вывод — если ликвидность есть, токены отправляются сразу |
| Reject | governance / withdrawGuardian | Отклонить вывод — получатель может вернуть токены через Cancel |
| Force Withdraw | Любой | После Approve — протолкнуть вывод, если автоматически не прошёл |
TIP
Действия Fill, Bounty, Cancel доступны для статуса NotRequired — см. Liquidity Request.
Как это работает в контракте
Оба метода (saveWithdrawNative, saveWithdrawAlien) сначала создают Pending со статусом NotRequired. Затем, если лимиты не пройдены, статус сразу обновляется на Required. Для Native токенов единственная причина Pending — лимиты, поэтому статус всегда Required. Для Alien — зависит от того, что именно не прошло: баланс, лимиты или оба.
Диаграмма создания Pending
Детали: Approve или Reject
Кто может вызвать: Только governance или withdrawGuardian
Требования:
- Текущий статус должен быть
Required - Можно установить только
ApprovedилиRejected
Логика при Approve:
- Если баланс MultiVault достаточен ИЛИ это Native токен → автоматический вывод
- Иначе просто меняется статус на
Approved
Callback: ❌ НЕ вызывается
Функция setPendingWithdrawalApprove проверяет, что текущий статус Required и что устанавливается Approved или Rejected. При установке Approved, если баланс MultiVault достаточен или это Native токен, автоматически выполняется вывод. В любом случае сумма добавляется к considered для текущего периода.
Детали: Force Withdraw (после Approve)
Кто может вызвать: Любой адрес
Требования:
- Статус
Approved(илиNotRequired) amount > 0
Логика:
amountпереводится получателю в полном объёме- Bounty никому не зачисляется (идёт получателю)
Callback: ✅ Вызывается
Функция forceWithdraw принимает массив pending withdrawals и для каждого: обнуляет сумму pending, переводит получателю полную сумму (без вычета bounty) и вызывает callback.
Структура данных
Storage маппинги
| Маппинг | Ключ | Описание |
|---|---|---|
| tokens_ | token address | Информация о токене, включая depositLimit |
| withdrawalLimits_ | token address | undeclared, daily, enabled |
| withdrawalPeriods_ | token → period ID | total (суммарный вывод), considered (одобренное governance) |
| pendingWithdrawals_ | recipient → ID | Параметры отложенного вывода |
| pendingWithdrawalsPerUser | recipient | Счётчик pending (используется как ID) |
| pendingWithdrawalsTotal | token address | Суммарный pending по токену |
ApproveStatus
| Значение | Статус | Описание |
|---|---|---|
| 0 | NotRequired | Одобрение не требуется (недостаток средств в MultiVault) |
| 1 | Required | Требуется одобрение (превышение лимитов) |
| 2 | Approved | Одобрено governance или withdrawGuardian |
| 3 | Rejected | Отклонено |
Управление лимитами
Установка лимитов
Все функции требуют модификатор onlyGovernance.
Deposit Limit
Функция setDepositLimit устанавливает максимальный баланс токена на MultiVault.
Withdrawal Limits
Доступны следующие функции управления:
- setDailyWithdrawalLimits — устанавливает суточный лимит (проверяет, что он не меньше undeclared)
- setUndeclaredWithdrawalLimits — устанавливает лимит на транзакцию (проверяет, что он не больше daily)
- enableWithdrawalLimits — включает лимиты для токена
- disableWithdrawalLimits — отключает лимиты для токена
События
| Событие | Параметры | Когда эмитится |
|---|---|---|
UpdateDailyWithdrawalLimits | token, limit | Изменён daily limit |
UpdateUndeclaredWithdrawalLimits | token, limit | Изменён undeclared limit |
UpdateWithdrawalLimitStatus | token, status | Включены/отключены лимиты |
PendingWithdrawalCreated | recipient, id, token, amount, payloadId | Создан pending при превышении лимитов |
PendingWithdrawalUpdateApproveStatus | recipient, id, approveStatus | Approve или Reject |
PendingWithdrawalWithdraw | recipient, id, amount | Автовывод при approve |
TIP
События Fill, Cancel, Bounty описаны в разделе Liquidity Request.
Права доступа
| Действие | Требуемая роль |
|---|---|
| Установить deposit limit | governance |
| Установить withdrawal limits | governance |
| Включить/отключить лимиты | governance |
| Approve/Reject pending | governance ИЛИ withdrawGuardian |
Коды ошибок
| Ошибка | Причина |
|---|---|
"Deposit: limits violated" | Депозит превышает depositLimit для токена |
"Settings: daily limit < undeclared" | Попытка установить daily < undeclared или undeclared > daily |
"Pending: wrong current approve status" | Попытка Approve/Reject для pending, который не в статусе Required |
"Pending: wrong approve status" | Указан недопустимый новый статус (не Approved и не Rejected) |
"Pending: zero amount" | Pending уже исполнен (amount = 0) |
"Pending: params mismatch" | Массовый Approve: длины массивов ID и статусов не совпадают |