# Substrate Vulnerability Patterns (7 Patterns)

This document contains detailed descriptions, detection patterns, and mitigations for 7 critical Substrate/FRAME vulnerabilities.

---

## 6.1 ARITHMETIC OVERFLOW ⚠️ CRITICAL

**Description**: Primitive integer types wrap in release mode instead of panicking on overflow/underflow. In debug mode they panic, but production builds silently produce incorrect values.

**Detection Patterns**:
```rust
// VULNERABLE: Direct arithmetic on primitive types
#[pallet::call]
impl<T: Config> Pallet<T> {
    pub fn transfer(origin: OriginFor<T>, amount: u128) -> DispatchResult {
        let sender = ensure_signed(origin)?;
        let mut balance = Self::balance_of(&sender);

        // OVERFLOW: balance - amount wraps in release mode!
        balance = balance - amount;  // If amount > balance, wraps to huge number

        // UNDERFLOW: balance + amount wraps in release mode!
        balance = balance + amount;  // If balance + amount > u128::MAX, wraps to small number

        Self::set_balance(&sender, balance);
        Ok(())
    }
}

// VULNERABLE: Multiplication overflow
let total = price * quantity;  // Wraps if price * quantity > u128::MAX
```

**What to Check**:
- [ ] NO direct arithmetic operators (`+`, `-`, `*`, `/`) on primitive types in dispatchables
- [ ] ALL arithmetic uses `checked_*`, `saturating_*`, or `overflowing_*` methods
- [ ] Balance updates use safe arithmetic
- [ ] Reward/fee calculations use safe arithmetic
- [ ] Type conversions checked for overflow

**Mitigation**:
```rust
// SECURE: Use checked arithmetic
#[pallet::call]
impl<T: Config> Pallet<T> {
    pub fn transfer(origin: OriginFor<T>, amount: u128) -> DispatchResult {
        let sender = ensure_signed(origin)?;
        let balance = Self::balance_of(&sender);

        // checked_sub returns None on underflow
        let new_balance = balance.checked_sub(amount)
            .ok_or(Error::<T>::InsufficientBalance)?;

        Self::set_balance(&sender, new_balance);
        Ok(())
    }

    pub fn calculate_reward(stakes: u128, rate: u128) -> Result<u128, Error<T>> {
        // checked_mul returns None on overflow
        stakes.checked_mul(rate)
            .ok_or(Error::<T>::ArithmeticOverflow)
    }
}

// SECURE: Use saturating arithmetic (when clamping is acceptable)
use sp_runtime::traits::Saturating;

let new_value = old_value.saturating_add(increment);  // Clamps at u128::MAX
let new_value = old_value.saturating_sub(decrement);  // Clamps at 0
```

**Available Safe Methods**:
```rust
// CheckedAdd, CheckedSub, CheckedMul, CheckedDiv traits
value.checked_add(other)?
value.checked_sub(other)?
value.checked_mul(other)?
value.checked_div(other)?

// Saturating trait (clamps at min/max)
value.saturating_add(other)
value.saturating_sub(other)
value.saturating_mul(other)

// Overflowing (returns bool flag)
let (result, overflowed) = value.overflowing_add(other);
ensure!(!overflowed, Error::<T>::ArithmeticOverflow);
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/arithmetic_overflow

---

## 4.2 DON'T PANIC ⚠️ CRITICAL - DoS

**Description**: Panics in dispatchable functions cause the node to stop processing blocks, enabling DoS attacks. Production runtime must never panic.

**Detection Patterns**:

### Pattern 1: Array Indexing
```rust
// VULNERABLE: Direct array indexing panics on out-of-bounds
pub fn get_validator(index: u32) -> DispatchResult {
    let validators = Self::validator_set();
    let validator = validators[index as usize];  // PANIC if index >= len!
    // ...
}
```

### Pattern 2: unwrap() / expect()
```rust
// VULNERABLE: unwrap() panics on None
pub fn process_data(origin: OriginFor<T>, data: Vec<u8>) -> DispatchResult {
    let value = Self::parse_data(&data).unwrap();  // PANIC on parse error!
    // ...
}

