6.1 Access Control

Protecting admin functions and ensuring only authorized users can perform certain actions:

CosmWasm Access Control

// Simple owner check
pub const ADMIN: Item<Addr> = Item::new("admin");

pub fn assert_admin(
    deps: Deps,
    sender: &Addr,
) -> Result<(), ContractError> {
    let admin = ADMIN.load(deps.storage)?;
    if *sender != admin {
        return Err(ContractError::Unauthorized {});
    }
    Ok(())
}

// Usage in execute
pub fn execute_update_config(
    deps: DepsMut,
    info: MessageInfo,
    new_config: ConfigUpdate,
) -> Result<Response, ContractError> {
    // Check admin first
    assert_admin(deps.as_ref(), &info.sender)?;

    // Proceed with update...
    Ok(Response::new())
}

// Guard module pattern
pub mod guards {
    pub fn assert_offer_owner(
        offer: &Offer,
        sender: &Addr,
    ) -> Result<(), ContractError> {
        if offer.owner != *sender {
            return Err(ContractError::NotOfferOwner {});
        }
        Ok(())
    }

    pub fn assert_trade_party(
        trade: &Trade,
        sender: &Addr,
    ) -> Result<(), ContractError> {
        if *sender != trade.buyer && *sender != trade.seller {
            return Err(ContractError::NotTradeParty {});
        }
        Ok(())
    }
}

Solidity Access Control

import "@openzeppelin/contracts/access/AccessControl.sol";

contract Hub is AccessControl {
    // Role definitions
    bytes32 public constant ADMIN_ROLE =
        keccak256("ADMIN_ROLE");
    bytes32 public constant EMERGENCY_ROLE =
        keccak256("EMERGENCY_ROLE");

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(ADMIN_ROLE, admin);
    }

    // Modifier-based access control
    modifier onlyAdmin() {
        require(
            hasRole(ADMIN_ROLE, msg.sender),
            "Not admin"
        );
        _;
    }

    modifier onlyEmergency() {
        require(
            hasRole(EMERGENCY_ROLE, msg.sender),
            "Not emergency role"
        );
        _;
    }

    function updateConfig(...)
        external onlyAdmin
    { ... }

    function emergencyPause()
        external onlyEmergency
    { ... }
}

contract Trade {
    // Custom modifiers
    modifier onlyTradeParty(uint256 tradeId) {
        TradeData storage t = trades[tradeId];
        require(
            msg.sender == t.buyer ||
            msg.sender == t.seller,
            "Not trade party"
        );
        _;
    }

    modifier onlyOfferOwner(uint256 offerId) {
        require(
            msg.sender == offers[offerId].owner,
            "Not offer owner"
        );
        _;
    }
}

Solana Access Control

// Access control via account constraints
#[derive(Accounts)]
pub struct UpdateConfig<'info> {
    // Admin must sign
    #[account(
        constraint = admin.key() == hub_config.admin
            @ HubError::Unauthorized
    )]
    pub admin: Signer<'info>,

    #[account(mut)]
    pub hub_config: Account<'info, HubConfig>,
}

// has_one constraint for ownership
#[derive(Accounts)]
pub struct UpdateOffer<'info> {
    pub owner: Signer<'info>,

    #[account(
        mut,
        has_one = owner @ OfferError::NotOwner
    )]
    pub offer: Account<'info, Offer>,
}

// Trade party validation
#[derive(Accounts)]
pub struct ConfirmFiat<'info> {
    #[account(
        constraint = caller.key() == trade.buyer ||
                     caller.key() == trade.seller
            @ TradeError::NotTradeParty
    )]
    pub caller: Signer<'info>,

    #[account(mut)]
    pub trade: Account<'info, Trade>,
}

// In instruction handler
pub fn some_admin_action(
    ctx: Context<AdminAction>
) -> Result<()> {
    // Admin constraint enforced by derive(Accounts)
    // If we reach here, caller is verified admin
    Ok(())
}

6.2 Reentrancy Protection

Preventing attackers from calling back into the contract during execution:

CosmWasm (Safe by Design)

// CosmWasm is NOT vulnerable to reentrancy!
// The execution model prevents it:
//
// 1. Messages are queued, not executed inline
// 2. State changes commit only after success
// 3. Callbacks happen in separate transactions

