# Plugin revert aborts core operation

Core paths calling plugin hooks without isolation, letting one plugin revert or lie to block slashing, claims, or removal

_Area: Smart contract access control and control flow. Seen in 2 disclosed reports; latest 2023-02-15. Updated 2026-10-25._

## How it works
A core contract (staking, registry, wallet) calls into a plugin or hook and treats the call as trusted. The plugin controls the return value or can simply revert. Because the core call is not isolated, one plugin's revert or false answer aborts the whole privileged operation: slashing, claiming, deactivation, or even the plugin's own removal. The broken assumption is that plugins are cooperative and their hooks are safe to call inline.

## Where it shows up
- A removal or deactivation path that asks the plugin itself whether it is done (`deactivated()`), so the plugin gates its own exit [E1](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AH-1.md).
- Plugin return values fed into unchecked sums or arithmetic, letting a plugin overflow a total [E1](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AH-1.md).
- Loops over plugins where one external call is wrapped in try-catch but a neighboring guard call (`requiresNotification()`) is not [E3](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AM-1.md).
- Bare `require()` on plugin-controlled values inside a privileged flow.
- Integration with an external contract whose newer version adds an interface check the plugin does not satisfy, so setup reverts [E2](https://bugbountylibrary.com/r/c4%3A2023-10-brahma%3AM-01.md).

## How to review
For every core path that calls a plugin, ask: can this call revert, and if so does it abort something the caller must be able to complete? The invariant: no single plugin can block slashing, claiming, deactivation, or its own removal. Check that removal does not depend on a plugin-supplied boolean; that plugin return values are bounded and checked before arithmetic; and that every plugin call in a loop is isolated (try-catch or equivalent) so one failure is skipped, not fatal. For external integrations, confirm the plugin implements any interface the host now requires [E2](https://bugbountylibrary.com/r/c4%3A2023-10-brahma%3AM-01.md).

## Variants seen
- Plugin refuses its own removal via `deactivated()` returning false, becoming unremovable [E1](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AH-1.md).
- Plugin returns a huge value from `claim()` to overflow the aggregate sum [E1](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AH-1.md).
- Plugin reverts `requiresNotification()` to abort `slash()` and let a colluder exit [E3](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AM-1.md).
- Missing interface support (EIP-165) makes guard installation revert, breaking integration [E2](https://bugbountylibrary.com/r/c4%3A2023-10-brahma%3AM-01.md).

## Impact and severity
High: frozen staking and claiming, evaded slashing, and unremovable malicious plugins [E1](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AH-1.md)[E3](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AM-1.md). Severity rises when the blocked path is a safety mechanism (slashing) or when the plugin cannot be removed, since there is no recovery short of upgrade.

## Why fixes fail
Wrapping only `notifyStakeChange` in try-catch while leaving `requiresNotification()` unguarded left the griefing path open [E3](https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AM-1.md); isolation must cover every plugin call on the path, not just one.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AH-1.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-10-brahma%3AM-01.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AM-1.md