// VULNERABLE: expect() also panics
let sender = ensure_signed(origin).expect("must be signed");  // PANIC!
```

### Pattern 3: Type Conversions
```rust
// VULNERABLE: as casts can panic or produce wrong values
pub fn set_value(origin: OriginFor<T>, value: u128) -> DispatchResult {
    let small_value = value as u32;  // Truncates if value > u32::MAX!
    Self::store_value(small_value);
}
```

### Pattern 4: Missing Input Validation
```rust
// VULNERABLE: No bounds checking on user input
pub fn divide(numerator: u128, denominator: u128) -> DispatchResult {
    let result = numerator / denominator;  // PANIC if denominator == 0!
    // ...
}
```

**What to Check**:
- [ ] NO array/slice indexing without bounds check
- [ ] NO `unwrap()`, `expect()` in dispatchables
- [ ] NO `as` casts without validation
- [ ] ALL user input validated before use
- [ ] Division operations check for zero divisor
- [ ] All `?` operator paths return DispatchError, not panic

**Mitigation**:
```rust
// SECURE: Bounds checking for array access
pub fn get_validator(index: u32) -> DispatchResult {
    let validators = Self::validator_set();
    let validator = validators.get(index as usize)
        .ok_or(Error::<T>::ValidatorIndexOutOfBounds)?;
    // ...
}

// SECURE: Handle Result/Option properly
pub fn process_data(origin: OriginFor<T>, data: Vec<u8>) -> DispatchResult {
    let value = Self::parse_data(&data)
        .map_err(|_| Error::<T>::InvalidData)?;
    // ...
}

// SECURE: Safe type conversions
use sp_runtime::traits::TryConvert;

pub fn set_value(origin: OriginFor<T>, value: u128) -> DispatchResult {
    let small_value: u32 = value.try_into()
        .map_err(|_| Error::<T>::ValueTooLarge)?;
    Self::store_value(small_value);
    Ok(())
}

// SECURE: Input validation with ensure!
pub fn divide(numerator: u128, denominator: u128) -> DispatchResult {
    ensure!(denominator != 0, Error::<T>::DivisionByZero);
    let result = numerator / denominator;  // Safe now
    // ...
}

// SECURE: Comprehensive validation
#[pallet::call]
impl<T: Config> Pallet<T> {
    pub fn create_proposal(
        origin: OriginFor<T>,
        title: Vec<u8>,
        description: Vec<u8>,
    ) -> DispatchResult {
        let proposer = ensure_signed(origin)?;

        // Validate all inputs
        ensure!(title.len() <= 100, Error::<T>::TitleTooLong);
        ensure!(description.len() <= 1000, Error::<T>::DescriptionTooLong);
        ensure!(!title.is_empty(), Error::<T>::TitleEmpty);

        // All validation passed, safe to proceed
        Self::store_proposal(proposer, title, description)?;
        Ok(())
    }
}
```

**Testing for Panics**:
```rust
// Use test-fuzz to find panic conditions
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn test_panics_on_invalid_input() {
        // This test should NOT panic in production code
        // If it does, you found a vulnerability
        Pallet::divide(100, 0).unwrap();
    }

    // Fuzz testing
    #[test_fuzz::test_fuzz]
    fn fuzz_transfer(sender: AccountId, amount: u128) {
        // Should never panic regardless of input
        let _ = Pallet::transfer(sender, amount);
    }
}
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/dont_panic

---

## 4.3 WEIGHTS AND FEES ⚠️ CRITICAL - DoS

**Description**: Incorrect weight functions allow cheap calls to expensive operations, enabling DoS attacks by spamming low-fee transactions that consume excessive resources.

**Detection Patterns**:
```rust
// VULNERABLE: Fixed weight for variable-cost operation
#[pallet::weight(10_000)]  // Same cost regardless of input!
pub fn process_items(origin: OriginFor<T>, items: Vec<Item>) -> DispatchResult {
    // Processing cost grows with items.len(), but weight is fixed!
    for item in items {
        Self::expensive_operation(item);  // O(n) operation
    }
    Ok(())
}

// VULNERABLE: No bounds on input size
#[pallet::weight(items.len() as u64 * 1000)]  // Weight grows with input
pub fn process_items(origin: OriginFor<T>, items: Vec<Item>) -> DispatchResult {
    // But no maximum limit! Attacker can send items.len() = 1 billion
    // and cause block to exceed weight limit
    Ok(())
}

// VULNERABLE: Zero weight for non-trivial operation
#[pallet::weight(0)]  // FREE operation!
pub fn expensive_computation(origin: OriginFor<T>, data: Vec<u8>) -> DispatchResult {
    let _ = Self::compute_hash_10000_times(&data);  // Expensive but free!
    Ok(())
}
```

