Skip to content

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:

  1. Deposit Limits — restrict the maximum amount of tokens that can be stored on the vault
  2. Withdrawal Limits — restrict the withdrawal amount per single transaction and cumulatively over 24 hours

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

Types of Limits

1. Deposit Limits

Restrict the maximum token balance on the vault.

ParameterDescription
depositLimitMaximum token balance on vault
Applies toAlien tokens only
When exceededDeposit is rejected (revert)

Why only for Alien: Native tokens are burned on deposit, not stored on the vault. Therefore, the vault balance for native tokens is always ~0.

2. Withdrawal Limits

Restrict withdrawal amounts from the vault.

ParameterDescription
undeclaredPer-transaction limit
dailyPer-period limit (24 hours)
enabledFlag to enable limits for the token
Applies toBoth Alien and Native tokens
When exceededPending Withdrawal is created

Invariant: daily >= undeclared

Deposit Limits

Validation Formula

A deposit is rejected if the sum of the current vault balance and the deposit amount exceeds the established limit. If the limit is not set (equals zero), the check is skipped.

Application by Token Type

Token TypeApplied?Reason
Alien✅ YesTokens are stored on vault
Native❌ No (effectively)Tokens are burned, vault balance = 0

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

ParameterValue
Undeclared limit10,000 USDT
Daily limit50,000 USDT
Already withdrawn in period (total)30,000 USDT
Governance approved (considered)20,000 USDT
Withdrawal request15,000 USDT

Check 1 (per-transaction):

15,000 < 10,000 → ❌ FAIL

Check 2 (per-period):

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

Result: 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

TimestampDate/time (UTC)Period ID
17040672002024-01-01 00:00:0019723
17041535992024-01-01 23:59:5919723
17041536002024-01-02 00:00:0019724

Period Parameters

For each period, two values are stored:

  • total — cumulative withdrawal for the period (including pending)
  • considered — amounts approved by governance

Automatic Reset

When transitioning to a new period, counters 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.

Reasons for Creating Pending Withdrawal

Withdrawal enters Pending for two reasons:

1. Limit Exceeded

Token TypeStatusDescription
NativeRequiredAlways set to Required
AlienRequiredSet to Required if limits exceeded

2. Insufficient Funds on Vault

Token TypeOccurs?StatusReason
Native❌ NoToken is minted, not stored on vault
Alien✅ YesNotRequiredVault may not have sufficient balance

Pending Creation Diagram



Actions with Pending Withdrawal

1. Set New Bounty Value

Who can call: Only recipient

Restrictions:

  • Bounty is set only for Alien tokens (for Native in EVM)
  • By default, pending is created with bounty = 0

The setPendingWithdrawalBounty function checks that the token is not Native and that the bounty does not exceed the withdrawal amount, then records the new bounty value.

Note: When calling saveWithdrawAlien, you can specify the bounty immediately if the transaction sender is the withdrawal recipient.


2. Cancel Fully or Partially

Who can call: Only recipient

Restrictions:

  • Available only for Alien tokens
  • Available only with NotRequired or Approved status
  • When partially canceling, can set new bounty

Result: Creates a reverse transfer to the TVM network for the specified amount

The cancelPendingWithdrawal function checks that the token is not Native and that the cancellation amount is correct. Then it decreases the pending amount by the specified value, initiates a reverse transfer to TVM, and optionally sets a new bounty for the remaining amount.


3. Approve or Reject (for Required Status)

Who can call: Only governance or withdrawGuardian

Requirements:

  • Current status must be Required
  • Can only set to Approved or Rejected

Logic on Approve:

  • If vault 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 vault 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.


4. Force Withdraw Manually

Who can call: Any address

Requirements:

  • Status NotRequired or Approved
  • amount > 0

Logic:

  • amount is 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.


5. Close via Deposit

Who can call: Any address (usually arbitrageur)

Requirements:

  • Pending withdrawals status: NotRequired or Approved
  • Deposit token matches the pending token

Logic:

  1. User sends a deposit specifying pending withdrawals and minimum total bounty
  2. For each pending: recipient receives amount minus bounty
  3. Callback is called for each closed pending
  4. Creates an EVM→TVM transfer for the deposit amount plus accumulated bounties minus fees

The function iterates through specified pending withdrawals, accumulates bounties, transfers amounts minus bounty to recipients, and calls callbacks. Then checks that total bounty is not less than expected and creates a transfer to TVM.


