Skip to main content
Sigvex

Business Logic Error

Detects violations of protocol-specific invariants — conditions that must always hold true for the protocol to operate correctly — including incorrect reward calculations, fee logic, and state transition rules.

Business Logic Error

Overview

Remediation Guide: How to Fix Business Logic Errors

The business logic error detector identifies violations of protocol-level invariants in smart contracts. Unlike low-level vulnerabilities such as reentrancy or integer overflow, business logic errors occur when the code correctly executes its instructions but those instructions implement incorrect protocol rules. Examples include: reward calculations that allow double-claiming, fee logic that can be bypassed under certain conditions, state transitions that reach invalid states, and governance rules that can be circumvented.

Sigvex detects these patterns by analyzing the semantic relationships between storage variables, tracking whether required conditions are checked before state-affecting operations, and identifying gaps between what the contract claims to enforce and what it actually enforces. The detector uses data-flow analysis to trace value paths from user inputs to critical state updates, flagging cases where the relationship violates common financial protocol invariants.

Business logic errors ranked third in OWASP’s 2025 Smart Contract Top 10, with documented losses exceeding $63.8 million from the Radiant Capital exploit ($50M) and Munchables exploit ($62M alone in March 2024).

Why This Is an Issue

Business logic errors are the hardest vulnerability class to detect because they require understanding the protocol’s intended behavior — not just the code’s behavior. They frequently arise when:

  • A single complex function is responsible for multiple state transitions that must be atomic
  • Reward or fee calculations contain an edge case the developer did not anticipate
  • Protocol rules interact in unexpected ways when combined (e.g., flash loans + reward mechanics)
  • Access controls correctly limit who can call a function, but not what parameters they can supply

The Radiant Capital exploit ($50M, October 2024) exploited a business logic flaw in their multi-sig update mechanism. The Munchables exploit ($62M, March 2024) was an insider exploit made possible by a lack of invariant checking on privileged storage writes.

How to Resolve

// Before: Vulnerable reward claim with no tracking of prior claims
contract VulnerableStaking {
    mapping(address => uint256) public staked;
    mapping(address => uint256) public lastClaimTime;
    uint256 public rewardRate = 100; // tokens per second per staked token

    function claimRewards() external {
        uint256 elapsed = block.timestamp - lastClaimTime[msg.sender];
        uint256 reward = staked[msg.sender] * rewardRate * elapsed;

        // VULNERABLE: updates lastClaimTime AFTER transfer
        // If rewardToken.transfer() triggers a callback, user can claim again
        rewardToken.transfer(msg.sender, reward);
        lastClaimTime[msg.sender] = block.timestamp; // Too late
    }
}

// After: Update accounting state before transfers
contract SecureStaking {
    mapping(address => uint256) public staked;
    mapping(address => uint256) public lastClaimTime;
    uint256 public rewardRate = 100;

    function claimRewards() external {
        uint256 elapsed = block.timestamp - lastClaimTime[msg.sender];
        uint256 reward = staked[msg.sender] * rewardRate * elapsed;

        // FIXED: update accounting before transfer
        lastClaimTime[msg.sender] = block.timestamp;
        rewardToken.transfer(msg.sender, reward);
    }
}

Examples

Vulnerable Code

// Vulnerable: Fee logic that can be bypassed with zero-amount deposit
contract VulnerableFeeProtocol {
    uint256 public constant FEE_RATE = 30; // 0.3% in basis points
    mapping(address => uint256) public deposits;

    function deposit(uint256 amount) external {
        // No minimum check — zero-amount deposits bypass fee accounting
        deposits[msg.sender] += amount;
        // Fee calculation rounds down to zero for tiny amounts
        uint256 fee = (amount * FEE_RATE) / 10000;
        collectedFees += fee;
        token.transferFrom(msg.sender, address(this), amount);
    }

    function getSharePrice() external view returns (uint256) {
        // Share price calculation can be manipulated via tiny-amount deposits
        // that inflate total deposits without paying proportional fees
        return (totalAssets() * 1e18) / totalShares;
    }
}

// Vulnerable: Incorrect ordering allows share price manipulation
contract VulnerableVault {
    uint256 public totalShares;

    function depositAndMint(uint256 assets) external {
        // WRONG ORDER: calculates shares using pre-deposit asset count
        // Then mints, inflating the denominator after the calculation
        uint256 shares = (assets * totalShares) / totalAssets();
        totalShares += shares;
        _mint(msg.sender, shares);
        token.transferFrom(msg.sender, address(this), assets);
    }
}

Fixed Code

// Fixed: Enforce minimum amounts and correct calculation ordering
contract SecureFeeProtocol {
    uint256 public constant FEE_RATE = 30;
    uint256 public constant MIN_DEPOSIT = 1000; // Prevents fee rounding bypass

    function deposit(uint256 amount) external {
        require(amount >= MIN_DEPOSIT, "Below minimum deposit");
        uint256 fee = (amount * FEE_RATE) / 10000;
        require(fee > 0, "Fee must be nonzero");
        collectedFees += fee;
        deposits[msg.sender] += amount - fee;
        token.transferFrom(msg.sender, address(this), amount);
    }
}

// Fixed: ERC-4626 compliant — previewDeposit uses assets BEFORE transfer
contract SecureVault {
    uint256 public totalShares;

    function depositAndMint(uint256 assets) external {
        // ERC-4626: calculate shares based on CURRENT state before mutation
        uint256 shares = previewDeposit(assets);
        // Transfer first, then mint
        token.transferFrom(msg.sender, address(this), assets);
        totalShares += shares;
        _mint(msg.sender, shares);
    }

    function previewDeposit(uint256 assets) public view returns (uint256) {
        uint256 supply = totalShares;
        return supply == 0 ? assets : (assets * supply) / totalAssets();
    }
}

Sample Sigvex Output

{
  "detector_id": "business-logic-error",
  "severity": "high",
  "confidence": 0.68,
  "description": "Function claimRewards() updates lastClaimTime after transferring tokens. If the token transfer triggers a callback, the rewards can be claimed multiple times before the accounting is updated.",
  "location": { "function": "claimRewards()", "offset": 196 }
}

Detection Methodology

Sigvex identifies business logic errors through several complementary analysis techniques:

  1. Accounting invariant detection: Identifies storage variables that represent financial accounting (balances, rewards, fees) and checks that updates occur in the correct order relative to transfers.
  2. Edge case analysis: Detects operations where mathematical rounding or zero-value inputs can bypass intended constraints — for example, fee calculations that round to zero.
  3. State transition validation: Tracks the sequence of state changes in critical functions and flags violations of common DeFi patterns (e.g., reads before writes in vault share calculations).
  4. Relationship tracking: Identifies pairs of storage variables that must maintain an invariant relationship (e.g., total supply = sum of balances) and flags operations that could violate it.

Confidence is medium for this detector class because business logic errors are protocol-specific and require understanding intent, not just code structure.

Limitations

False positives:

  • Intentional protocol designs that deviate from common patterns may be flagged. For example, protocols with deliberate fee-free small transactions.
  • Complex multi-contract protocols where the invariant is enforced across contract boundaries may appear vulnerable when analyzing individual contracts.

False negatives:

  • Novel protocol designs without established invariants are harder to analyze.
  • Business logic errors that only manifest under specific market conditions or multi-step attack sequences may require cross-transaction analysis.
  • Reentrancy — business logic errors often combine with reentrancy for exploitation
  • Precision Errors — rounding errors are a common source of business logic violations
  • Reward Manipulation — a specific subclass of business logic error targeting reward systems
  • Vault Inflation — ERC-4626 vault calculation ordering issues

References