Skip to main content
A treasury can let users burn share tokens to redeem a proportional share of its holdings. If it charges the user’s share tokens once but pays out once for every entry in a caller-controlled list, a user can submit [near, near, near] and receive NEAR tokens several times. Treat every user-supplied list as a list with duplicates unless you check otherwise. If your method charges once but processes every entry, a caller can repeat a value and receive the same benefit multiple times.

The vulnerable pattern

The vulnerable logic looks like this:
Pseudocode: vulnerable flow
The code assumes that token_ids contains each token only once. A caller controls that list, so that assumption is unsafe.

How the exploit happens

  1. The user redeems their share tokens once.
  2. They pass [near, near, near] instead of [near].
  3. The loop processes every near entry as a separate payout.
  4. The user’s NEAR balance is credited several times, even though they paid their share tokens only once.
Each later payout may be smaller because the treasury balance changed in the preceding iteration. It is still an unintended extra payout, and enough repeated entries can drain far more NEAR than the redemption should allow.

The safe pattern

Accept one token ID per redemption. Each call deducts the user’s share tokens before paying out:
Pseudocode: safe flow
If the user wants to redeem several token types, they call redeem several times. They cannot redeem more than their share balance because every call deducts share_amount. If an operation genuinely needs a list, deduct share_amount inside the loop before each payout. The user then pays the share amount for each entry, so a repeated token ID cannot create a free extra redemption.

General rule

Avoid caller-controlled lists when one item is enough. When you must process a list, charge or deduct the required resource for each item before granting its benefit.