February 24, 2026
Beyond Reentrancy: Smart Contract Risks That Can Escape Traditional Reviews
Reentrancy deserves the attention it gets, but settlement bypasses, oracle manipulation, broken access control, and unchecked external calls continue to create serious security risks in production smart contracts.
Ask someone to name a smart contract vulnerability and they’ll probably say reentrancy first—and for good reason. The DAO exploit permanently changed how the industry thinks about contract security. Reentrancy remains one of the most important attack classes to understand and defend against.
But a contract can be completely resistant to reentrancy and still lose funds through vulnerabilities that receive far less attention. Many of these issues aren’t the result of a single dangerous line of code; they emerge from relationships between state transitions, access control, payment flow, and external dependencies.
Settlement bypass: completing state before confirming payment
function fulfillOrder(uint256 orderId) external { Order storage order = orders[orderId]; order.status = OrderStatus.Fulfilled; _releaseGoods(order);}This function compiles cleanly and contains none of the characteristics typically associated with a reentrancy issue. Yet it can still release assets without verifying that payment has actually been completed.
The vulnerability isn’t in how state changes—it’s when it changes. A lifecycle transition such as Fulfilled should only occur after successful settlement has been confirmed.
When reviewing contracts, ask a structural question rather than searching for a specific code pattern:
Does any state transition to a completed or fulfilled state occur before payment or settlement has been verified?
That question often reveals issues that simple pattern matching cannot.
Flash-loan oracle manipulation: trusting a price you don’t control
function liquidate(address borrower) external { uint256 price = pool.balanceOf(address(this));
if (getCollateralValue(borrower, price) < threshold) { _seizeCollateral(borrower); }}Reading a price directly from AMM reserves may appear harmless until a flash loan temporarily manipulates those reserves within the same transaction.
An attacker can borrow capital, distort the pool price, execute the liquidation using the manipulated value, and repay the loan before the transaction completes.
The important observation isn’t either condition by itself.
It’s the combination of:
- a contract that can interact with flash-loan liquidity, and
- business logic that relies on instantaneous spot prices without additional validation.
Security reviews become much more effective when they examine these relationships rather than isolated statements.
Broken access control on payable functions
function deposit() external payable { balances[msg.sender] += msg.value;}
function emergencyWithdraw() external payable { payable(owner).transfer(address(this).balance);}This example looks almost too obvious—which is exactly why it can survive code review.
Developers naturally spend more time investigating complex logic than reviewing straightforward administrative functions. As a result, functions with names like emergencyWithdraw() are sometimes assumed to already include appropriate authorization.
A reliable review asks simple questions consistently:
- Is the function externally accessible?
- Does it move or release funds?
- Is access explicitly restricted?
Answering those questions for every externally callable function removes much of the human inconsistency that manual reviews inevitably introduce.
Unchecked .send() and .call() return values
function refund(address user, uint256 amount) external { user.send(amount); refunded[user] = true;}The .send() function returns a boolean indicating whether the transfer succeeded.
If that return value is ignored, the transfer can fail while the contract still records the refund as completed.
The solution is straightforward: always verify the returned value or use safer transfer utilities that propagate failures correctly.
Because this mistake is small and repetitive, it is also one of the easiest for automated analysis to identify consistently across an entire codebase.
Why structural analysis matters
Each example above shares the same characteristic.
The vulnerability doesn’t exist because of a single dangerous statement.
It exists because of the relationship between multiple facts within the contract:
- a state transition without settlement verification,
- an oracle read combined with flash-loan exposure,
- an externally callable function without authorization,
- or a transfer whose success is never validated.
These relationships are often spread across multiple functions or logical steps, making them more difficult to identify through simple pattern matching or isolated code inspection.
Automated structural analysis can evaluate these relationships consistently across every contract, applying the same review criteria to every function without fatigue or oversight. Rather than depending solely on whether a reviewer remembers to check a particular pattern, structural analysis systematically verifies how permissions, state transitions, external interactions, and business logic work together.
As smart contracts continue to increase in complexity, understanding these relationships becomes just as important as identifying individual vulnerability classes.
If you’re auditing Solidity projects with OSE Auditor, you can analyze smart contracts directly from the command line:
ose audit ./contracts --track web3To get started, see the Getting Started guide, or explore the CLI Reference for additional options.