Protocol

Security model

Trust assumptions, privileged roles, invariants, what is tested, and what is not.

Phase 1 has not been audited by a third party yet. The suite is small (≈1,200 lines of Solidity), built on OpenZeppelin, and covered by fork tests against Robinhood Chain, but treat it as unaudited code.

Privileged roles

RoleHeld byCanCannot
PinarcConfig.ownerPinarc admin (later governance)Change curve, fee and guard parameters for future launches; set the guardianChange any existing curve (parameters are snapshotted at creation); touch funds
PinarcConfig.guardianAdmin by default; multisig laterCreatorBond.slash(token, reason) — move an active bond into that token's floor reserveSend a bond anywhere else; slash twice; touch curves, floors, vaults or locks
Token creatorLaunch walletsetMetadataURI, transferMetadataAuthority, CreatorBond.release after graduation + lock, withdraw/extend/transfer the LP lock after it expiresMint, pause, blacklist, change fees, pull liquidity
FactoryContractBondingCurve.initialize, commitBatchFor, CreatorBond.postAnything after creation

BondingCurve, FloorReserve, VestingVault and LPLocker have no owner and no admin functions.

Upgradeability

None. BondingCurve clones point at one immutable implementation (EIP-1167); the other contracts are plain. A parameter change means new launches behave differently; existing launches never change.

Invariants

  • tokensSold ≤ supplyOnCurve; graduation happens exactly when equal.
  • Selling exactly supplyOnCurve tokens raises exactly graduationUsdg (by construction of virtualUsdg).
  • The curve never holds fees: every fee is split and transferred in the same call.
  • After graduation the curve holds only unclaimed batch tokens and refunds.
  • FloorReserve.redeem burns before it pays, and pays reserve × amount / totalSupply, so the floor price is non-decreasing under redemptions.
  • A vesting schedule releases at most total; vestedAmount is monotone in time.
  • A lock can only be extended, transferred or withdrawn by its owner, and only withdrawn after unlockAt.

Defences

  • ReentrancyGuard on every state-changing external function of every contract.
  • SafeERC20 everywhere; forceApprove before router / vault / floor / bond calls.
  • Rounding is always against the caller (ceilDiv in quotes) so the curve cannot be drained by dust.
  • Pair-price alignment neutralises pre-seeded pools at graduation; if alignment is too expensive the migration still completes.
  • _validate rejects floor > max, team > max, bad vesting schedules and bonds below the minimum.
  • Anti-sniper limits are enforced on the recipient (to), so routing through a fresh contract does not bypass them.

Fork test coverage

test/Launchpad.t.sol runs against a fork of Robinhood Chain with the real Uniswap V2 deployment: parameter snapshotting, bond + dev buy + team vesting at creation, rejected choices, single clearing price and claims, per-wallet batch cap, anti-sniper limits then relaxation, buy/sell round trip fee accounting, quotes vs execution, graduation with LP lock and floor funding, LP burn variant, graduation against a pre-seeded skewed pair, bond slash into the floor, and a batch that graduates on its own.

Known limitations

  • The guardian is a single key until the multisig is in place; it can only move bonds into the floor, but it can do so at its discretion.
  • maxWallet is checked on to's balance only during the window; a buyer can still spread across wallets.
  • Metadata is off-chain by design; the app sanitises it, but a self-hosted metadataURI can point anywhere.
  • If a pre-seeded pair is skewed so badly that alignment would cost more than half of either side, the migration adds at the pool's price.
  • Phase 1 has no presale, allowlist, escrow or governance contracts; those designs are not audited either because they do not exist yet.