Sample report — ERC-20 token launch
A realistic AEDSC report on a typical pre-launch ERC-20 token. 1 HIGH + 2 MED + 3 LOW + 4 INFO. Owner mint without timelock, missing zero-address checks, deprecated SafeMath.
Ship-blocker: the unbounded owner mint must move behind a 48h timelock before mainnet. The unchecked transfer in distribute() can break atomicity if USDT-style tokens are added later. Everything else is hardening.
- HIGHF-01 · centralization-risk + own-ruleFoundersToken.sol:112–118
Owner can mint unlimited supply with no timelock
The mint() function is callable by owner() at any time with no cap, no timelock, and no event log other than the standard ERC-20 Transfer. A compromised owner key drains holders to zero. This is the highest-impact and most-flagged finding on pre-launch ERC-20s in 2026 — Etherscan auto-flags it on the contract page.
FoundersToken.solvulnerablefunction mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); }suggested patch drop-in fix+ uint256 public constant MAX_SUPPLY = 1_000_000_000 ether; + ITimelock public immutable timelock; - function mint(address to, uint256 amount) external onlyOwner { + function mint(address to, uint256 amount) external { + require(msg.sender == address(timelock), "only timelock"); + require(totalSupply() + amount <= MAX_SUPPLY, "cap"); _mint(to, amount); }Fix: Move mint behind a Timelock (OpenZeppelin's TimelockController, 48h minimum) and cap total supply with a hardcoded MAX_SUPPLY constant. Renounce mint authority after the launch allocation if the tokenomics allow.centralization-riskmissing-timelocketherscan-flag - MEDIUMF-02 · unchecked-transferFoundersToken.sol:143
Unchecked return value on token.transfer in distribute()
distribute() calls token.transfer(recipient, amount) but discards the boolean return. Standard ERC-20s (USDT, USDC on certain forks) return false rather than reverting on failure. A silent fail leaves the contract in an inconsistent state.
FoundersToken.solvulnerabletoken.transfer(recipient, amount);suggested patch drop-in fix- token.transfer(recipient, amount); + token.safeTransfer(recipient, amount);Fix: Wrap with SafeERC20.safeTransfer or require(token.transfer(...)) explicitly.unchecked-transfererc20 - MEDIUMF-03 · missing-zero-checkFoundersToken.sol:76
Missing zero-address check on setTreasury()
setTreasury(address _treasury) accepts address(0) silently. If called by accident, future fee streams are burned permanently.
FoundersToken.solvulnerablefunction setTreasury(address _treasury) external onlyOwner { treasury = _treasury; }Fix: Add require(_treasury != address(0), 'zero addr').missing-zero-check - LOWF-04 · external-functionFoundersToken.sol:55, 102
Public function should be external
Two public functions are never called internally. external is cheaper because args read from calldata.
FoundersToken.solvulnerablefunction transfer(address to, uint256 amount) public { ... }Fix: Change public to external for both functions.gas-optimization - LOWF-05 · timestampFoundersToken.sol:163
Use of block.timestamp in scheduling
vestingStart uses block.timestamp. Miners can shift this by ±15 seconds. Not exploitable here but flagged for awareness.
FoundersToken.solvulnerableuint256 vestingStart = block.timestamp;Fix: Accept for vesting (drift is bounded). Document the ±15s tolerance in the contract NatSpec.timestamp - LOWF-06 · pragmaFoundersToken.sol:1
Floating pragma version
pragma solidity ^0.8.20 lets a future bug-fix release compile with subtly different behavior. Pin to a single version.
FoundersToken.solvulnerablepragma solidity ^0.8.20;Fix: pragma solidity 0.8.24;pragma - INFOF-07 · deprecatedFoundersToken.sol:4
SafeMath imported but unused (Solidity 0.8 has built-in checks)
SafeMath was needed pre-0.8. On 0.8+, all arithmetic reverts on overflow. The import adds 2 KB of bytecode for nothing.
FoundersToken.solvulnerableimport "@openzeppelin/contracts/utils/math/SafeMath.sol";Fix: Remove the import.deprecated - INFOF-08 · natspec-coverageFoundersToken.sol:various
No NatSpec on public functions
Etherscan, RPC indexers, and integrators rely on NatSpec for human-readable transaction summaries. None are present.
FoundersToken.solvulnerable// (no NatSpec on any function)Fix: Add /// @notice + /// @param for every external function. Standard pattern: OpenZeppelin's contracts.doc - INFOF-09 · event-coverageFoundersToken.sol:44–49
Constructor sets owner but emits no event
Setting initial state in the constructor without an OwnershipTransferred event makes off-chain indexing harder.
FoundersToken.solvulnerableowner = msg.sender;Fix: Inherit from OpenZeppelin's Ownable (emits the event for you).events - INFOF-10 · magic-numberFoundersToken.sol:129
Magic number 100 for fee basis points
100 appears in fee logic without a named constant. Risk of confusion with basis-point math.
FoundersToken.solvulnerablefee = amount * 100 / 10000;Fix: uint16 public constant FEE_BPS = 100; // 1.00%style
Get one of these for your contract.
Free for the first scan. Founder Pro €29/mo with a 7-day free trial unlocks unlimited scans, priority queue, and a human triaging every report — rate locked for life.