**What to Check**:
- [ ] Weight functions account for input size
- [ ] Loops have bounded iterations
- [ ] Storage access counted in weight
- [ ] Upper bounds enforced on Vec/array parameters
- [ ] Weights determined empirically via benchmarking
- [ ] No free/zero-weight dispatchables (unless trivial)

**Mitigation**:
```rust
// SECURE: Weight proportional to input size with bounds
#[pallet::weight({
    // Ensure items.len() has a reasonable maximum
    let bounded_len = items.len().min(T::MaxItems::get() as usize);
    T::DbWeight::get().reads_writes(bounded_len as u64, bounded_len as u64)
        .saturating_add(T::WeightPerItem::get().saturating_mul(bounded_len as u64))
})]
pub fn process_items(origin: OriginFor<T>, items: Vec<Item>) -> DispatchResult {
    // Enforce maximum items
    ensure!(items.len() <= T::MaxItems::get() as usize, Error::<T>::TooManyItems);

    for item in items {
        Self::expensive_operation(item);
    }
    Ok(())
}

// SECURE: Use benchmarking framework
#[pallet::weight(T::WeightInfo::transfer())]
pub fn transfer(
    origin: OriginFor<T>,
    dest: T::AccountId,
    amount: BalanceOf<T>,
) -> DispatchResult {
    // Weight calculated by benchmarking.rs
    // ...
}

// BETTER: Benchmark with input parameters
#[pallet::weight(T::WeightInfo::process_items(items.len() as u32))]
pub fn process_items(origin: OriginFor<T>, items: Vec<Item>) -> DispatchResult {
    ensure!(items.len() <= T::MaxItems::get() as usize, Error::<T>::TooManyItems);
    // ...
}
```

**Benchmarking**:
```rust
// benchmarking.rs - Empirically determine weights
#[benchmarks]
mod benchmarks {
    use super::*;

    #[benchmark]
    fn process_items(n: Linear<1, 100>) {
        let items: Vec<Item> = (0..n).map(|i| Item::new(i)).collect();

        #[extrinsic_call]
        process_items(RawOrigin::Signed(caller), items);

        // Verify operation succeeded
        assert!(SomeStorage::<T>::get().is_some());
    }
}
```

**Configuration Constants**:
```rust
#[pallet::config]
pub trait Config: frame_system::Config {
    // Define maximum bounds
    #[pallet::constant]
    type MaxItems: Get<u32>;

    #[pallet::constant]
    type MaxDataSize: Get<u32>;

    // Weight info from benchmarking
    type WeightInfo: WeightInfo;
}

// In runtime/lib.rs
impl pallet_example::Config for Runtime {
    type MaxItems = ConstU32<100>;  // Max 100 items per call
    type MaxDataSize = ConstU32<10_000>;  // Max 10KB data
    type WeightInfo = pallet_example::weights::SubstrateWeight<Runtime>;
}
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/weights_fees

---

## 4.4 VERIFY FIRST, WRITE LAST ⚠️ HIGH (Pre-v0.9.25)

**Description**: In Substrate versions before v0.9.25, storage writes before validation persist even if the dispatch later fails, allowing attackers to modify state without paying the full cost.

**Detection Patterns**:
```rust
// VULNERABLE: Storage write before validation (pre-v0.9.25)
pub fn claim_reward(origin: OriginFor<T>) -> DispatchResult {
    let claimer = ensure_signed(origin)?;

    // WRONG: Writing to storage before all validation!
    <ClaimCount<T>>::mutate(|count| *count += 1);

    // Validation happens AFTER storage write
    let reward = Self::calculate_reward(&claimer)?;
    ensure!(reward > 0, Error::<T>::NoReward);

    // If this fails, ClaimCount was still incremented!
    Self::transfer_reward(&claimer, reward)?;

    Ok(())
}

