Security
The Reborn contracts are Solidity 0.8.24 with via_ir, native overflow checks, and OpenZeppelin 5.x. The original Fantums was Solidity 0.6.2 with manual SafeMath. The list below is the audit-grade diff.
Risks closed vs the original
| Risk in original | Closed in Reborn |
|---|---|
No reentrancy guard on mintFantum (sends FTM → calls Sushi for buyback → then mutates state) | Every payable function uses nonReentrant |
Approve-then-transferFrom self-pattern in withdrawOtherToken (weird semantics, breaks on tokens that block self-approval) | SafeERC20.safeTransfer used directly |
Hardcoded burn address 0xf000000000000000000000000000000000000000 (non-standard) | 0x000000000000000000000000000000000000dEaD for visible burns |
| No pause mechanism for emergencies | Every Reborn contract inherits Pausable |
Mutable _owners array of addresses with no time-lock | Ownable with TokenTimelock for the team's sFUM allocation |
| No EIP-2981 royalty standard | EIP-2981 from day 1 |
| Centralised metadata API | On-chain SVG renderer — see On-chain rendering |
| Client-side PoW mint puzzle | Deterministic shuffle with public seed commitment |
OpenZeppelin 5.x stack
| Library | Usage |
|---|---|
Pausable | Emergency pause on all stateful contracts |
ReentrancyGuard | Every payable and external state-mutating function on Duel / MintDrop / Marketplace / Graveyard |
SafeERC20 | All ERC-20 transfers |
Ownable | Admin functions, with intent to migrate to a TokenTimelock and eventually a multisig |
Base64 | On-chain SVG encoding |
ERC721 + ERC721Enumerable | Core NFT |
EIP-2981 | Royalty standard |
Hard caps everywhere
The original had unbounded price functions (priceOf could climb absurdly), unbounded name-change costs (2^count * 10 FTM becomes impossible after a few changes), and an unbounded _owners array.
Reborn's hard caps:
| Contract | Cap | Value |
|---|---|---|
| MintDrop | MAX_ETH_PRICE | 1 ether per mint |
| MintDrop | MAX_STABLE_PRICE | $10,000 in USDC/USDT |
| MintDrop | MAX_AIRDROP_PER_MINT | 1,000 sFUM per mint |
| MintDrop | MAX_BATCH | 20 mints per transaction |
| Duel | MAX_FIGHT_COST_SFUMS | 10,000 sFUM per fight |
| Graveyard | Per-tier cost cap | Owner-tunable up to a hard ceiling per tier |
| Marketplace | feeBps ceiling | Owner-tunable up to 10% (cannot exceed) |
| Marketplace | ogFeeBps <= feeBps | Invariant enforced both ways |
These caps mean the owner cannot tune values into a state that grief-traps the protocol.
Time-locked team allocation
The team's 20,000 sFUM allocation is timelocked in SFUMSTimelock.sol:
| Property | Value |
|---|---|
| Allocation | 20,000 sFUM (20% of total supply) |
| Cliff | 12 months |
| Linear vest after cliff | 24 months |
| Total lock period | 36 months from deployment |
| Early release | Not possible. Period. |
The timelock is a one-way deposit. Once the team's allocation is deposited, the only way out is the schedule.
EIP-712 signing for duel results
The duel keeper signs results with EIP-712 typed data:
struct DuelResult {
uint256 tokenA;
uint256 tokenB;
uint256 winnerId;
uint8 rounds;
bytes32 seed;
int32 newEloA;
int32 newEloB;
uint256 nonce;
uint64 expiry;
}
The on-chain contract:
- Recovers the signer via
ECDSA.recover - Checks the signer is the trusted keeper
- Re-runs the duel computation on-chain from
seed+ the two stat blocks - Reverts if the computed winner doesn't match the signed
winnerId - Checks
expiry > block.timestamp - Checks the
noncehasn't been used
The keeper EOA can be replaced by the contract owner. It can later be replaced by a multisig, or eventually a fully on-chain matchmaking contract.
Supply chain audit
A full supply-chain audit of npm dependencies was performed on 2026-05-20 against the known compromises from April-May 2026:
| Check | Outcome |
|---|---|
npm audit on root | 0 vulnerabilities |
42 compromised @tanstack/* names (May 11 attack) | 0 present |
node-ipc (May 14 attack) | 0 present |
14 compromised @antv/* + adjacent names (May 19 wave) | 0 present |
| Shai-Hulud worm IOCs (strings, binaries, exfil patterns) | 0 present |
Suspicious install scripts in node_modules | 0 found |
Full audit report lives in docs/SECURITY-AUDIT-2026-05-20.md. Verdict: clean.
Ongoing security posture
| Practice | Status |
|---|---|
Direct deps pinned to exact versions (no ^) | Planned for pre-mainnet |
npm provenance verification on install | Planned |
GitHub Actions: pull_request only, never pull_request_target with fork checkout | Planned (no CI exists yet) |
| External smart-contract audit | Planned pre-mainnet |
| Bug bounty program | Planned post-launch |
What we do NOT defend against
Be honest about scope:
- Sonic chain-level failures. If Sonic mainnet itself has a consensus bug or a hostile validator majority, our contracts cannot defend against that. This is true of every dApp.
- Founder wallet compromise. If the founder wallet's seed is leaked, the attacker can call
pause(). They cannot mint more sFUM (fixed supply), cannot release timelocked tokens (timelock contract), cannot break the mint cap (hard cap). The blast radius is "the protocol gets paused" and "the dev's 1-of-1 Monarch moves". - External marketplace exploits. If a third-party marketplace listing our tokens has a bug, that's their bug. Our marketplace contract is audit-targeted.
- Frontend phishing. A clone of our frontend can capture user signatures. Always verify the URL.
See also
- Contract addresses — where to verify signed bytecode
- Open source — read the source yourself
- On-chain rendering — the audit posture on the renderer
- Tokenomics — supply caps in detail
Last updated: 2026-05-21