pub fn execute_release(
    deps: DepsMut,
    trade_id: u64,
) -> Result<Response, ContractError> {
    let mut trade = TRADES.load(deps.storage, trade_id)?;

    // Update state first (safe, no reentrancy)
    trade.state = TradeState::EscrowReleased;
    TRADES.save(deps.storage, trade_id, &trade)?;

    // Queue bank message (executes AFTER this fn)
    let msg = BankMsg::Send {
        to_address: trade.buyer.to_string(),
        amount: vec![Coin {
            denom: trade.denom,
            amount: trade.amount,
        }],
    };

    // Even if BankMsg calls back, state is updated
    Ok(Response::new()
        .add_message(CosmosMsg::Bank(msg)))
}

// For cross-contract calls that need responses,
// use SubMsg with Reply handling
let submsg = SubMsg::reply_on_success(
    wasm_execute_msg,
    REPLY_ID,
);

Solidity (Vulnerable!)

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Escrow is ReentrancyGuard {

    // BAD - Vulnerable to reentrancy!
    function releaseUnsafe(uint256 tradeId)
        external
    {
        uint256 amount = escrowBalances[tradeId];
        address buyer = trades[tradeId].buyer;

        // External call BEFORE state update
        IERC20(token).transfer(buyer, amount);

        // State update AFTER - attacker can reenter!
        escrowBalances[tradeId] = 0;
    }

    // GOOD - CEI Pattern (Checks-Effects-Interactions)
    function releaseSafe(uint256 tradeId)
        external
        nonReentrant // OpenZeppelin guard
    {
        // CHECKS
        uint256 amount = escrowBalances[tradeId];
        require(amount > 0, "No balance");

        // EFFECTS - update state first!
        escrowBalances[tradeId] = 0;
        trades[tradeId].state = TradeState.Released;

        // INTERACTIONS - external call last
        IERC20(token).safeTransfer(
            trades[tradeId].buyer,
            amount
        );
    }
}