// VULNERABLE: Event emitted before validation
pub fn submit_proposal(origin: OriginFor<T>, data: Vec<u8>) -> DispatchResult {
    let proposer = ensure_signed(origin)?;

    // WRONG: Event before validation
    Self::deposit_event(Event::ProposalSubmitted(proposer.clone()));

    // Validation after event
    ensure!(data.len() <= 1000, Error::<T>::DataTooLarge);

    Ok(())
}
```

**What to Check**:
- [ ] Using Substrate v0.9.25+ (transactional storage layer)
- [ ] OR all validation happens BEFORE any storage writes
- [ ] OR manual `#[transactional]` attribute used
- [ ] Events emitted AFTER all validation and state changes
- [ ] Pattern: validate → write → emit event

**Mitigation**:
```rust
// OPTION 1: Upgrade to v0.9.25+ (automatic transactional storage)
// Storage writes automatically rolled back on error

// OPTION 2: Verify First, Write Last pattern
pub fn claim_reward(origin: OriginFor<T>) -> DispatchResult {
    let claimer = ensure_signed(origin)?;

    // ALL VALIDATION FIRST
    let reward = Self::calculate_reward(&claimer)?;
    ensure!(reward > 0, Error::<T>::NoReward);
    ensure!(Self::can_transfer(&claimer, reward)?, Error::<T>::TransferFailed);

    // THEN ALL WRITES
    <ClaimCount<T>>::mutate(|count| *count += 1);
    Self::transfer_reward(&claimer, reward)?;

    // FINALLY EVENTS
    Self::deposit_event(Event::RewardClaimed {
        claimer,
        amount: reward,
    });

    Ok(())
}

// OPTION 3: Manual transactional attribute (pre-v0.9.25)
use frame_support::transactional;

#[transactional]  // Rollback all storage on error
pub fn claim_reward(origin: OriginFor<T>) -> DispatchResult {
    // Storage writes rolled back if function returns Err
    Ok(())
}
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/verify_first

---

## 4.5 UNSIGNED TRANSACTION VALIDATION ⚠️ HIGH

**Description**: Insufficient validation in `ValidateUnsigned` trait allows spam, replay attacks, or spoofed data from offchain workers or external sources.

**Detection Patterns**:
```rust
// VULNERABLE: No validation in validate_unsigned
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
    type Call = Call<T>;

    fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
        match call {
            Call::submit_price { price, .. } => {
                // WRONG: No validation of price source or replay protection!
                ValidTransaction::with_tag_prefix("OffchainWorker")
                    .priority(100)
                    .build()
            }
            _ => InvalidTransaction::Call.into(),
        }
    }
}

// VULNERABLE: No replay protection
pub fn submit_data(origin: OriginFor<T>, data: Vec<u8>) -> DispatchResult {
    ensure_none(origin)?;  // Unsigned transaction
    // WRONG: Same data can be submitted multiple times!
    Self::process_data(data)?;
    Ok(())
}
```

**What to Check**:
- [ ] Consider using signed transactions instead (strongly preferred)
- [ ] IF unsigned is necessary:
  - [ ] All parameters validated in `validate_unsigned`
  - [ ] Replay protection via nonce or one-time tag
  - [ ] Data source authenticated (cryptographic signature)
  - [ ] Rate limiting or spam protection
  - [ ] Priority set appropriately (high for OCW, low for user-submitted)

**Mitigation**:
```rust
// OPTION 1: Use signed transactions (PREFERRED)
#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::weight(10_000)]
    pub fn submit_price(
        origin: OriginFor<T>,  // Signed
        price: u128,
        signature: Signature,
    ) -> DispatchResult {
        let signer = ensure_signed(origin)?;
        // Signed transaction provides natural replay protection and authentication
        Ok(())
    }
}

// OPTION 2: Proper unsigned validation (if truly necessary)
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
    type Call = Call<T>;

    fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
        match call {
            Call::submit_price { price, block_number, signature } => {
                // 1. Validate signature from authorized source
                let public_key = Self::authority_key();
                ensure!(signature.verify(price, block_number, &public_key),
                    InvalidTransaction::BadProof);

                // 2. Replay protection - check block_number is current
                let current_block = <frame_system::Pallet<T>>::block_number();
                ensure!(block_number == current_block,
                    InvalidTransaction::Stale);

                // 3. One-time submission via unique tag
                let tag = (b"price", block_number).encode();

                ValidTransaction::with_tag_prefix("OffchainWorker")
                    .priority(TransactionPriority::MAX)
                    .and_provides(tag)  // Prevents duplicate submission
                    .longevity(5)  // Valid for 5 blocks
                    .propagate(true)
                    .build()
            }
            _ => InvalidTransaction::Call.into(),
        }
    }
}

