Architecture
OddMaki's smart contract architecture — EIP-2535 Diamond proxy, facets, and modular upgrade mechanism.
OddMaki is deployed as a single EIP-2535 Diamond proxy on Base. All protocol functionality — venue management, market creation, orderbook, matching, resolution (UMA and Pyth), batch operations, and vault custody — lives in separate facets that share state through isolated storage libraries.
Diamond Proxy (EIP-2535)
The Diamond pattern splits a monolithic contract into multiple implementation contracts (facets) behind a single proxy address. When a call arrives, the fallback() function looks up which facet implements the called selector, then delegatecalls into it. All facets share the same storage space.
Why EIP-2535:
- 100+ external functions across 18 business domains — far beyond the 24KB contract size limit
- Individual facets can be upgraded without touching unrelated logic
- New features — like Pyth-powered price markets, batch order operations, or per-market access control — can be added by deploying a single new facet
Facets
The protocol deploys 21 facets: 3 core Diamond infrastructure + 18 business logic.
Core Infrastructure
| Facet | Purpose |
|---|---|
DiamondCutFacet | Upgrade entry point — diamondCut() |
DiamondLoupeFacet | Introspection — facets(), facetAddress() |
OwnershipFacet | Diamond ownership — transferOwnership(), owner() |
Business Logic
| Facet | Domain | Key Functions |
|---|---|---|
VenueFacet | Venue config | createVenue(), updateFees(), updateVenue(), setPaused() |
ProtocolFacet | Protocol config | setProtocolFeeBps(), setProtocolTreasury(), suspendVenue() |
AccessControlFacet | Access gates | setMarketAccessControl(), isTradingAllowed() |
MarketsFacet | Market creation | createMarket(), pauseMarket(), unpauseMarket() |
MarketGroupFacet | Market groups | createMarketGroup(), addMarketToGroup(), activateMarketGroup() |
PriceMarketFacet | Price markets | createPriceMarket() (Pyth-fed) |
MetadataFacet | Event metadata | updateMarketMetadata(), updateMarketGroupMetadata() |
TagsFacet | Event tags | updateMarketTags(), updateMarketGroupTags() |
LimitOrdersFacet | Limit orders | placeOrder(), cancelOrder(), cancelOrdersOnResolvedMarket() |
MarketOrdersFacet | Market orders | placeMarketOrder() (FOK/FAK) |
BatchOrdersFacet | Batch operations | batchPlaceOrders(), batchCancelOrders(), cancelAndReplace() |
MatchingFacet | Order matching | matchOrders() |
OrderBookFacet | Read-only queries | getTopOfBook(), getMarkPrice() |
VaultFacet | Token custody | splitPosition(), mergePositions() |
ResolutionFacet | UMA resolution | assertMarketOutcome(), settleAssertion(), reportResolution() |
PythResolutionFacet | Pyth resolution | resolvePriceMarket() |
NegRiskFacet | Position conversion | convertPositions() |
ERC1155ReceiverFacet | Token receiving | onERC1155Received() |
Upgrade Mechanism
The Diamond owner calls diamondCut() with an array of FacetCut structs:
| Action | What It Does |
|---|---|
| Add | Map new function selectors to a new facet |
| Replace | Point existing selectors to an updated facet |
| Remove | Delete selector mappings (facetAddress must be address(0)) |
An optional initialization hook (_init + _calldata) runs after applying cuts — useful for one-time migration logic. Every new facet (batch orders, price markets, per-market access control) was added this way without disturbing existing storage or state.
Storage Model
All state uses namespaced storage — each domain gets its own struct at a unique keccak256 slot. This prevents collision between facets.
| Storage Library | Domain |
|---|---|
LibProtocolStorage | Protocol fee bps, treasury, oracle addresses |
LibVenueStorage | Venue configs, ID counter |
LibMarketRegistryStorage | Market lifecycle, ID counter |
LibMarketTradingStorage | Position IDs, volume, paused flag |
LibMarketOracleStorage | CTF condition, UMA params |
LibOrderStorage | Order structs, ID counter |
LibOrderBookStorage | Tick levels, top of book |
LibFillStorage | Fill records |
LibMarketGroupStorage | Group data |
LibResolutionStorage | UMA assertion data |
LibPriceMarketStorage | Pyth feed, strike, close time |
LibNegRiskStorage | Wrapped collateral mappings |
LibAccessControlStorage | Per-market access overrides |
External Dependencies
| Dependency | Purpose |
|---|---|
| Gnosis CTF | ERC-1155 outcome tokens — split, merge, redeem positions |
| UMA Optimistic Oracle V3 | Decentralized dispute resolution for binary and grouped markets |
| Pyth Network | Price feeds for auto-resolving price markets |
| OpenZeppelin | ReentrancyGuard, IERC20, IERC1155 |
Security Model
| Tier | Who | Can Do |
|---|---|---|
| Diamond Owner | Protocol admin | Upgrade facets, set treasury/oracle/CTF, set protocol fee (0–200 bps), suspend venues |
| Venue Operator | msg.sender of createVenue | Update venue config, pause/unpause venue, pause individual markets (immutable role) |
| Market Creator | msg.sender of createMarket | Update tags/metadata, set per-market access control |
| External Access Control | Optional per-venue or per-market contracts | Gate trading and market creation |
| Participants | Anyone | Place orders, match, assert outcomes, resolve price markets |
There is no global protocol pause. Pausing is scoped to individual venues (by their operator) or individual markets (by operator/creator). The protocol owner holds a separate suspend lever for emergencies.
What's Next
- Orderbook → — how the on-chain CLOB works
- Price Markets → — Pyth-fed auto-resolving markets
- Token Mechanics → — outcome tokens and the CTF
- Contracts → — deployed addresses and subgraph endpoints