// ReentrancyGuard uses a mutex
abstract contract ReentrancyGuard {
    uint256 private _status;
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    modifier nonReentrant() {
        require(_status != _ENTERED);
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

Solana (Safe by Design)

// Solana is NOT vulnerable to reentrancy!
// Why?
//
// 1. Each instruction runs atomically
// 2. Account borrowing rules (single mutable ref)
// 3. CPI calls are synchronous but isolated

pub fn release_escrow(
    ctx: Context<ReleaseEscrow>
) -> Result<()> {
    let trade = &mut ctx.accounts.trade;

    // Update state
    trade.state = TradeState::EscrowReleased;

    // CPI to token program
    // Even though this is "external", the trade
    // account is already borrowed mutably here
    // - no one else can modify it
    token::transfer(
        ctx.accounts.into_transfer_context(),
        trade.amount,
    )?;

    // No reentrancy possible because:
    // - trade account is borrowed mutably
    // - any attempt to call back would fail
    //   the account borrow check

    Ok(())
}

// Solana's real risks are different:
// - Missing signer checks
// - Missing owner checks on accounts
// - Account confusion attacks
// - Missing account initialization checks
Solidity Reentrancy is CRITICAL

The DAO hack that led to Ethereum's fork was a reentrancy attack. Always use the CEI pattern and consider ReentrancyGuard for any function that makes external calls and modifies state.

6.3 Circuit Breakers & Pause Functionality

CosmWasm Circuit Breaker

#[cw_serde]
pub struct CircuitBreaker {
    pub global_pause: bool,
    pub pause_new_offers: bool,
    pub pause_new_trades: bool,
    pub pause_escrow_funding: bool,
    pub pause_escrow_release: bool,
}

pub const CIRCUIT_BREAKER: Item<CircuitBreaker> =
    Item::new("circuit_breaker");

pub fn assert_not_paused(
    deps: Deps,
    operation: Operation,
) -> Result<(), ContractError> {
    let cb = CIRCUIT_BREAKER.load(deps.storage)?;

    if cb.global_pause {
        return Err(ContractError::Paused {});
    }

    match operation {
        Operation::CreateOffer if cb.pause_new_offers =>
            Err(ContractError::OperationPaused {}),
        Operation::CreateTrade if cb.pause_new_trades =>
            Err(ContractError::OperationPaused {}),
        Operation::FundEscrow if cb.pause_escrow_funding =>
            Err(ContractError::OperationPaused {}),
        Operation::ReleaseEscrow if cb.pause_escrow_release =>
            Err(ContractError::OperationPaused {}),
        _ => Ok(()),
    }
}

pub fn execute_create_offer(...) -> Result<...> {
    assert_not_paused(deps.as_ref(), Operation::CreateOffer)?;
    // ... rest of function
}

Solidity Circuit Breaker

import "@openzeppelin/contracts/security/Pausable.sol";

contract Hub is Pausable, AccessControl {
    // Fine-grained operation pausing
    bytes32 public constant OP_CREATE_OFFER =
        keccak256("CREATE_OFFER");
    bytes32 public constant OP_CREATE_TRADE =
        keccak256("CREATE_TRADE");
    bytes32 public constant OP_FUND_ESCROW =
        keccak256("FUND_ESCROW");
    bytes32 public constant OP_RELEASE_ESCROW =
        keccak256("RELEASE_ESCROW");

    mapping(bytes32 => bool) public operationPaused;

    modifier whenOperationNotPaused(bytes32 op) {
        require(!paused(), "Globally paused");
        require(
            !operationPaused[op],
            "Operation paused"
        );
        _;
    }

    function pauseOperation(bytes32 op)
        external onlyRole(EMERGENCY_ROLE)
    {
        operationPaused[op] = true;
        emit OperationPaused(op);
    }

    function unpauseOperation(bytes32 op)
        external onlyRole(ADMIN_ROLE)
    {
        operationPaused[op] = false;
        emit OperationUnpaused(op);
    }

    // Global emergency pause
    function emergencyPause()
        external onlyRole(EMERGENCY_ROLE)
    {
        _pause();
    }
}

Solana Circuit Breaker

#[account]
pub struct HubConfig {
    pub admin: Pubkey,

    // Circuit breaker flags
    pub global_pause: bool,
    pub pause_new_offers: bool,
    pub pause_new_trades: bool,
    pub pause_escrow_funding: bool,
    pub pause_escrow_release: bool,

    // ... other fields
}

// Check via constraint
#[derive(Accounts)]
pub struct CreateOffer<'info> {
    #[account(
        constraint = !hub_config.global_pause
            @ HubError::GloballyPaused,
        constraint = !hub_config.pause_new_offers
            @ HubError::OperationPaused
    )]
    pub hub_config: Account<'info, HubConfig>,

    // ... other accounts
}

// Admin instruction to toggle
pub fn set_circuit_breaker(
    ctx: Context<SetCircuitBreaker>,
    global_pause: Option<bool>,
    pause_new_offers: Option<bool>,
    pause_new_trades: Option<bool>,
) -> Result<()> {
    let config = &mut ctx.accounts.hub_config;

    if let Some(v) = global_pause {
        config.global_pause = v;
    }
    if let Some(v) = pause_new_offers {
        config.pause_new_offers = v;
    }
    if let Some(v) = pause_new_trades {
        config.pause_new_trades = v;
    }

    Ok(())
}

6.4 Platform-Specific Vulnerabilities

Vulnerability CosmWasm Solidity Solana
Reentrancy Safe (message queue) HIGH RISK - use CEI + guards Safe (account borrowing)
Integer Overflow Safe (Rust panics) Safe in 0.8+ (checked math) Safe (Rust panics)
Front-running Possible (mempool) HIGH RISK (MEV) Lower risk (fast blocks)
Signature Replay Use nonces Use nonces + EIP-712 Built-in (recent blockhash)
Missing Access Control Check info.sender Check msg.sender Check Signer + constraints
Account Confusion N/A N/A HIGH RISK - verify account owners
Uninitialized Storage Rare (Option types) Check initialization Check is_initialized
💡
Solana-Specific: Account Confusion

In Solana, attackers can pass arbitrary accounts to your program. Always verify that accounts are owned by the expected program and have the correct type. Anchor's Account<'info, T> type and owner constraint help prevent this.

Knowledge Check

Test Your Understanding

1. Why is CosmWasm naturally protected from reentrancy attacks?

2. What is the CEI pattern in Solidity?

3. What is an "account confusion" attack in Solana?