Contract surface
A deployed covenant is a single contract on Base. It extends OpenZeppelin’s ERC-20 with three bounded extensions:
| Method | Caller | Purpose |
|---|---|---|
| constructor(R, s, T, e) | Platform deployer | Mints 10,000 tokens to the treasury; sets the four immutable parameters |
| allocate(backer, amount) | Platform-only | Records backer allocation against treasury tokens during the funding phase |
| distribute(teb) | Platform-only | Records a quarterly TEB report; dividend = s × teb pro-rata; updates on-chain payout index |
| claim(holder) | Holder or platform | Pulls accrued dividend to the holder's wallet (pull-payment pattern) |
| unlock() | Platform-only | Irreversibly enables token transfer once the three pre-IPO triggers clear |
| transitionToPersonToken() | Platform-only | At year T, switches distribution logic from Phase 1 (s × TEB) to Phase 2 (e × TEB) |
| pause() | Platform emergency | Halts transfers + distributions during investigation (see delisting framework) |
That’s the whole surface. Everything else — forecast, conviction, pricing, cross-listing cap-check — runs off-chain in libraries covered by 75 passing tests. The contract is the arithmetic that absolutely has to be on-chain; the rest is an implementation detail of the platform.
On-chain invariants
Eight invariants that must hold across every block. Violation of any one is a critical-severity bug:
| # | Invariant | Enforcement |
|---|---|---|
| I₁ | Token supply is exactly forever | No mint() / burn() public; constructor mints once, never again |
| I₂ | Sum of holder balances + treasury balance = totalSupply | ERC-20 default; checked by every transfer path |
| I₃ | funded_amount monotonic non-decreasing until unlock() | allocate() guards against negative or zero amounts; no withdrawal path pre-unlock |
| I₄ | No transfer executable while locked | transfer() reverts with LOCKED error if !unlocked; emits Locked event on attempt |
| I₅ | unlock() is one-way | Once unlocked flag flips true, no path sets it back to false; no re-lock() method exists |
| I₆ | Phase transition is one-way, fires exactly once, at year T | transitionToPersonToken() checks both phase == ISA and block.timestamp >= T; storage flag prevents re-entry |
| I₇ | AON escrow returns funds if minimum threshold not met | Per SEA Rule 15c2-4. settle() validates clearing; on failure refundAll() releases every escrowed bid atomically. |
| I₈ | Cap-ledger 25% invariant cross-validated by two algorithms | Analytic transition-scan + monthly-bucket scan; both must agree before any new obligation is admitted. See /spec/cross-listing-math. |
Why it matters —Each invariant has a Solidity unit test plus an invariant-fuzzing test (via Foundry) that attempts randomized call sequences to break it. The invariants are the foundation every off-chain price depends on — if the on-chain supply can be inflated, every price on the secondary market is wrong by the same factor.
Access control
Three roles, each with strictly bounded authority:
| Role | Authorized actions | How assigned |
|---|---|---|
| Platform admin | allocate(), distribute(), unlock(), transitionToPersonToken(), pause() | Multi-sig (3-of-5) controlled by Preflop operations; rotateable |
| Issuer | View-only on their covenant; initiate the quarterly report flow off-chain which the platform ratifies on-chain | Bound to the issuer wallet in constructor |
| Holder | claim() for their own accrued dividend; transfer() once unlocked | Held by any wallet with a non-zero balance |
Platform admin is the only privileged role. The multi-sig means no single key compromise enables unauthorized allocate / distribute / unlock. Transitions (unlock, Phase 2) require an explicit multi-sig execution trail recorded on-chain.
Upgrade posture · immutable
Deployed covenants are immutable. No proxy, no upgrade path, no admin-callable logic swap. Every operational parameter that could change (timing, thresholds, fee routing) is off-chain. The on-chain contract is a 400-LOC frozen artifact.
The tradeoff is explicit: we give up flexibility in exchange for a simpler audit surface and a guarantee to holders that the terms at issuance cannot be retroactively modified. If a bug is discovered, the path is:
- —Minor (cosmetic / event emission): accept, document, fix in next vintage.
- —Moderate (auxiliary feature): pause() affected contracts, deploy a parallel migration path, holders opt-in to migrate.
- —Critical (invariant-breaking): pause() all affected contracts, governance-approved emergency migration plan with mandatory holder notification.
Audit roadmap
| Stage | Target horizon | Status |
|---|---|---|
| Internal review + Foundry invariant suite | Done | 75+ tests + fuzzing across all six invariants |
| Slither + Mythril static analysis | Done | No high-severity findings outstanding |
| External security audit (tier-1 firm) | Pre-mainnet launch | Planned; budget earmarked |
| Bug bounty (live) | Mainnet launch | Immunefi program planned; scales with TVL |
| Formal verification (Certora or equivalent) | Post-launch, 12 months | Long-term goal; targeted at the six invariants specifically |
Why it matters —Formal verification on six named invariants is a realistic target for a contract this small. Most DeFi protocols stop at audit + bounty because the surface is too large; Preflop’s minimal-surface design means the invariants are tractable to prove mechanically.
Known risks & mitigations
| Risk | Severity | Mitigation |
|---|---|---|
| Platform multi-sig key compromise | High | 3-of-5 threshold; keys rotated quarterly; HSM for primary signers; incident response playbook |
| Issuer dispute over TEB reporting | Medium | Off-chain arbitration; on-chain record of every report with content hash; delisting framework for uncured disputes |
| Layer-1 (Base) chain-level incident | Low-Medium | Base is Ethereum L2 secured by Ethereum mainnet; Preflop tokens are portable if Base migrates/sunsets (escape hatch in the platform, not in the covenant contract) |
| Oracle dependence | Low | Minimal — distribute() takes TEB as input from the platform (which derives it off-chain from Plaid + tax attestations); no external oracle dependency in the hot path |
| Reentrancy on claim() | Low | Pull-payment pattern; OpenZeppelin ReentrancyGuard applied; state mutations precede transfers |
| Front-running of allocate() | Low | Allocations are platform-only via signed authorization; no public allocate() for backers to front-run |