// With replay protection in dispatch
pub fn submit_price(
    origin: OriginFor<T>,
    price: u128,
    block_number: T::BlockNumber,
) -> DispatchResult {
    ensure_none(origin)?;

    // Check not already processed
    ensure!(!<ProcessedBlocks<T>>::contains_key(block_number),
        Error::<T>::AlreadyProcessed);

    // Mark as processed
    <ProcessedBlocks<T>>::insert(block_number, true);

    // Process data
    Self::update_price(price)?;

    Ok(())
}
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/unsigned_validation

---

## 4.6 BAD RANDOMNESS ⚠️ MEDIUM

**Description**: Using low-security randomness source (`pallet_randomness_collective_flip`) in production allows validator collusion or manipulation.

**Detection Patterns**:
```rust
// VULNERABLE: Using RandomnessCollectiveFlip in production
impl pallet_example::Config for Runtime {
    type Randomness = pallet_randomness_collective_flip::RandomnessCollectiveFlip;
}

// VULNERABLE: Using random_seed() instead of random()
pub fn draw_winner(origin: OriginFor<T>) -> DispatchResult {
    let seed = T::Randomness::random_seed();  // WRONG: Doesn't incorporate subject
    let winner_index = Self::pick_from_seed(&seed);
    // ...
}

// VULNERABLE: Using randomness for critical security
pub fn generate_secret_key(origin: OriginFor<T>) -> DispatchResult {
    let random = T::Randomness::random(&b"secret"[..]);
    // BAD: Even with BABE, not sufficient for cryptographic keys!
    let secret_key = Self::derive_key(random.0);
    // ...
}
```

**What to Check**:
- [ ] NOT using `pallet_randomness_collective_flip` in production
- [ ] Using BABE randomness (`pallet_babe::RandomnessFromOneEpochAgo`)
- [ ] Using `random(subject)` instead of `random_seed()`
- [ ] NOT using on-chain randomness for cryptographic keys
- [ ] Understanding randomness can be influenced by validators

**Mitigation**:
```rust
// SECURE: Use BABE randomness (production)
impl pallet_example::Config for Runtime {
    // Use BABE VRF for production randomness
    type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
}

// SECURE: Use random() with subject, not random_seed()
pub fn draw_winner(origin: OriginFor<T>) -> DispatchResult {
    let _ = ensure_signed(origin)?;

    // Incorporate subject to make each call's randomness unique
    let (random_hash, _block_number) = T::Randomness::random(&b"lottery"[..]);

    // Convert to integer for selection
    let random_number = u32::from_le_bytes([
        random_hash.as_ref()[0],
        random_hash.as_ref()[1],
        random_hash.as_ref()[2],
        random_hash.as_ref()[3],
    ]);

    let winner_index = random_number % Self::participant_count();
    Self::award_prize(winner_index)?;

    Ok(())
}

// SECURE: Don't use on-chain randomness for crypto keys
// Instead: generate off-chain, store hash on-chain for verification
pub fn register_key(origin: OriginFor<T>, key_hash: Hash) -> DispatchResult {
    // Store hash of externally-generated key
    // User generates key off-chain with proper entropy
    Ok(())
}
```

**Randomness Quality by Source**:
```rust
// NOT SECURE for production
pallet_randomness_collective_flip  // Low influence, collusion risk

// SECURE for production
pallet_babe::RandomnessFromOneEpochAgo  // VRF-based, high security

// SECURE for testing only
pallet_insecure_randomness_collective_flip  // Explicit "insecure" naming
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/randomness

---

## 4.7 BAD ORIGIN ⚠️ CRITICAL

**Description**: Using `ensure_signed` for privileged operations instead of proper origin validation (`ensure_root`, custom origins) allows unauthorized access.

**Detection Patterns**:
```rust
// VULNERABLE: Using ensure_signed for privileged operation
#[pallet::call]
impl<T: Config> Pallet<T> {
    pub fn emergency_pause(origin: OriginFor<T>) -> DispatchResult {
        let _caller = ensure_signed(origin)?;  // WRONG: Any signed user!
        <SystemPaused<T>>::put(true);  // Critical operation with no real authorization
        Ok(())
    }

