# Common Smart Contract Security Mistakes Teams Make Before Launch

> These common smart contract security mistakes have cost protocols millions. Learn which patterns to avoid before your next mainnet deployment.

- **Published:** 2026-06-13
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** smart-contract-security, solidity, web3, common-mistakes
- **Canonical URL:** https://clixo.sh/blog/common-smart-contract-security-mistakes-before-launch

Most smart contract exploits do not succeed because of novel cryptographic attacks or exotic edge cases. They succeed because of mistakes that have been documented publicly for years — patterns that appear in post-mortems over and over, in different codebases, written by developers who knew the rules but did not apply them consistently. The following mistakes are not theoretical. Each one has contributed to real losses.

## Mistake 1: Updating State After External Calls

The most persistent mistake in Solidity development is performing state updates after sending ETH or calling external contracts. This is the direct cause of reentrancy vulnerabilities.

The correct order is Checks-Effects-Interactions: validate conditions, update state, then interact with external contracts. Reversing the last two steps — interacting before effects — opens a window where a malicious recipient can re-enter the function while state still reflects pre-withdrawal balances.

Teams know this rule. They still get it wrong in complex flows where the external call happens deep in a helper function and the state update is several lines above in the calling function. Review every external call path, not just the obvious ones.

```mermaid
flowchart TD
  A["Function called"] --> B["Checks: validate conditions"]
  B --> C{"Conditions met?"}
  C -->|"No"| D["Revert"]
  C -->|"Yes"| E["Effects: update state"]
  E --> F["Interactions: external call"]
  F --> G["Done"]
```

## Mistake 2: Missing Access Control on Privileged Functions

An admin function with no access modifier is callable by anyone. This should be caught by any competent code review, yet it appears in audit reports regularly. The common cause is moving fast during development and adding access modifiers "later" — and later not arriving before the audit or, worse, before launch.

A related variant: the function has an access modifier, but the modifier logic is incorrect. A modifier that checks `msg.sender == owner` but where `owner` can be set by an unprotected setter is equivalent to no access control.

Review every function that modifies protocol-level state. If it is not intended to be callable by anyone, it must have an enforcing modifier, and that modifier must be tested.

## Mistake 3: Trusting On-Chain Randomness

Using `block.timestamp`, `block.number`, or `blockhash` as a source of randomness in a smart contract is not random — it is miner or validator-influenced data. For any protocol where the outcome of a transaction has economic value (NFT mints, lottery functions, reward distribution), on-chain "randomness" is manipulable by block proposers.

The correct approach is a verifiable random function from an oracle like Chainlink VRF, which provides cryptographically provable randomness that validators cannot predict or manipulate. There is no secure shortcut using only on-chain data.

## Mistake 4: Spot Price Oracle Manipulation

A protocol that reads a price from a DEX spot price within a single transaction — such as a Uniswap pool's current reserve ratio — can have that price manipulated by flash loans. An attacker borrows a large amount with no collateral, moves the price to an extreme value, exploits the protocol, repays the loan, and walks away with the profit.

This affects any protocol that reads external prices to make consequential decisions: liquidations, collateral valuations, exchange rates. Time-weighted average prices (TWAPs) are more resistant to flash loan manipulation but have their own trade-offs. Chainlink price feeds are commonly used for off-chain aggregated pricing. Neither is perfect for all use cases, and using either correctly requires understanding the specific attack vectors each leaves open.

## Mistake 5: Unprotected Initializers on Upgradeable Contracts

Upgradeable contracts cannot use constructors for initialization. Instead, they use an `initialize` function that must be called once after deployment. If this function has no access control and no guard against being called multiple times, any address can reinitialize the contract, replacing the owner with their own address.

The fix requires applying OpenZeppelin's `initializer` modifier and, on the implementation contract side, calling `_disableInitializers()` in the constructor to prevent the implementation from being initialized directly. Missing either step is a critical vulnerability.

## Mistake 6: Integer Truncation in Type Casting

Solidity 0.8 added built-in overflow and underflow checks for standard arithmetic. It did not add protection against silent truncation when casting between integer types. Casting a `uint256` to a `uint128` that does not fit will not revert — it will silently discard the upper bits.

This is particularly dangerous in token amount calculations, where a truncated value could result in a user receiving far less than expected or, in fee calculation inversions, far more.

Use OpenZeppelin's `SafeCast` library for any cast where the value's range is not guaranteed to fit in the target type.

## Mistake 7: DoS Through Unbounded Loops

A function that iterates over an array of unbounded size will fail when the array grows large enough that the gas cost exceeds the block limit. This is a denial of service vulnerability: the function becomes permanently uncallable, potentially locking funds or freezing protocol state.

Common appearances: iterating over all users to distribute rewards, iterating over all positions during a liquidation sweep, or iterating over a list of tokens a vault has ever interacted with.

The solution is to design around this pattern: use paginated functions, track state in mappings rather than arrays, or use a push model where individual users update their own state rather than a single function updating all users.

## Mistake 8: Relying on `tx.origin` for Authorization

`tx.origin` is the original externally owned account that initiated a transaction, no matter how many contracts were called in between. Using it for authorization means any contract that can trick a user into initiating a transaction can impersonate that user.

Authorization checks should always use `msg.sender` — the immediate caller. `tx.origin` has legitimate uses (checking that a caller is not a contract, for specific use cases) but it should never be the sole authorization check for privileged operations.

## Mistake 9: Incomplete Handling of ERC-20 Return Values

The ERC-20 standard specifies that `transfer` and `transferFrom` should return a `bool`. Some widely used tokens — USDT is the canonical example — do not return a value at all. If your contract calls `token.transfer(recipient, amount)` and checks the return value, it will revert on USDT.

The fix is OpenZeppelin's `SafeERC20` library, which wraps transfer calls with handling for both the standard-compliant and non-compliant variants. Use it on every ERC-20 interaction.

## Mistake 10: No Post-Launch Security Plan

An audit is not a warranty. Protocols that launch without a bug bounty program, without on-chain monitoring, and without a documented incident response plan are operating as though security work ends at deployment. It does not.

Post-launch attack surface includes deployment configuration errors, integrations with other protocols that get exploited, and governance attacks. A plan for how to detect anomalies and respond to them is not optional for any protocol holding user funds.

Each of these mistakes is preventable with engineering discipline and the right review process. The pattern is consistent: teams that build fast without a security-first culture encounter these issues at the worst possible time.

Clixo builds Web3 systems with security embedded throughout the engineering process. If you need a team that will not ship the mistakes on this list, [start a conversation](https://clixo.sh/#contact).

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
