Protocol
Security
Audit surface, invariants, and what is still centralized.
Status#
Testnet. Unaudited by a third party. Admin keys are a single EOA.
Do not put real value into this deployment. It runs on GIWA Sepolia with test ETH, and the items under open items are not theoretical.
What has been done: an internal review with fuzzing, invariant testing, and static analysis, plus source verification of every deployed contract. What has not: an external audit, and a multisig over admin powers.
What is tested#
| Tool | Scope |
|---|---|
| Hardhat unit tests | behaviour and integration across the launch and DEX contracts |
| Foundry fuzz | curve quoting, fee splitting, and guard conditions |
| Foundry invariants | supply, reserve, and fee-ledger properties over ~16k call sequences |
| Slither | static analysis, with every finding triaged and reasoned |
| Gas snapshots | regression tracking on hot paths |
Curve properties proven by fuzzing#
- The buy fee is exactly 2% floored, and
net + fee == gross. No dust vanishes. - A buy never oversells the 800M allocation, and
ethUsed + refund == gross. - Graduation uses the minimal ETH: one wei less must not graduate. The last buyer is not overcharged.
- A buy followed immediately by a sell is never profitable, so the curve cannot be cycled dry.
- Fees split 70/30 with no dust, and only the registry's recipient can claim.
- A CTO redirect cannot take fees the previous recipient already accrued.
- Selling above circulating supply reverts, so ETH cannot be minted out of virtual reserves.
- Slippage and deadline guards hold; nothing trades after graduation.
- A claim to a contract that rejects ETH reverts without zeroing the balance.
Invariants held#
| Invariant | Why it matters |
|---|---|
totalSupply never changes | fixed supply is a hard promise |
balanceOf(pool) >= realTokenReserve + dexTokenReserve | the pool always covers what it owes |
pool.balance >= realEthReserve | accounting cannot exceed custody |
realEthReserve >= creatorFeeAccrued + protocolFeeAccrued | sells cannot spend earmarked fees |
Σ claimableCreatorFees == creatorFeeAccrued | the fee ledger balances across redirects |
state never regresses from GRADUATING / DEX_ACTIVE | one-way lifecycle |
| LP share value never decreases | swap fees, donations, and rounding accrue to the pool, never leak |
balanceOf(0) >= MINIMUM_LIQUIDITY | a pool cannot be drained and re-primed at a chosen ratio |
Design decisions that carry security weight#
Atomic graduation. Pair creation, funding, minting, and locking happen in one transaction. There is no window in which a half-migrated pool can be traded.
Empty-pair assertion. The migration requires both reserves and both balances to be exactly zero before funding. Strict equality is the point — it is the defence against someone pre-seeding the pair at a fake ratio.
Launch-pair gating. Only the launch factory can create a launch pair, and only the migration manager can seed it. Both halves of a front-run are closed.
Creator set, not a slot. An earlier factory stored one launchPairCreator, so authorising a new launch factory
would have revoked the old one in the same transaction and left in-flight curves unable to graduate — their ETH stranded
with no migration path. That was caught during review, with a live launch mid-curve at the time. The current factory
uses a set with per-creator revocation.
Fees booked, not transferred. A swap makes no external call beyond its two ERC-20 transfers, and the fee recipient is read from the registry only at claim time. No registry call sits on the swap path.
No upgrades. Nothing is a proxy and nothing has an admin-controlled implementation slot. A deployed launch cannot have its rules changed underneath it. The cost is that fixes ship as a new stack.
No metadata setter. tokenURI() derives from the token address against an immutable base, so a live token's
metadata cannot be repointed by anyone.
Open items#
These are known and tracked, not surprises.
- No multisig or timelock on admin powers.
feeToSetter, launch-pair authorisation, andsetupAdminare held by one EOA. This must change before mainnet. It now guards a real fee stream, which raises the priority rather than changing the requirement. - External audit outstanding. Internal review only so far.
- The router has no dedicated invariant suite. Pairs and the curve do; the router is covered by unit tests only.
- Per-launch contracts start unverified. Blockscout does not match new token and curve deployments against already-verified bytecode. A repo script submits them — see network.
Graduation has now been exercised on these addresses: GIWA graduated at block 31727133, migrating 3.6 WETH and
200,000,000 tokens, with the resulting LP position held by the locker. You can verify that yourself by reading
PermanentLiquidityLocker.lockedPair(token) and lockedLiquidity(token).
What is centralized#
Being direct about this, since "decentralized" gets used loosely:
| Power | Who holds it | Can it affect your tokens? |
|---|---|---|
feeTo — protocol fee destination | deploy EOA | no, it only redirects the protocol's own share |
| Launch-pair authorisation | deploy EOA | it gates which launch factories can graduate |
setupAdmin — one-time wiring | deploy EOA | already used; configured blocks reuse |
| Fee recipient per token | the token's creator | yes, for that token's creator fees |
| Locked initial LP | nobody | it cannot be withdrawn at all |
| Token supply and metadata | nobody | no mint, no burn, no URI setter |
The admin powers cannot mint tokens, move your balances, drain a curve, or unlock liquidity.
Reporting something#
Found a problem? Please report it privately rather than opening a public issue, and give us a chance to deploy a fixed stack first. Since nothing is upgradeable, a fix means a redeploy and a front-end cutover, which takes coordination.