# 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)