---
# System prepended metadata

title: 'Close 7,138 Spam Bounties on Asset Hub Polkadot'

---

# Close 7,138 Spam Bounties on Asset Hub Polkadot

> **Track:** Treasurer (Track 11) | **Preimage size:** ~29 KB | **Decision deposit:** 25,000 DOT
> **Call data:** [Download hex](https://drive.google.com/file/d/1t33MJYmCGIsUPEii4lwraEuKLuWXYBqq/view?usp=sharing)

---

## Summary

This proposal closes 7,138 spam bounties created on Asset Hub Polkadot by exploiting a misconfigured `BountyDepositBase` parameter. The deposit to propose a bounty was approximately **0.002 DOT**, enabling a small number of accounts to flood the bounties system at negligible cost.

The spam bounties consume on-chain storage, degrade the UX for legitimate bounty proposers and curators, and represent a systemic nuisance. This referendum uses the **Treasurer origin** to call `bounties.closeBounty()` on each spam bounty, which slashes the proposer's deposit to the Treasury and removes the bounty from storage.

A fix has been submitted via [polkadot-fellows/runtimes#1134](https://github.com/polkadot-fellows/runtimes/pull/1134) targeting the 2.2.1 release, which sets `BountyDepositBase = 10 DOT`. However, the existing spam bounties still need to be cleaned up.

---

## Spam Attack Details

- **7,138 spam bounties** were created on Asset Hub Polkadot (bounty IDs 71–7208)
- **2 primary spammer accounts** were responsible for 99.96% of the spam:
  - `15GJyYcjB9UHcK9Tig1DafYftjrmf8FpNNTQpJXbz4T8ao4T` — 5,500 bounties, all with description `<><`
  - `16A1vM59H27EhTKiHoGmUP7qaZEAtqjCRCRQ2GEpTRBXvLEU` — 1,635 bounties, descriptions `<><`, `><>`, and a sandwich joke
- 3 additional accounts created 1 low-quality bounty each (IDs 71, 72, 73)
- All spam bounties are in **Proposed** status — never approved by governance
- All spam bounties have proposer bonds **below 0.01 DOT**

---

## What This Proposal Does

For each of the 7,138 spam bounties, `bounties.closeBounty(bountyId)` is called via `utility.forceBatch`. This:

- **Slashes** the proposer's deposit to the Treasury (not returned to the spammer)
- **Removes** the bounty data and description from on-chain storage
- **Frees storage**, reducing chain bloat

`utility.forceBatch` is used so that if any individual close call fails (e.g. a bounty was already closed), the remaining calls continue executing.

---

## Legitimate Bounties — NOT Affected

The following 18 legitimate bounties were carefully excluded from this cleanup:

| ID | Status | Description | Value (DOT) |
|----|--------|-------------|-------------|
| 11 | Active | Anti-Scam Bounty | 7,500 |
| 17 | Active | Events Community Bounty | 1,000,000 |
| 22 | Active | Polkadot Assurance Legion Bounty | 540,000 |
| 24 | Active | Moderation Team Bounty | 25,004 |
| 31 | Active | Public RPCs for Relay and System Chains | 45,084 |
| 32 | Active | System Parachains Collator Bounty | 15,883 |
| 36 | Active | DeFi Infrastructure and Tooling Bounty | 592,973 |
| 37 | Active | Paseo - Developer Testnet | 125,018 |
| 38 | Active | Games Bounty | 100,000 |
| 43 | Active | Meetups Bounty | 5,000 |
| 44 | Active | POLKADOT <> KUSAMA BRIDGE SECURITY BOUNTY | 30,581 |
| 50 | Active | Infrastructure Builders Program | 461,054 |
| 59 | Active | Open Source Developer Grants Program | 96,000 |
| 63 | Active | Polkadot Fast-Grants Bounty | 67,705 |
| 64 | Active | OG Rust Bounties | 250,000 |
| 67 | Proposed | NFTMozaic MVP Grants | 110,000 |
| 69 | Proposed | Polkadot to the public! | 2,000 |
| 70 | Proposed | ink! Alliance | 261,137 |

---

## Spam Classification Methodology

A bounty was classified as spam only when it matched **at least 2 independent signal categories**, ensuring no false positives:

| Signal | Description |
|--------|-------------|
| Known attacker accounts | Previously identified spammer addresses |
| High volume | Accounts proposing 10+ bounties |
| 100% Proposed status | Never approved by governance |
| Burst sequential IDs | Runs of 5+ consecutive bounty IDs from one proposer |
| Low description variety | Same description reused across many bounties (e.g. `<><` used 7,000+ times) |
| Abnormally low bonds | Proposer bond below 0.1 DOT (exploiting the misconfigured deposit) |

---

## Governance Path

| Parameter | Value |
|-----------|-------|
| Track | Treasurer (Track 11) |
| Origin | Treasurer — authorized as `RejectOrigin` for `pallet_bounties` |
| Decision deposit | 25,000 DOT (returned upon approval) |
| Number of referenda | 1 |
| Preimage size | 28,987 bytes (~29 KB) |

---

## Single Referendum — How It Works

All 7,138 spam bounties cannot be closed in a single block — the proof size would be ~26 MB, exceeding the 5 MB parachain PoV limit. Instead, `pallet_scheduler` is used to spread execution across 36 blocks after enactment each with a gap of 10 blocks, requiring only **1 referendum**.

### Call Structure

```
utility.forceBatch([
  scheduler.scheduleAfter(10,  None, 0, utility.forceBatch([closeBounty(71)   .. closeBounty(270)])),  // 200 calls
  scheduler.scheduleAfter(20,  None, 0, utility.forceBatch([closeBounty(271)  .. closeBounty(470)])),  // 200 calls
  ...
  scheduler.scheduleAfter(360, None, 0, utility.forceBatch([closeBounty(7071) .. closeBounty(7208)])), // 138 calls
])
```

### Execution Flow

1. Referendum passes → the outer `utility.forceBatch` executes on the enactment block
2. 36 `scheduler.scheduleAfter` calls are made, each storing a sub-batch for a future block (+10 to +360 blocks from enactment)
3. Over the next 360 blocks, each sub-batch fires with **Treasurer origin preserved** from the referendum
4. Each sub-batch calls `bounties.closeBounty()` for up to 200 bounties — fitting within a single block's PoV limit
5. `utility.forceBatch` ensures partial failures don't revert the rest

### Why This Is Safe

| Constraint | Requirement | Actual | Status |
|------------|-------------|--------|--------|
| `ScheduleOrigin` | Root or Treasurer | Treasurer (from referendum) | ✅ |
| `MaxScheduledPerBlock` | 50 | 1 per block | ✅ |
| Per-block PoV limit | 5 MB | ~728 KB per sub-batch (200 × 3,642 bytes) | ✅ |
| `forceBatch` | Partial failure tolerance | Used at both levels | ✅ |

---

## Sub-Batch Breakdown

<details>
<summary>Click to expand all 36 sub-batches</summary>

| Sub-batch | Delay | Bounty IDs | Count |
|-----------|-------|------------|-------|
| 1 | +10 blocks | 71–270 | 200 |
| 2 | +20 blocks | 271–470 | 200 |
| 3 | +30 blocks | 471–670 | 200 |
| 4 | +40 blocks | 671–870 | 200 |
| 5 | +50 blocks | 871–1070 | 200 |
| 6 | +60 blocks | 1071–1270 | 200 |
| 7 | +70 blocks | 1271–1470 | 200 |
| 8 | +80 blocks | 1471–1670 | 200 |
| 9 | +90 blocks | 1671–1870 | 200 |
| 10 | +100 blocks | 1871–2070 | 200 |
| 11 | +110 blocks | 2071–2270 | 200 |
| 12 | +120 blocks | 2271–2470 | 200 |
| 13 | +130 blocks | 2471–2670 | 200 |
| 14 | +140 blocks | 2671–2870 | 200 |
| 15 | +150 blocks | 2871–3070 | 200 |
| 16 | +160 blocks | 3071–3270 | 200 |
| 17 | +170 blocks | 3271–3470 | 200 |
| 18 | +180 blocks | 3471–3670 | 200 |
| 19 | +190 blocks | 3671–3870 | 200 |
| 20 | +200 blocks | 3871–4070 | 200 |
| 21 | +210 blocks | 4071–4270 | 200 |
| 22 | +220 blocks | 4271–4470 | 200 |
| 23 | +230 blocks | 4471–4670 | 200 |
| 24 | +240 blocks | 4671–4870 | 200 |
| 25 | +250 blocks | 4871–5070 | 200 |
| 26 | +260 blocks | 5071–5270 | 200 |
| 27 | +270 blocks | 5271–5470 | 200 |
| 28 | +280 blocks | 5471–5670 | 200 |
| 29 | +290 blocks | 5671–5870 | 200 |
| 30 | +300 blocks | 5871–6070 | 200 |
| 31 | +310 blocks | 6071–6270 | 200 |
| 32 | +320 blocks | 6271–6470 | 200 |
| 33 | +330 blocks | 6471–6670 | 200 |
| 34 | +340 blocks | 6671–6870 | 200 |
| 35 | +350 blocks | 6871–7070 | 200 |
| 36 | +360 blocks | 7071–7208 | 138 |

</details>

---

## Full List of Spam Bounty IDs

7,138 bounties — IDs **71 through 7208**, excluding the following legitimate bounty IDs:

> `11, 17, 22, 24, 31, 32, 36, 37, 38, 43, 44, 50, 59, 63, 64, 67, 69, 70`

---

## How to Verify the Call Data

You can independently verify the preimage without trusting this post:

1. Download the call data hex from the [link above](https://drive.google.com/file/d/1t33MJYmCGIsUPEii4lwraEuKLuWXYBqq/view?usp=sharing)
2. Go to [polkadot.js.org/apps](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fasset-hub-polkadot-rpc.n.dwellir.com) connected to **Asset Hub Polkadot**
3. Navigate to **Developer → Extrinsics → Decode**
4. Paste the hex — you should see the outer `utility.forceBatch` containing 36 `scheduler.scheduleAfter` calls, each wrapping a `utility.forceBatch` of `bounties.closeBounty` calls
5. Cross-check the preimage hash on-chain via **Developer → Chain State → preimage → preimageFor**

---

## Preventive Measures

The root cause has been addressed in [polkadot-fellows/runtimes#1134](https://github.com/polkadot-fellows/runtimes/pull/1134):

- `BountyDepositBase` increased from ~0.002 DOT → **10 DOT**

This makes future spam attacks economically unviable.

---

## References

- [Bounties UI on Asset Hub Polkadot](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fasset-hub-polkadot-rpc.n.dwellir.com#/bounties)
- [Deposit fix PR — polkadot-fellows/runtimes#1134](https://github.com/polkadot-fellows/runtimes/pull/1134)
- [Call data hex](https://drive.google.com/file/d/1t33MJYmCGIsUPEii4lwraEuKLuWXYBqq/view?usp=sharing)