Over the past 18 months, our audit team reviewed 40 smart contracts written for payment and custody applications. Here is what the vulnerability distribution looks like, and what it means for developers building payment infrastructure.
The Numbers
- 37 had at least one medium-severity issue
- 18 had at least one high-severity issue
- 4 had at least one critical issue that would have resulted in direct fund loss if deployed
- 0 were issue-free
These are contracts written by teams who considered themselves security-conscious enough to commission an audit. The real-world distribution is almost certainly worse.
The Most Common Issues
1. Reentrancy (Still)
Reentrancy remains the most common critical issue we find, present in roughly 30% of contracts we audit. The pattern is almost always the same: state is updated after an external call rather than before it. Use OpenZeppelin ReentrancyGuard on any function that makes external calls and modifies state. It is two lines and eliminates the entire class of vulnerability.
2. Integer Overflow in Fee Calculations
Solidity 0.8.x added built-in overflow protection, but we still audit contracts using unchecked blocks in fee calculation paths where the developer was trying to optimise gas. Unchecked arithmetic in a fee calculation is a gift to an attacker who can craft payment amounts that cause overflow and pay zero fees.
3. Price Oracle Manipulation
Payment contracts that read spot prices from a single DEX using instantaneous spot price are vulnerable to flash loan manipulation. Always use a time-weighted average price with at least a 30-minute window, or use Chainlink as your price source.
4. Missing Access Controls on Administrative Functions
We found four contracts where functions to update the fee recipient address or drain the fee treasury had no access control whatsoever. In one case, a function to drain the treasury to any address was fully public.
5. Precision Loss in Division
Solidity truncates division toward zero. In fee calculations, this can be exploited: an attacker can craft amounts where the division truncates to zero, effectively paying no fees. Multiply before dividing, and implement a minimum fee floor.
What Good Payment Contract Security Looks Like
- All state changes before external calls (checks-effects-interactions)
- ReentrancyGuard on all externally callable functions with state changes
- Pull payment pattern for ETH withdrawals rather than push
- Ownable2Step for admin key rotation (prevents accidental address transfers)
- Events emitted for every state change
- TWAP oracle with minimum 30-minute window or Chainlink price feed
- Emergency pause functionality with a defined governance process
Getting Your Contract Audited
We offer smart contract audits with a two-week turnaround for contracts up to 1,500 lines of Solidity. Our audit reports include a severity-classified finding list, remediation guide, and a free re-audit of fixes. Contact us to request a quote.
Alex Morozov is Head of Engineering at KomiKey Technologies and has audited over 80 smart contracts since 2018.
The oracle manipulation section is particularly important right now. We did a competitive analysis of payment contracts deployed in the last 6 months and still found a worrying number using single-source instantaneous price. Some of them are processing real volume. The gap between research awareness and production practice is real.
Good post. I would add that Ownable2Step from OpenZeppelin should be the default for all new contracts, not optional. The number of contracts lost to mistyped transfer addresses is embarrassing for the industry. It is a one-line change.