Smart Contract Security: Common Vulnerabilities and How to Avoid Them (Ethereum, Solana, BSC)
Published by Autonix Lab — AI, Web3 & Blockchain Consulting
Smart contracts are immutable by design. Once deployed, a bug isn't a patch away — it's a potential nine-figure exploit waiting to happen. The history of DeFi is littered with protocols that passed audits, raised millions, and still got drained because of a single overlooked edge case.
This article walks through the most dangerous vulnerability classes across Ethereum/Solidity, Solana/Rust, and BNB Smart Chain — with concrete examples and practical mitigation patterns for each.
Ethereum & Solidity
Ethereum has the oldest and most battle-tested smart contract ecosystem, which means it also has the longest list of documented exploits.
1. Reentrancy
The classic. The DAO hack in 2016 — $60M drained. Still happening today.
The problem: A contract sends ETH to an external address before updating its internal state. The recipient's fallback function re-enters the original contract and withdraws again before the balance is decremented.
// ❌ Vulnerable
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}(""); // external call first
require(success);
balances[msg.sender] -= amount; // state update AFTER — too late
}
// ✅ Safe — Checks-Effects-Interactions pattern
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // update state FIRST
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
Or use OpenZeppelin's ReentrancyGuard:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SafeVault is ReentrancyGuard {
function withdraw(uint amount) external nonReentrant {
// ...
}
}
Rule: Always follow Checks-Effects-Interactions. State changes before external calls, always.
2. Integer Overflow & Underflow
Pre-Solidity 0.8, arithmetic didn't revert on overflow. Adding 1 to uint256 max wrapped back to 0.
// ❌ Solidity < 0.8 — overflows silently
uint256 public totalSupply = type(uint256).max;
totalSupply += 1; // wraps to 0
Fix: Use Solidity 0.8+ (overflow protection is built in) or OpenZeppelin's SafeMath for older codebases.
3. Access Control Failures
Missing or incorrectly implemented modifiers leave admin functions publicly callable.
// ❌ Anyone can call this
function setOwner(address newOwner) external {
owner = newOwner;
}
// ✅ Restricted correctly
function setOwner(address newOwner) external onlyOwner {
owner = newOwner;
}
Also watch for uninitialized proxies — if you deploy an upgradeable contract and don't initialize it immediately, someone else can call initialize() and take ownership. This exact attack vector was used in the Parity multisig hack.
4. Flash Loan Price Oracle Manipulation
If your contract reads token prices directly from a DEX (Uniswap spot price), it's manipulable with a flash loan in the same transaction.
Fix: Use time-weighted average prices (TWAPs) via Uniswap v3's on-chain oracle, or a decentralized oracle like Chainlink. Never use getReserves() as a price source.
5. tx.origin Authentication
// ❌ Phishable — tx.origin is the original EOA, not the immediate caller
require(tx.origin == owner);
// ✅ Use msg.sender
require(msg.sender == owner);
A malicious contract can trick the owner into calling it, then call your contract — tx.origin still passes, msg.sender does not.
Solana & Rust
Solana's programming model is fundamentally different from EVM chains. Programs (contracts) are stateless — all data lives in accounts passed in at call time. This creates a different but equally dangerous class of vulnerabilities.
1. Missing Account Ownership Checks
Solana programs must verify that accounts passed to them are owned by the expected program. Without this check, an attacker can pass a fake account with crafted data.
// ❌ Vulnerable — no ownership check
pub fn process_withdraw(ctx: Context<Withdraw>) -> Result<()> {
let vault = &ctx.accounts.vault;
// assumes vault is legit — but who owns it?
transfer_funds(vault, ctx.accounts.user.key)?;
Ok(())
}
// ✅ Safe with Anchor — ownership enforced by constraint
#[account(
mut,
has_one = owner,
constraint = vault.owner == ctx.accounts.user.key()
)]
pub vault: Account<'info, Vault>,
The Anchor framework handles most ownership checks automatically through its Account<'info, T> type — use it. Native programs without Anchor need to manually verify account.owner == expected_program_id.
2. Signer Verification Failures
Just because an account is passed in doesn't mean it signed the transaction.
// ❌ No signer check
pub fn admin_action(ctx: Context<AdminAction>) -> Result<()> {
// anyone can pass any pubkey as admin
Ok(())
}
// ✅ Anchor enforces it declaratively
#[derive(Accounts)]
pub struct AdminAction<'info> {
#[account(signer)]
pub admin: AccountInfo<'info>,
}
3. Arithmetic Overflow in Rust
Unlike Solidity 0.8+, Rust's default integer arithmetic in release builds does NOT panic on overflow — it wraps silently (same as Solidity pre-0.8).
// ❌ Wraps silently in release mode
let new_balance: u64 = user_balance + deposit_amount;
// ✅ Use checked arithmetic
let new_balance = user_balance
.checked_add(deposit_amount)
.ok_or(ErrorCode::Overflow)?;
Always use checked_add, checked_sub, checked_mul for financial arithmetic in Solana programs.
4. PDA (Program Derived Address) Seed Collisions
PDAs are deterministic addresses derived from seeds. If your seed scheme isn't specific enough, two different users or accounts can resolve to the same PDA.
// ❌ Seed collision risk — same seed for different users
let seeds = &[b"vault"];
// ✅ Include user pubkey in seeds for uniqueness
let seeds = &[b"vault", user.key().as_ref()];
BNB Smart Chain (BSC)
BSC is EVM-compatible, so all Ethereum/Solidity vulnerabilities apply. However, BSC has additional risk factors unique to its ecosystem.
1. Flash Loan + Low Liquidity Oracle Attacks
BSC has significantly lower liquidity than Ethereum mainnet for most token pairs. This makes price oracle manipulation via flash loans dramatically cheaper and more common. The majority of BSC DeFi exploits from 2021–2023 followed this exact pattern — PancakeSwap spot price used as oracle, manipulated within a single transaction.
Fix: Mandatory TWAPs (minimum 30-minute window), or Chainlink price feeds where available on BSC. Never use PancakeSwap getReserves() as a price source in production.
2. Centralized Owner Keys & Rugpull Vectors
BSC's lower deployment cost and faster iteration cycle has attracted many projects with dangerous owner privileges baked in — mint functions without caps, fee parameters that can be set to 100%, blacklist functions.
Even if you're not building a rugpull, these patterns get flagged by security scanners and destroy user trust.
// ❌ Unlimited mint with no access constraint beyond ownership
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
// ✅ Cap it
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
function mint(address to, uint256 amount) external onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Cap exceeded");
_mint(to, amount);
}
Consider timelocks on sensitive owner functions (OpenZeppelin's TimelockController) and multi-sig ownership (Gnosis Safe) for production contracts on BSC.
3. Token Fee-on-Transfer Handling
Many BSC tokens implement transfer taxes. If your contract assumes transferFrom(user, address(this), amount) results in exactly amount tokens received, you'll have accounting bugs.
// ❌ Assumes exact amount received
token.transferFrom(msg.sender, address(this), amount);
balances[msg.sender] += amount; // may be wrong if token has fees
// ✅ Check actual received amount
uint256 before = token.balanceOf(address(this));
token.transferFrom(msg.sender, address(this), amount);
uint256 received = token.balanceOf(address(this)) - before;
balances[msg.sender] += received;
Cross-Chain Universal Rules
Regardless of the chain, these practices should be non-negotiable:
Audits aren't optional. A single audit is a minimum bar, not a guarantee. Critical contracts should go through multiple independent auditors. Certik, Trail of Bits, Halborn, and OtterSec (Solana-focused) are reputable choices.
Use established libraries. OpenZeppelin for EVM, Anchor for Solana. Don't reimplement token standards, access control, or math from scratch.
Formal verification where stakes are high. For core protocol logic (AMM invariants, lending collateral math), tools like Certora Prover or Echidna (fuzzing) can catch edge cases auditors miss.
Bug bounties before launch. Immunefi is the standard platform. A $50K bounty is cheap insurance against a $50M exploit.
Monitor post-deployment. Forta Network and OpenZeppelin Defender provide real-time alerting for anomalous on-chain activity. Most exploits can be front-run or paused if you're watching.
Summary
| Vulnerability | Ethereum | Solana | BSC |
|---|---|---|---|
| Reentrancy | Critical | N/A (different model) | Critical |
| Oracle manipulation | High | Medium | Critical |
| Access control | Critical | Critical | Critical |
| Arithmetic overflow | Solved in 0.8+ | Manual (checked_*) | Solved in 0.8+ |
| Account validation | N/A | Critical | N/A |
| Fee-on-transfer | Low | N/A | High |
Security in smart contract development isn't a phase you do at the end — it's a design constraint from day one. The immutability that makes blockchain valuable is the same property that makes bugs catastrophic.
Autonix Lab provides Web3 security consulting, smart contract development and auditing, and DeFi architecture services. If you're building on Ethereum, Solana, or BSC and want an expert review of your contracts, get in touch.
Tags: #blockchain #web3 #solidity #solana #smartcontracts #security #defi #bsc
Top comments (0)