Threats and what bounds them
Oracle latency and manipulation (vaults)
A vault mints and redeems at whatever its oracle returns. Two attacks follow: trading against a stale rate when the real market has moved (latency arbitrage), and a wrong rate posted by a compromised keeper.- Fees bound the profit of latency arbitrage: 0.3% each way when fresh, 1% during the stale window, so a round trip costs 0.6% to 2%, which exceeds typical intraday FX moves. Underwriters accept the residual risk in exchange for 80% of those fees.
- The keeper oracle rejects any post that moves a rate by more than 15% (
maxMoveBps = 1500), so one bad post is bounded; it never accepts a rate older than the stored one, and posts cannot be dated in the future. - The keeper key on the VPS holds only the keeper role and a little gas. The owner key (deployer) can
forcePostany rate and re-allow-list keepers; that is the strongest single point of trust in the system today. - After 2 hours without a post the vault charges the stale fee and closes underwriting; after 5 days it refuses dollar conversions altogether (
StalePrice). Curve trading in the quote asset is unaffected by the oracle at every stage.
Vault insolvency after a large FX move
If a currency strengthens against the dollar after minting, the dollar value of outstanding synthetic rises above the vault’s assets.minCRBpsgates mints and underwriter withdrawals, never redemptions. At the deployed 100% every mint is fully backed at its own rate; a higher setting (for example 110%) would require underwriter equity before minting.- Below 100% collateral,
redeemscales the payout byassets / liability(the haircut), so early redeemers cannot drain the vault ahead of later ones, andinvariant_redeemingEverythingNeverExceedsAssetsholds under random mints, redeems, deposits, withdrawals and ±10% rate steps. - Underwriters lose first: their equity absorbs the move before any holder is haircut. With no underwriters, holders share the loss pro rata until fees rebuild a buffer.
- The liability cap (1,000,000 dollars per vault) bounds the exposure of any single currency.
Curve manipulation and MEV
The curve has no external price input after creation, so nothing off-chain can be manipulated to move it; only trades do. A trader’sminTokensOut / minQuoteOutRaw is the sole protection against sandwiching, and every quote function is exact for the block it runs in. Fees make round trips strictly unprofitable (testFuzz_buySell_neverProfits) and the invariant suite checks solvency, token conservation and that the reserve never exceeds the graduation target. Creators have no special powers over a curve beyond claiming their fee share; supply, fees and reserves are immutable after initialize.
Graduation griefing
Anyone can pre-initialise the Uniswap v4 pool for a launch’s pair at an arbitrary price before graduation. The curve handles it:initializePool is a no-op on an initialised pool, the curve reads the actual sqrtPriceX96 and provides full-range liquidity at that price. A large mispricing would then be arbitraged by the first swap; the position itself remains locked. graduate() is permissionless and idempotent, and the router wraps its automatic attempt in try so a pool-side failure cannot block the filling buy (test_graduate_poolPreInitialisedByGriefer_stillWorks, test_buyWithUSDC_overshoot_refundsAndAutoGraduates).
Admin keys
Every admin function is listed here; there is no timelock and no multisig yet, and the deployer account holds every owner role (Ownable2Step, so a transfer takes transferOwnership plus acceptOwnership by the new owner).
The one factory power worth underlining:
setAddresses changes the locker and PositionManager that future graduations mint to, for every launch that has not graduated yet. Positions already minted are unaffected. Protocol fees flow to feeCollector (the deployer by default) through pull calls only.
Reentrancy
Router, LaunchFactory, FxVault and LiquidityLocker use OpenZeppelin’s ReentrancyGuard; BondingCurve uses ReentrancyGuardTransient (transient storage, which is why the chain must support Cancun). Every state-changing entry point that moves tokens is guarded, state is updated before external transfers (checks-effects-interactions), and the router holds no balances between calls. Launch tokens and synthetics are plain OpenZeppelin ERC-20s without hooks; the dollar and EURC are the issuers’ contracts.
Upgradeability and immutability
No proxies.BondingCurve clones are minimal proxies to an immutable implementation that locks itself in its constructor; every clone is initialised exactly once. FxVault.oracle, usdc, synthToken and code are immutables; Router.registry and usdc are immutables; LaunchFactory.curveImplementation and LiquidityLocker.posm are immutables. Changing behaviour means deploying new contracts and pointing the factory or registry at them, which never affects existing curves, vaults or positions.
The dollar blocklist (Arc)
Arc’s native USDC reverts any transfer touching a blocklisted address. Every fee and reward is therefore pull-claimed:claimCreatorFees, sweepProtocolFees, claimProtocolFees, LiquidityLocker.collect. The only pushed transfers go to msg.sender (refunds of untaken quote, sale proceeds, excess value), so a blocklisted caller can only fail their own transaction. USDG on Robinhood Chain is a Paxos token with its own compliance controls; the same pull design applies.
Randomness and ordering
Nothing readsPREVRANDAO (always 0 on Arc) or block numbers; deadlines and oracle ages are timestamps. Instant finality on both chains means events are final when emitted.
Test suite
forge test in contracts/ runs the unit, fuzz and invariant suites (256 fuzz runs and 64 invariant runs of depth 32 by default; 2,000 fuzz runs in the ci profile). Uniswap v4 is deployed from artifacts inside the tests, and mock ERC-20s stand in for the dollar because standard anvil cannot execute Arc’s native USDC.