Summary Table of Actions

ActionWho CanRequired StatusCallbackBounty
Set BountyrecipientAnyNew value is set
CancelrecipientNotRequired / ApprovedCan set new
Approve/Rejectgovernance / withdrawGuardianRequired❌ No
Force WithdrawAnyNotRequired / Approved✅ YesNot deducted, goes to recipient
Close via DepositAnyNotRequired / Approved✅ YesGoes to depositor (arbitrageur)

Data Structures

WithdrawalLimits

Structure stores withdrawal limits for a token:

  • undeclared — per-transaction limit
  • daily — 24-hour period limit
  • enabled — flag to enable limits

WithdrawalPeriodParams

Structure stores 24-hour period parameters:

  • total — cumulative withdrawal for the period
  • considered — amounts approved by governance

PendingWithdrawalParams

Structure stores pending withdrawal parameters:

  • token — token address
  • amount — amount to withdraw (after fees)
  • bounty — reward for arbitrageur
  • timestamp — TVM event timestamp
  • approveStatus — approval status
  • chainId — source Chain ID
  • callback — callback data after withdrawal

ApproveStatus

Possible approval statuses for pending withdrawal:

  • NotRequired (0) — approval not required (insufficient funds on vault)
  • Required (1) — approval required (limits exceeded)
  • Approved (2) — approved by governance or withdrawGuardian
  • Rejected (3) — rejected

Storage Mappings

Data is stored in the following mappings:

  • tokens_ — token information, including depositLimit
  • withdrawalLimits_ — withdrawal limits by token
  • withdrawalPeriods_ — period parameters (token → period ID → parameters)
  • pendingWithdrawals_ — pending withdrawals (user → ID → parameters)
  • pendingWithdrawalsPerUser — pending count for each user (used as ID)
  • pendingWithdrawalsTotal — total pending by token

Period duration — 86400 seconds (24 hours).

Limit Management

Setting Limits

All functions require the onlyGovernance modifier.

Deposit Limit

The setDepositLimit function sets the maximum token balance on the vault.

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

EventParametersWhen Emitted
UpdateDailyWithdrawalLimitstoken, limitDaily limit changed
UpdateUndeclaredWithdrawalLimitstoken, limitUndeclared limit changed
UpdateWithdrawalLimitStatustoken, statusLimits enabled/disabled
PendingWithdrawalCreatedrecipient, id, token, amount, payloadIdPending created
PendingWithdrawalUpdateApproveStatusrecipient, id, approveStatusStatus changed
PendingWithdrawalUpdateBountyrecipient, id, bountyBounty changed
PendingWithdrawalWithdrawrecipient, id, amountAuto-withdrawal on approve
PendingWithdrawalForcerecipient, idForce withdraw
PendingWithdrawalCancelrecipient, id, amountPending cancelled
PendingWithdrawalFillrecipient, idClosed via deposit

Access Rights

ActionRequired Role
Set deposit limitgovernance
Set withdrawal limitsgovernance
Enable/disable limitsgovernance
Approve/Reject pendinggovernance OR withdrawGuardian
Set bountyrecipient of pending withdrawal
Cancel pendingrecipient of pending withdrawal
Force withdrawAny address
Close via depositAny address

Risks and Edge Cases

1. Bypassing Limits via Split Transactions

Risk: Attacker splits a large withdrawal into many small ones.

Protection: Daily limit tracks cumulative withdrawal over 24 hours.

2. daily < undeclared

Risk: Incorrect configuration.

Protection: Validation require(daily >= undeclared) in both setter functions.

3. Limits Disabled (enabled = false)

Risk: If limits are not set for a token, protection doesn't apply.

Mitigation: Governance should set limits for each new token.

4. Timestamp Manipulation

Risk: Attacker delays transaction until new period.

Protection: Uses eventTimestamp from TVM event, not block.timestamp.

5. Race Condition on Approve

Risk: Governance approves pending, but vault balance is insufficient.

Result: Status changes to Approved, but tokens are not transferred. Requires forceWithdraw().

6. Cancel Unavailable for Native Tokens

Limitation: cancelPendingWithdrawal() is available only for Alien tokens.

Reason: Native tokens exist only in the TVM network. Cannot cancel pending.

7. Strict Inequality in Check

Feature: A transaction for exactly the limit amount requires approval (amount < limit, not <=).

ChainConnect Bridge Documentation