Skip to content

[stable2512] Backport #11612#11637

Merged
EgorPopelyaev merged 3 commits intostable2512from
backport-11612-to-stable2512
Apr 10, 2026
Merged

[stable2512] Backport #11612#11637
EgorPopelyaev merged 3 commits intostable2512from
backport-11612-to-stable2512

Conversation

@paritytech-release-backport-bot
Copy link
Copy Markdown

Backport #11612 into stable2512 from dhirajs0.

See the documentation on how to use this bot.

@paritytech-release-backport-bot
Copy link
Copy Markdown
Author

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin backport-11612-to-stable2512
git worktree add --checkout .worktree/backport-11612-to-stable2512 backport-11612-to-stable2512
cd .worktree/backport-11612-to-stable2512
git reset --hard HEAD^
git cherry-pick -x ecada3402a70d906e10c6d33b0f42b6174fea119
git push --force-with-lease

@github-actions github-actions Bot added the A3-backport Pull request is already reviewed well in another branch. label Apr 3, 2026
@github-actions github-actions Bot requested a review from dhirajs0 April 3, 2026 18:36
@EgorPopelyaev
Copy link
Copy Markdown
Contributor

@dhirajs0 Hey Dhiraj, couls you check conflicts here?

…when parent bounty is not Active (#11612)

Fix an authorization bypass in `pallet-multi-asset-bounties` where any
signed account could
forcibly unassign an active child bounty's curator when the parent
bounty was not in `Active` state
(e.g., `CuratorUnassigned`). This also caused the child curator's native
balance hold (deposit) to
be permanently leaked — removed from pallet storage but never released
or burned on-chain.

**Root cause:** In `unassign_curator`, the `BountyStatus::Active`
branch's catch-all `Some(sender)`
arm used `if let Some(parent_curator) = parent_curator { ... }` with no
`else` clause. When
`parent_curator` was `None` (parent bounty not Active), the block was
silently skipped and execution
fell through to the state transition — no `BadOrigin` error was
returned.

No integration changes required for downstream projects. This is a fix
internal to
`pallet-multi-asset-bounties` with no public API changes. The extrinsic
signature and behavior for
authorized callers remain identical.

The fix restructures the `BountyStatus::Active` arm in
`unassign_curator` with two changes:

Previously, `CuratorDeposit::take()` was called unconditionally at the
top of the `Active` arm
(before verifying the caller). Now it is called inside each `match
maybe_sender` arm, only after the
caller is confirmed to be authorized. This prevents the deposit from
being removed from storage on
an unauthorized (and reverted) call path.

```diff
 BountyStatus::Active { ref curator, .. } => {
-    let maybe_curator_deposit =
-        CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id);
     match maybe_sender {
         None => {
-            if let Some(curator_deposit) = maybe_curator_deposit {
+            if let Some(curator_deposit) =
+                CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
+            {
                 T::Consideration::burn(curator_deposit, curator);
             }
         },
```

The catch-all `Some(sender)` arm now uses
`parent_curator.ok_or(BadOrigin)?` followed by an
`ensure!`. When `parent_curator` is `None`, the call is immediately
rejected with `BadOrigin`.

```diff
         Some(sender) => {
-            if let Some(parent_curator) = parent_curator {
-                if sender == parent_curator && *curator != parent_curator {
-                    if let Some(curator_deposit) = maybe_curator_deposit {
-                        T::Consideration::burn(curator_deposit, curator);
-                    }
-                } else {
-                    return Err(BadOrigin.into());
-                }
+            let parent_curator = parent_curator.ok_or(BadOrigin)?;
+            ensure!(
+                sender == parent_curator && *curator != parent_curator,
+                BadOrigin
+            );
+            if let Some(curator_deposit) =
+                CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
+            {
+                T::Consideration::burn(curator_deposit, curator);
             }
         },
```

A comprehensive test
(`unprivileged_caller_cannot_unassign_active_child_curator_when_parent_not_active`)
is added that:

1. Creates an active child bounty with a separate child curator.
2. Has the parent curator voluntarily unassign (putting parent into
`CuratorUnassigned`).
3. Asserts that an unprivileged attacker is rejected with `BadOrigin`.
4. Verifies the child bounty remains `Active`, the curator deposit stays
in storage, and the
   balance hold is intact.
5. Confirms the child curator can still voluntarily unassign themselves
and that the deposit is
   properly released.

* [x] My PR includes a detailed description as outlined in the
"Description" and its two subsections above.
* [x] My PR follows the [labeling requirements]
* [x] I have made corresponding changes to the documentation (if
applicable)
* [x] I have added tests that prove my fix is effective or that my
feature works (if applicable)

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit ecada34)
@dhirajs0 dhirajs0 force-pushed the backport-11612-to-stable2512 branch from 06599d6 to c8ba830 Compare April 8, 2026 13:54
@dhirajs0 dhirajs0 marked this pull request as ready for review April 8, 2026 13:56
@dhirajs0 dhirajs0 requested a review from a team as a code owner April 8, 2026 13:56
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

This pull request is amending an existing release. Please proceed with extreme caution,
as to not impact downstream teams that rely on the stability of it. Some things to consider:

  • Backports are only for 'patch' or 'minor' changes. No 'major' or other breaking change.
  • Should be a legit fix for some bug, not adding tons of new features.
  • Must either be already audited or not need an audit.
Emergency Bypass

If you really need to bypass this check: add validate: false to each crate
in the Prdoc where a breaking change is introduced. This will release a new major
version of that crate and all its reverse dependencies and basically break the release.

@EgorPopelyaev EgorPopelyaev enabled auto-merge (squash) April 10, 2026 15:03
@EgorPopelyaev EgorPopelyaev merged commit 7b51b90 into stable2512 Apr 10, 2026
244 of 260 checks passed
@EgorPopelyaev EgorPopelyaev deleted the backport-11612-to-stable2512 branch April 10, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A3-backport Pull request is already reviewed well in another branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants