Limits
What are Limits
The limit system in MultiVault protects against instant contract drainage in case of relay node compromise or vulnerability exploitation. Limits are applied to two types of operations:
- Deposit Limits — restrict the maximum amount of tokens that can be stored on MultiVault
- Withdrawal Limits — restrict the withdrawal amount per single transaction and cumulatively over 24 hours
Important
Limits exist only on the EVM network side.
Why are Limits Needed?
- Relay node compromise — limits slow down fund withdrawal and provide time to detect an attack
- Smart contract exploits — limits block large transactions
- Liquidity protection — prevent instant pool drainage
- Fraud detection window — exceeding limits creates a pending withdrawal requiring approval
Race condition on approve
Governance may approve a pending withdrawal, but the MultiVault balance may turn out to be insufficient — the status will change to Approved, but tokens will not be transferred. To complete the withdrawal, forceWithdraw() or Fill via LP will be required (see Liquidity Request).
Deposit Limits
Restrict the maximum token balance on MultiVault.
| Parameter | Description |
|---|---|
depositLimit | Maximum token balance on MultiVault |
| Applies to | Alien tokens only |
| When exceeded | Deposit is rejected (revert) |
A deposit is rejected if the sum of the current MultiVault balance and the deposit amount exceeds the established limit. If the limit is not set (equals zero), the check is skipped.
Why only for Alien
Native tokens on deposit (EVM→TVM transfer) are burned, not stored on MultiVault. Therefore, the MultiVault balance for native tokens is always ~0.
Withdrawal Limits
Restrict withdrawal amounts from MultiVault.
| Parameter | Description |
|---|---|
undeclared | Per-transaction limit |
daily | Per-period limit (24 hours) |
enabled | Flag to enable limits for the token |
| Applies to | Both Alien and Native tokens |
| When exceeded | Pending Withdrawal is created |
Invariant: daily >= undeclared
Withdrawal proceeds instantly if both conditions are met simultaneously. If limits are disabled for the token, the check is skipped.
Condition 1: Per-transaction (undeclared)
The current transaction amount must be strictly less than the per-transaction limit (undeclared).
Important: A transaction for exactly the limit amount requires approval.
Condition 2: Per-period (daily)
The sum of the current transaction amount plus already withdrawn during the period minus governance-approved amounts must be strictly less than the daily limit.
- Already withdrawn (total) — cumulative withdrawal for the current 24-hour period
- Approved (considered) — amounts approved by governance (subtracted to avoid blocking subsequent legitimate withdrawals)
Calculation Example
| Parameter | Value |
|---|---|
| Undeclared limit | 10,000 USDT |
| Daily limit | 50,000 USDT |
| Already withdrawn in period (total) | 30,000 USDT |
| Governance approved (considered) | 20,000 USDT |
| Withdrawal request | 15,000 USDT |
Check 1 (per-transaction):
15,000 < 10,000 → ❌ FAILCheck 2 (per-period):
15,000 + 30,000 - 20,000 = 25,000 < 50,000 → ✅ PASSResult: Although check 2 passed, check 1 failed → Pending Withdrawal is created with Required status.
24-Hour Period Mechanism
Period ID Calculation
Period ID is calculated by dividing the timestamp by the period duration (86400 seconds = 24 hours). All transactions within the same day receive the same period ID.
Examples
| Timestamp | Date/time (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 |
Automatic Reset
When transitioning to a new period, counters (total, considered) are automatically reset to zero (new entry in mapping).
Important: Uses eventTimestamp from the TVM event, not block.timestamp. This prevents manipulation through transaction execution delay.
Behavior When Limits Are Exceeded
When a withdrawal fails the limit check, a Pending Withdrawal with Required status is created — the same object as when liquidity is insufficient (see Liquidity Request), but with a different status and set of available actions.
Available Actions
A Pending Withdrawal with Required status awaits a governance decision:
| Action | Who can | Description |
|---|---|---|
| Approve | governance / withdrawGuardian | Approve the withdrawal — if liquidity is available, tokens are sent immediately |
| Reject | governance / withdrawGuardian | Reject the withdrawal — recipient can return tokens via Cancel |
| Force Withdraw | Anyone | After Approve — push through the withdrawal if it didn't execute automatically |
TIP
Fill, Bounty, and Cancel actions are available for NotRequired status — see Liquidity Request.
How it works in the contract
Both methods (saveWithdrawNative, saveWithdrawAlien) first create a Pending with NotRequired status. Then, if limits are not passed, the status is immediately updated to Required. For Native tokens, the only reason for Pending is limits, so the status is always Required. For Alien — it depends on what exactly failed: balance, limits, or both.
Pending Creation Diagram
Details: Approve or Reject
Who can call: Only governance or withdrawGuardian
Requirements:
- Current status must be
Required - Can only set to
ApprovedorRejected
Logic on Approve:
- If MultiVault balance is sufficient OR it's a Native token → automatic withdrawal
- Otherwise, just changes status to
Approved
Callback: ❌ NOT called
The setPendingWithdrawalApprove function checks that the current status is Required and that Approved or Rejected is being set. When setting Approved, if the MultiVault balance is sufficient or it's a Native token, withdrawal is automatically executed. In any case, the amount is added to considered for the current period.
Details: Force Withdraw (after Approve)
Who can call: Any address
Requirements:
- Status
Approved(orNotRequired) amount > 0
Logic:
amountis transferred to the recipient in full- Bounty is not credited to anyone (goes to the recipient)
Callback: ✅ Called
The forceWithdraw function accepts an array of pending withdrawals and for each: resets the pending amount to zero, transfers the full amount to the recipient (without deducting bounty), and calls the callback.
Data Structures
Storage Mappings
| Mapping | Key | Description |
|---|---|---|
| tokens_ | token address | Token information, including depositLimit |
| withdrawalLimits_ | token address | undeclared, daily, enabled |
| withdrawalPeriods_ | token → period ID | total (cumulative withdrawal), considered (governance-approved) |
| pendingWithdrawals_ | recipient → ID | Pending withdrawal parameters |
| pendingWithdrawalsPerUser | recipient | Pending counter (used as ID) |
| pendingWithdrawalsTotal | token address | Total pending by token |
ApproveStatus
| Value | Status | Description |
|---|---|---|
| 0 | NotRequired | Approval not required (insufficient funds on MultiVault) |
| 1 | Required | Approval required (limits exceeded) |
| 2 | Approved | Approved by governance or withdrawGuardian |
| 3 | Rejected | Rejected |
Limit Management
Setting Limits
All functions require the onlyGovernance modifier.
Deposit Limit
The setDepositLimit function sets the maximum token balance on MultiVault.
Withdrawal Limits
The following management functions are available:
- setDailyWithdrawalLimits — sets the daily limit (checks that it's not less than undeclared)
- setUndeclaredWithdrawalLimits — sets the per-transaction limit (checks that it's not more than daily)
- enableWithdrawalLimits — enables limits for the token
- disableWithdrawalLimits — disables limits for the token
Events
| Event | Parameters | When Emitted |
|---|---|---|
UpdateDailyWithdrawalLimits | token, limit | Daily limit changed |
UpdateUndeclaredWithdrawalLimits | token, limit | Undeclared limit changed |
UpdateWithdrawalLimitStatus | token, status | Limits enabled/disabled |
PendingWithdrawalCreated | recipient, id, token, amount, payloadId | Pending created when limits exceeded |
PendingWithdrawalUpdateApproveStatus | recipient, id, approveStatus | Approve or Reject |
PendingWithdrawalWithdraw | recipient, id, amount | Auto-withdrawal on approve |
TIP
Fill, Cancel, and Bounty events are described in the Liquidity Request section.
Access Rights
| Action | Required Role |
|---|---|
| Set deposit limit | governance |
| Set withdrawal limits | governance |
| Enable/disable limits | governance |
| Approve/Reject pending | governance OR withdrawGuardian |
Error Codes
| Error | Cause |
|---|---|
"Deposit: limits violated" | Deposit exceeds depositLimit for the token |
"Settings: daily limit < undeclared" | Attempt to set daily < undeclared or undeclared > daily |
"Pending: wrong current approve status" | Attempt to Approve/Reject a pending not in Required status |
"Pending: wrong approve status" | Invalid new status specified (not Approved or Rejected) |
"Pending: zero amount" | Pending already executed (amount = 0) |
"Pending: params mismatch" | Batch Approve: ID and status array lengths don't match |