> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blend.money/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Deep Dive

> Contract-level security: transaction guards, rate limiting, reentrancy protection, slippage controls, access control, sanctions screening, and haltable operations.

Every transaction through Blend passes through 4 independent security layers before it executes: transaction guards, rate limiting, reentrancy protection, and slippage verification.

## Transaction guards

Every Safe has a `RolesGuard` installed as its transaction guard. The guard runs two pre-execution hooks.

**`checkTransaction`** fires before any direct transaction. When the system is paused, only Safe owners can execute. Non-owners receive a `SystemPaused()` revert.

**`checkModuleTransaction`** fires before any module-initiated transaction. When paused, ALL module transactions are blocked. There is no owner bypass for module calls. This means the automation layer stops completely during a pause, even if the Safe owner tries to trigger it.

The pause mechanism is controlled by `PAUSER_ROLE` via `pause(bool _shouldPause)`. This is a kill switch for the automation layer that does not affect the owner's ability to withdraw directly.

## Rate limiting

Every rebalance and vault action is rate-limited per (safe, vault) pair. The key is `keccak256(abi.encodePacked(safe, vault))`.

`MIN_SECONDS_BETWEEN_OPERATIONS` is set between 1 and 300 seconds at deployment. The contract enforces strictly-greater-than comparison against the last operation timestamp.

The timestamp is set before execution. If the transaction reverts, all state changes (including the timestamp) roll back.

| Parameter            | Value                             |
| -------------------- | --------------------------------- |
| **Rate limit key**   | `keccak256(safe, vault)` per pair |
| **Min interval**     | 1-300 seconds (set at deployment) |
| **Failure behavior** | Timestamp still updates           |
| **Scope**            | Per safe/vault pair, not global   |

## Reentrancy protection

The StrategyManager uses separate boolean guards for two execution paths.

`isRebalanceInitiated` is set to true during `executeRebalance` and checked by downstream contracts. `isVaultActionInitiated` is set to true during `executeVaultAction`. These are independent flags, not a shared mutex.

This separation matters because vault actions and rebalances call different controller contracts. A rebalance cannot trick a vault action controller into executing, and vice versa.

## Slippage protection

Four independent layers protect against unfavorable execution.

**Layer 1: Governance maximum.** `maxSlippageBps` is a hard ceiling set by governance. No swap can exceed this tolerance, regardless of what the caller requests.

**Layer 2: Per-swap limit.** Every swap call includes a `slippageBps` parameter. The SwapAdapter rejects any value exceeding `maxSlippageBps`.

**Layer 3: Oracle verification.** The SwapAdapter calculates the expected output using `PriceLib.quoteToBase` (or `baseToQuote` for reverse swaps) and then applies the slippage tolerance to derive a minimum acceptable output. The actual swap result must meet or exceed this minimum.

**Layer 4: Off-chain cross-check.** For vault deposits and withdrawals, MorphoVaultLib validates against an expectation computed off-chain. The caller passes in `assetPerShare`, and the vault's actual `assets/shares` ratio must stay within its bounds. Slippage is never accepted on oracle prices alone. The oracle check and the off-chain expectation both have to agree.

Beyond these layers, `checkSwapInvariants` enforces a zero-balance invariant. The SwapAdapter must hold zero tokens of both the input and output before and after every swap. This prevents residual token accumulation that could be drained.

```solidity theme={"theme":{"light":"github-light","dark":"github-dark"}}
// Simplified slippage check flow
uint256 expected = PriceLib.quoteToBase(inputAmount, oracle);
uint256 minimum = PriceLib.applySlippage(expected, slippageBps);
require(actualOutput >= minimum, "SlippageExceeded");
```

## Access control

Multiple layers restrict who can call what.

**`onlyExecutor`** - `StrategyManager.executeRebalance` and `executeVaultAction` are restricted to a single authorized executor address. This is the off-chain worker that triggers operations.

**`_onlyDelegateCall`** - All VaultControllers, including `UserWithdrawController`, compare `address(this)` against an immutable `SELF` address. This ensures the code only runs via delegatecall from a Safe, never as a standalone call.

**Trusted broadcaster and chain** - `RolesReceiver` validates both the broadcaster address AND the source chain ID on every cross-chain message. Both are immutable, set at construction. `setPeer()` always reverts. An attacker would need to compromise LayerZero AND match both trust anchors.

**Temporal access** - `WhitelistedSwapAdapter` only allows swaps during active `isRebalanceInitiated()` or `isVaultActionInitiated()` execution windows. Outside those windows, swap calls revert.

**Single-owner validation** - `UserWithdrawController` requires the Safe to have exactly one owner. This resolves the withdrawal recipient on-chain without relying on off-chain input.

## Sanctions screening

Blend screens wallet addresses against OFAC and global sanctions lists at three stages before any funds move. This is the implementation behind [Compliance Controls](/blend/compliance).

| Stage                     | When                          | What happens                         |
| ------------------------- | ----------------------------- | ------------------------------------ |
| **Account resolution**    | `GET /account` or SIWE verify | EOA screened against Chainalysis API |
| **Deposit quote**         | `quoteDeposit`                | Deposit EOA screened separately      |
| **Account-scoped routes** | Every authenticated request   | Account OFAC flag checked            |

The screening uses the Chainalysis sanctions API with a Redis-backed cache. Cache misses fall back to the database or a fresh Chainalysis screen. If the Chainalysis API is unreachable, the check fails closed (blocks the request with 503).

<Warning>Sanctions screening is a legal compliance requirement. It cannot be bypassed or disabled.</Warning>

## Haltable operations

The API supports a per-account-type halt state. When `isHalted` is true, the API returns HTTP `423 Locked` for deposit-related operations.

Withdrawals are intentionally allowed during halt. Users can always retrieve their funds, even when the system is in a degraded state.

| Operation                       | During halt   |
| ------------------------------- | ------------- |
| **Deposit quotes**              | Blocked (423) |
| **Deposit session lock/submit** | Blocked (423) |
| **Withdrawals**                 | Allowed       |
| **Balance and discovery**       | Allowed       |
| **Withdrawal quotes**           | Allowed       |
| **Balance queries**             | Allowed       |
| **Discovery**                   | Allowed       |

The halt flag is set at the account type level, not globally. One platform's account type can be halted while others continue operating.

<CardGroup cols={2}>
  <Card title="Architecture overview" icon="diagram-project" href="/architecture/overview">
    See how Safe isolation and cross-chain coordination work.
  </Card>

  <Card title="Contracts" icon="cube" href="/architecture/contracts">
    Explore the 9 contract systems powering the protocol.
  </Card>

  <Card title="Security model" icon="lock" href="/blend/security">
    Understand the defense-in-depth model and real-world scenarios.
  </Card>

  <Card title="Audits" icon="file-certificate" href="/resources/audits">
    Review independent security audit reports.
  </Card>
</CardGroup>
