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": "critical",
"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:
- 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.
- Edge case analysis: Detects operations where mathematical rounding or zero-value inputs can bypass intended constraints — for example, fee calculations that round to zero.
- 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).
- 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.
Related Detectors
- 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