    pub fn set_global_config(
        origin: OriginFor<T>,
        new_fee: Balance,
    ) -> DispatchResult {
        let _caller = ensure_signed(origin)?;  // WRONG: Any user can change fees!
        <GlobalFee<T>>::put(new_fee);
        Ok(())
    }
}

// VULNERABLE: Not using configured ForceOrigin
pub fn force_transfer(
    origin: OriginFor<T>,
    from: T::AccountId,
    to: T::AccountId,
) -> DispatchResult {
    let _admin = ensure_signed(origin)?;  // WRONG: Should check ForceOrigin!
    // Should be: T::ForceOrigin::ensure_origin(origin)?;
    Self::do_transfer(from, to)?;
    Ok(())
}
```

**What to Check**:
- [ ] Root-level operations use `ensure_root(origin)?`
- [ ] Privileged operations use custom origin types (ForceOrigin, AdminOrigin)
- [ ] `ensure_signed` only for user-level operations
- [ ] Origin configuration documented for governance
- [ ] Sudo privileges removed before production launch

**Mitigation**:
```rust
// SECURE: Use ensure_root for root operations
#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::weight(10_000)]
    pub fn emergency_pause(origin: OriginFor<T>) -> DispatchResult {
        ensure_root(origin)?;  // Only root account
        <SystemPaused<T>>::put(true);
        Self::deposit_event(Event::SystemPaused);
        Ok(())
    }
}

// SECURE: Use custom origin types for privileged operations
#[pallet::config]
pub trait Config: frame_system::Config {
    /// Origin that can execute force operations
    type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;

    /// Origin that can update parameters
    type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}

#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::weight(10_000)]
    pub fn force_transfer(
        origin: OriginFor<T>,
        from: T::AccountId,
        to: T::AccountId,
        amount: BalanceOf<T>,
    ) -> DispatchResult {
        // Validate against configured ForceOrigin
        T::ForceOrigin::ensure_origin(origin)?;

        Self::do_transfer(from, to, amount)?;
        Ok(())
    }

    #[pallet::weight(10_000)]
    pub fn update_fee(
        origin: OriginFor<T>,
        new_fee: BalanceOf<T>,
    ) -> DispatchResult {
        // Validate against configured UpdateOrigin
        T::UpdateOrigin::ensure_origin(origin)?;

        <GlobalFee<T>>::put(new_fee);
        Self::deposit_event(Event::FeeUpdated { new_fee });
        Ok(())
    }

    #[pallet::weight(10_000)]
    pub fn transfer(
        origin: OriginFor<T>,
        to: T::AccountId,
        amount: BalanceOf<T>,
    ) -> DispatchResult {
        // Regular user operation - ensure_signed is correct
        let from = ensure_signed(origin)?;

        Self::do_transfer(from, to, amount)?;
        Ok(())
    }
}

// Runtime configuration
impl pallet_example::Config for Runtime {
    // Require governance approval for force operations
    type ForceOrigin = EnsureRootOrHalfCouncil;

    // Require 2/3 council for parameter updates
    type UpdateOrigin = EnsureRootOrTwoThirdsCouncil;
}
```

**Origin Types Reference**:
```rust
// Built-in origins
ensure_root(origin)?              // Root/sudo only
ensure_signed(origin)?            // Any signed account
ensure_none(origin)?              // Unsigned (use with extreme caution)

// Custom origins (configured in runtime)
T::ForceOrigin::ensure_origin(origin)?
T::UpdateOrigin::ensure_origin(origin)?
T::AdminOrigin::ensure_origin(origin)?

// Common origin configurations
EnsureRoot                        // Root only
EnsureSigned                      // Any signed
EnsureSignedBy<AccountId>         // Specific account
EnsureRootOrHalfCouncil          // Root or 50%+ council
EnsureRootOrTwoThirdsCouncil     // Root or 67%+ council
```

**References**: building-secure-contracts/not-so-smart-contracts/substrate/bad_origin
