smart-contracts/backlog/docs/CoW_Architecture_Diagrams.md

807 lines
29 KiB
Markdown

# CoW Protocol + MycoFi Integration: Architecture Diagrams
## 1. Complete Order Lifecycle
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ ORDER LIFECYCLE: USER INTENT → EXECUTION │
└─────────────────────────────────────────────────────────────────────────────┘
PHASE 1: USER CREATES ORDER (Off-chain, Private)
────────────────────────────────────────────────────────
User Wallet
├─ Signs intent message:
│ {
│ "buy": 1000 MYCO
│ "sell": 500 USDC
│ "minOut": 900 MYCO
│ "validTo": block.timestamp + 24h
│ }
└─ Message stays PRIVATE (not in mempool)
CoW API (off-chain orderbook)
PHASE 2: BATCH COLLECTION (~30 seconds)
────────────────────────────────────────
Watch Tower Service
├─ Collects all pending intents (private)
├─ Polls MycoConditionalOrder.getTradeableOrder()
│ ├─ Static input: {direction, amount, minOut, receiver}
│ ├─ Calls BondingCurveAdapter.quoteBuy() for live quote
│ └─ Returns GPv2Order.Data ready for solver
└─ Broadcasts order batch to solvers
PHASE 3: SOLVER AUCTION (Typically <5 seconds)
───────────────────────────────────────────────
Solver 1 Solver 2 Solver 3
(Uniswap routing) (Direct CoW matching) (1inch aggregation)
│ │ │
└─ Computes solution ├─ Finds Alice ↔ Bob ├─ Finds best paths
(all orders) │ direct swap │ through DEXs
Surplus = $5 │ Surplus = $8 │ Surplus = $3
│ WINNER! ✓
GPv2Settlement.settle()
receives solution from winning solver
PHASE 4: ON-CHAIN SETTLEMENT
───────────────────────────
GPv2Settlement.settle()
├─ PRE-INTERACTION HOOKS
│ └─ None for basic orders
├─ INPUT TRANSFERS (via VaultRelayer)
│ └─ User's USDC transferred from Balancer Vault to adapter
│ VaultRelayer (authorized) pulls 500 USDC
├─ CUSTOM LOGIC (via pre-interaction)
│ └─ BondingCurveAdapter.executeBuyForCoW(500 USDC, 900 MYCO min)
│ ├─ Calls MycoBondingCurve.buy(500 USDC)
│ ├─ MYCO minted to adapter (1000 tokens)
│ └─ Returns 1000 MYCO to settlement
├─ OUTPUT TRANSFERS (via VaultRelayer)
│ └─ Settlement pulls 1000 MYCO from adapter to user
└─ POST-INTERACTION HOOKS
└─ None for basic orders
FINAL STATE
───────────
✓ User: -500 USDC, +1000 MYCO
✓ BondingCurve: +500 USDC reserve, -1000 MYCO supply
✓ Solver: Received 8 USDC surplus (profit for execution)
✓ Gas: Paid by solver (deducted from surplus)
```
---
## 2. Contract Interaction Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ CONTRACT INTERACTION ARCHITECTURE │
└─────────────────────────────────────────────────────────────────┘
USER/WALLET
├─ Approves Balancer Vault (not Settlement!)
│ └─ IERC20(USDC).approve(balancerVault, uint256.max)
└─ Signs intent (off-chain)
Watch Tower (off-chain bot)
└─ Calls MycoConditionalOrder.getTradeableOrder()
├─ Decodes staticInput (OrderData)
├─ Decodes offchainInput (OffchainData from Watch Tower)
├─ Calls BondingCurveAdapter.quoteBuy() for live quote
│ └─ Returns expected MYCO output
└─ Generates GPv2Order.Data struct
├─ sellToken: USDC
├─ buyToken: MYCO
├─ sellAmount: 500 USDC
├─ buyAmount: 1000 MYCO (from quote)
├─ receiver: user address
└─ validTo: quote.validTo
Solver (winning solver)
└─ Calls GPv2Settlement.settle()
├─ Verifies order signature (ERC-1271 via Authenticator if Safe)
├─ Calls VaultRelayer.transferFromAccounts()
│ └─ Transfers 500 USDC from user's Vault allowance
│ → Sends to BondingCurveAdapter
├─ INTERACTION: Calls BondingCurveAdapter.executeBuyForCoW()
│ │
│ ├─ Checks only called from Settlement (modifier)
│ ├─ Checks USDC balance (already received)
│ ├─ Calls BondingCurve.buy(500 USDC, 900 MYCO min)
│ │ │
│ │ ├─ Calculates price: P(supply) = basePrice + coeff*(supply^exp)
│ │ ├─ Mints 1000 MYCO to adapter
│ │ └─ Returns amount minted
│ │
│ └─ Returns 1000 MYCO to Settlement
├─ Calls Vault.transferToAccounts()
│ └─ Transfers 1000 MYCO from adapter → user wallet
└─ Emits OrderFulfilled event
TOKENS HELD DURING EXECUTION
─────────────────────────────
Settlement: Temporarily holds order tokens (atomic)
Adapter: Receives input, produces output (pre/post interaction logic)
VaultRelayer: Only pulls/pushes via Settlement call
```
---
## 3. MEV Attack Prevention Visualization
```
┌──────────────────────────────────────────────────────────────┐
│ MEV PROTECTION: CoW vs Traditional AMM │
└──────────────────────────────────────────────────────────────┘
TRADITIONAL AMM (Uniswap-style):
──────────────────────────────────
Mempool (public visibility)
├─ Alice's TX: Buy 1000 MYCO for 500 USDC
│ (broadcast in mempool, visible to all)
├─ MEV Bot observes:
│ "Alice buying MYCO, will push price up"
├─ Bot's sandwich attack:
│ 1. Bot front-runs: Buy MYCO first
│ ├─ Price moves: 0.5 → 0.51 MYCO/USDC
│ └─ Bot pays: 490 USDC for 961 MYCO
│ 2. Alice's TX executes
│ ├─ Price higher: 0.51 MYCO/USDC
│ └─ Alice pays: 500 USDC for 980 MYCO (loss vs no MEV!)
│ 3. Bot back-runs: Sell MYCO
│ ├─ Sells 961 MYCO for 490 USDC
│ └─ Bot profit: ~10 USDC (extracted from Alice)
└─ Alice LOSES ~20 MYCO due to MEV extraction!
CoW PROTOCOL (Batch Auction):
──────────────────────────────
Private Order Collection
├─ Alice's intent: "Buy 1000 MYCO for 500 USDC" [SIGNED, PRIVATE]
├─ Bob's intent: "Buy 600 MYCO for 300 USDC" [SIGNED, PRIVATE]
└─ Both intents stay PRIVATE (not in mempool)
MEV bots can't see them!
Batch Closure (30 seconds)
├─ CoW collects orders: {Alice, Bob, Charlie, Dave, ...}
└─ Solvers compete to find best settlement
Solver Auction:
├─ Solver A (DEX routing):
│ └─ Route through Uniswap → 950 MYCO for Alice
├─ Solver B (Direct CoW matching):
│ ├─ Alice wants: 1000 MYCO for 500 USDC
│ ├─ Bob has MYCO but wants stables
│ ├─ Direct swap: Alice ↔ Bob
│ │ └─ Alice: 500 USDC → 1000 MYCO (NO AMM, no slippage!)
│ │ └─ Bob: 1000 MYCO → 500 USDC
│ └─ Surplus: $0 MEV extracted (perfect execution!)
└─ Solver B WINS (best price for users)
On-chain Settlement:
└─ Alice pays: 500 USDC
Alice receives: 1000 MYCO
No sandwich. No MEV extraction. MEV-protected!
KEY PROTECTIONS:
─────────────────
✓ Private Order Flow: Intent never in public mempool
✓ Uniform Clearing Price: All trades at same price in batch
✓ Coincidence of Wants: Direct peer-to-peer when possible
(No AMM slippage, no liquidity provider fees)
```
---
## 4. BondingCurveAdapter Token Flow
```
┌───────────────────────────────────────────────────────────────┐
│ TOKEN FLOW: BondingCurveAdapter in Settlement │
└───────────────────────────────────────────────────────────────┘
SCENARIO: User buys 1000 MYCO with 500 USDC via CoW Protocol
INITIAL STATE:
──────────────
User Wallet:
├─ 5000 USDC
└─ 100 MYCO
└─ Approval: Balancer Vault can spend 5000 USDC
BondingCurveAdapter:
└─ 0 USDC, 0 MYCO (fresh contract)
Balancer Vault:
└─ Holds user's approved allowances
STEP 1: Solver calls GPv2Settlement.settle()
─────────────────────────────────────────────
Settlement.settle({
orders: [order_from_user],
inTransfers: [500 USDC],
outTransfers: [1000 MYCO],
interactions: [call BondingCurveAdapter.executeBuyForCoW()]
})
STEP 2: Settlement pulls input tokens
──────────────────────────────────────
VaultRelayer.transferFromAccounts(
[User],
[USDC],
[500e6], // 500 USDC (6 decimals)
[BondingCurveAdapter]
)
User Wallet: BondingCurveAdapter: VaultRelayer:
5000 USDC → 0 USDC → (transfer)
4500 USDC ←─ pull ──┘
STEP 3: Settlement calls pre-interaction hook
──────────────────────────────────────────────
BondingCurveAdapter.executeBuyForCoW(
500e6, // usdcAmount
900e18 // minMycoOut (slippage protection)
)
├─ Checks: "Only called from Settlement" ✓
├─ Checks: "USDC balance >= 500e6" ✓ (we have 500 USDC)
├─ Calls BondingCurve.buy(500e6, 900e18)
│ │
│ ├─ Current supply: 10,000,000 MYCO
│ ├─ Price calc: P = basePrice + coeff * supply^exponent
│ ├─ Calculates output: ~1000 MYCO
│ ├─ Checks min output: 1000 >= 900 ✓
│ │
│ ├─ Burns tokens: bondingCurve.buy() path:
│ │ ├─ Adapter already approved to spend curve's USDC
│ │ ├─ USDC.transferFrom(adapter, curve, 500e6) ✓
│ │ └─ Curve updates: reserveBalance += 500 USDC
│ │
│ └─ Mints to adapter:
│ └─ MycoToken.mint(adapter, 1000e18) ✓
└─ Returns: 1000 MYCO minted
State after hook:
User: 4500 USDC, 100 MYCO
Adapter: 0 USDC, 1000 MYCO (minted by curve)
Curve: 500 USDC reserve, supply-1000
STEP 4: Settlement pulls output tokens (to user)
────────────────────────────────────────────────
Settlement registers: "Adapter owes 1000 MYCO"
Vault.transferToAccounts(
[BondingCurveAdapter],
[MYCO],
[1000e18],
[User]
)
VaultRelayer pulls from adapter (via Vault):
Adapter: Vault: User:
1000 MYCO → (pull) → 100 MYCO
0 MYCO ←───────────────────────── 1100 MYCO
FINAL STATE:
────────────
User Wallet:
├─ 4500 USDC (spent 500)
├─ 1100 MYCO (gained 1000) ✓
└─ Approval reset to available balance
BondingCurveAdapter:
└─ 0 USDC, 0 MYCO (drained, ready for next order)
BondingCurve Contract:
├─ reserveBalance: +500 USDC
├─ totalSupply: -1000 MYCO
└─ Ready for next user
KEY SECURITY POINTS:
─────────────────────
✓ Adapter doesn't hold tokens permanently
✓ All transfers atomic (via Settlement)
✓ VaultRelayer prevents unauthorized fund access
✓ Reentrancy guard protects during curve execution
✓ Quote validation ensures minimum output respected
```
---
## 5. Conditional Order State Machine
```
┌─────────────────────────────────────────────────────────────┐
│ IConditionalOrder State & Polling Lifecycle │
└─────────────────────────────────────────────────────────────┘
CONDITIONAL ORDER LIFECYCLE:
────────────────────────────
[CREATED]
└─ ComposableCoW.create({
handler: MycoConditionalOrder,
salt: unique_identifier,
staticInput: abi.encode({
direction: BUY,
inputAmount: 500e6, // 500 USDC
minOutputAmount: 900e18, // 900 MYCO minimum
receiver: user,
validityDuration: 86400 // 24 hours
})
})
└─ Stored in ComposableCoW registry
└─ Hashmap: owner → conditionalOrders[]
[POLLING] (every block, ~12 seconds on Ethereum, ~2 sec on Base)
└─ Watch Tower calls:
MycoConditionalOrder.getTradeableOrder(
owner: user_address,
sender: watchtower,
staticInput: above_encoded_data,
offchainInput: abi.encode({
quotedOutput: 1000e18, // Current quote from adapter
validTo: now + 30 min // Quote expiry
})
)
└─ Handler validates:
├─ Quote not expired? ✓
├─ quotedOutput >= minOutputAmount? ✓
├─ User has sufficient balance? ✓
└─ Returns GPv2Order.Data ready for solver
[SUBMITTED]
└─ Watch Tower submits to solver network
├─ Submits valid orders
└─ Retries failed orders next block
[AWAITING_SOLVER]
├─ Solvers receive order batch
├─ Compete to solve efficiently
└─ Winning solver selected
[EXECUTED / FULFILLED]
└─ Winning solver calls GPv2Settlement.settle()
├─ Order confirmed on-chain
└─ User receives MYCO
[REVERTED / CANCELLED]
├─ User calls ComposableCoW.cancel(orderHash)
│ └─ Order removed from registry
└─ Or if:
├─ Quote expires
├─ Balance insufficient
└─ Validity duration exceeded
WATCH TOWER ERROR HANDLING:
───────────────────────────
getTradeableOrder() returns error? What to do?
PollTryNextBlock("reason")
└─ Temporary issue (likely)
└─ Retry next block (~12 sec)
└─ Examples:
├─ User balance just transferred out
├─ Price quote slightly stale
└─ Network congestion
OrderNotValid("reason")
└─ Order invalid right now
└─ Will check again next block
└─ Examples:
├─ Quote expired
├─ Output below minimum
└─ Order not yet valid
PollNever("reason")
└─ STOP polling this order permanently
└─ Remove from active orders
└─ Examples:
├─ User disabled notifications
├─ Permanent configuration error
└─ Handler contract error
POLLING FREQUENCY (Base):
─────────────────────────
Block time: ~2 seconds
Watch Tower poll: every block
Batch window: ~30 seconds
Typical orders in batch: 100-1000+
Expected time from intent → execution: 5-60 seconds
(depending on batch phase and solver competition)
```
---
## 6. Security Architecture: Fund Protection
```
┌──────────────────────────────────────────────────────────────┐
│ SECURITY: How Funds Are Protected │
└──────────────────────────────────────────────────────────────┘
THE PROBLEM: Direct Settlement Authorization
───────────────────────────────────────────────
If user approved Settlement directly:
USDC.approve(GPv2Settlement, 5000 USDC)
Malicious solver could:
└─ In pre-interaction hook:
├─ Call Settlement.transferFrom(user, attacker, 5000 USDC)
└─ Steal all approved funds!
THE SOLUTION: Three-Layer Authorization
──────────────────────────────────────────
LAYER 1: Balancer Vault (Token Holder)
└─ User approves Vault (not Settlement):
USDC.approve(BalancerVault, uint256.max)
└─ Vault stores user allowances separately
LAYER 2: VaultRelayer (Authorized to Pull)
└─ VaultRelayer is ONLY authorized address
└─ VaultRelayer.transferFromAccounts(users, tokens, amounts, recipients)
└─ Can ONLY transfer TO Settlement
└─ No other transfers allowed
LAYER 3: Settlement (Atomic Execution)
└─ Settlement.settle() orchestrates:
├─ Calls VaultRelayer to pull input tokens
│ └─ VaultRelayer pulls from Vault (user's allowance)
│ └─ Transfers to specified recipient (adapter/settlement)
├─ Executes interactions (pre, during, post)
│ └─ Can't make arbitrary token calls (can call whitelisted)
└─ Calls VaultRelayer to return output tokens
└─ VaultRelayer pulls from adapter/settlement
└─ Returns to user
ATTACK SURFACE: What Malicious Solver CAN'T Do
────────────────────────────────────────────────
❌ Drain USDC allowance
└─ Settlement doesn't have direct access
└─ VaultRelayer only transfers to Settlement
└─ Settlement immediately re-transfers to user
❌ Call arbitrary token functions
└─ Settlement.settle() params validated
└─ Only authorized interactions executed
└─ Adapter is whitelisted
❌ Reenter settlement
└─ ReentrancyGuard on Settlement
└─ ReentrancyGuard on Adapter
❌ Access Vault directly
└─ VaultRelayer is the ONLY authorized puller
└─ Solver can't call Vault directly
SAFETY VERIFICATION CHECKLIST:
────────────────────────────────
Before deployment:
☑ Users approve BalancerVault, NOT Settlement
☑ Adapter approved for finite amounts (not uint256.max? Or whitelisted handler?)
☑ Adapter onlySettlement modifier on execution functions
☑ Settlement has ReentrancyGuard
☑ Adapter has ReentrancyGuard
☑ VaultRelayer address set correctly
☑ Quote expiry enforced in handler
☑ Slippage protection (minOutputAmount) enforced
☑ No direct token transfers without Settlement mediation
```
---
## 7. Watch Tower Polling Sequence
```
┌──────────────────────────────────────────────────────────┐
│ Watch Tower Polling Sequence (Every Block) │
└──────────────────────────────────────────────────────────┘
TIME: Every ~2 seconds (Base block time)
Watch Tower Service (background job)
├─ FOR EACH registered conditional order handler:
│ 1. GET all orders in ComposableCoW registry
│ └─ Orders: owner → handler → [order1, order2, ...]
│ 2. FOR EACH order in registry:
│ ┌─ Fetch staticInput (from registry)
│ │ {direction, inputAmount, minOutputAmount, receiver, validityDuration}
│ │
│ ├─ Fetch current quote from adapter
│ │ offchainData = {
│ │ quotedOutput: BondingCurveAdapter.quoteBuy(inputAmount),
│ │ validTo: now + 30min
│ │ }
│ │
│ ├─ CALL: MycoConditionalOrder.getTradeableOrder(
│ │ owner,
│ │ watchTowerAddress,
│ │ staticInput,
│ │ offchainData
│ │ )
│ │
│ ├─ Handler validates & returns GPv2Order.Data
│ │ └─ May throw:
│ │ ├─ PollTryNextBlock → Skip this block, retry next
│ │ ├─ OrderNotValid → Skip, retry later
│ │ ├─ PollNever → Remove from polling
│ │
│ ├─ Order is valid → Register in active orders
│ │ └─ Add to "orders to submit to solvers"
│ │
│ └─ Submit to solver network
│ └─ POST /orders with signed order
└─ LOOP: Wait for next block (in parallel, other handlers)
TIMELINE EXAMPLE (Real-world MycoFi order):
────────────────────────────────────────────
T+0s: User signs intent (private, off-chain)
└─ Creates conditional order in ComposableCoW
T+2s: Watch Tower polls
├─ Fetches static order params
├─ Calls adapter.quoteBuy() for live price
├─ Calls getTradeableOrder() → returns valid GPv2Order
└─ Submits to solver network
T+4s: (Poll cycle 2) ← Order already submitted, will appear in next batch
T+30s: Batch closes
└─ CoW collects all orders submitted in this batch window
T+32s: Solvers receive batch
├─ Solver A begins solving
├─ Solver B begins solving
└─ Solver C begins solving
T+35s: Solver B finds best solution
├─ Surplus: $8 profit for execution
└─ Submits GPv2Settlement.settle() tx
T+37s: Transaction included in block
├─ VaultRelayer pulls 500 USDC
├─ BondingCurveAdapter.executeBuyForCoW() executes
├─ MYCO minted, transferred to user
└─ USER RECEIVES 1000 MYCO! ✓
Total latency: ~37 seconds (quite reasonable!)
WHAT IF QUOTE EXPIRES?
──────────────────────
T+0s: Order created, quote valid until T+30s
T+28s: Watch Tower polls
├─ quotedOutput fetched
└─ Order submitted to solver
T+30s: Quote expires (validTo timestamp)
T+35s: Solver tries to execute
├─ Settlement calls getTradeableOrder()
├─ Handler checks: block.timestamp > quote.validTo?
│ └─ YES! Expired
├─ Throws OrderNotValid("Quote expired")
└─ Settlement fails, order not executed
T+40s: Watch Tower polls again
├─ Calls adapter for NEW quote
├─ Returns fresh GPv2Order with new validTo
└─ Resubmits to solvers
RATE LIMITING:
───────────────
Watch Tower polling: 1 per block (limited by block time)
BondingCurveAdapter.quoteBuy(): Call-per-poll (cheap, view function)
CoW API submission: Subject to API rate limits (5 reqs/sec)
For 1000 active conditional orders:
└─ Polls all 1000 orders per block
└─ ~1000 quote calls per block
└─ Adapter quotes must be fast (view function, no state change)
```
---
## 8. Custom Order Type Decision Tree
```
┌────────────────────────────────────────────────────────────┐
│ Which Custom Order Type Should You Use? │
└────────────────────────────────────────────────────────────┘
Need to execute a bonding curve trade?
├─ YES: Is it a simple "buy now" or "sell now" order?
│ │
│ ├─ YES → Use MycoConditionalOrder
│ │ ├─ Static input: amount, min output
│ │ ├─ Offchain input: live quote
│ │ └─ Executes immediately when quote valid
│ │
│ └─ NO: Do you need additional logic?
│ │
│ ├─ Need to trade at specific price level?
│ │ └─ Use MycoLimitOrder
│ │ └─ Only execute if price >= limit
│ │
│ ├─ Need to split trade over time?
│ │ └─ Use MycoTWAPOrder
│ │ └─ Divide into N parts, execute every T minutes
│ │
│ ├─ Need recurring purchases (e.g., daily)?
│ │ └─ Use MycoDCAOrder
│ │ └─ Buy $X every interval
│ │
│ └─ Need complex conditions?
│ └─ Combine handlers with BaseConditionalOrder
│ ├─ Pre-hook: Check oracle price
│ ├─ Handler: Generate GPv2Order
│ └─ Post-hook: Update tracking
┌─────────────────────┐
│ DECISION TABLE │
└─────────────────────┘
┌──────────────────┬──────────────────┬─────────────────┐
│ Use Case │ Handler │ Key Features │
├──────────────────┼──────────────────┼─────────────────┤
│ Buy now │ MycoConditional │ Live quote, │
│ Sell now │ Order │ slippage prot. │
├──────────────────┼──────────────────┼─────────────────┤
│ Buy at $0.50 │ MycoLimitOrder │ Price trigger, │
│ (no higher) │ │ post-only opt │
├──────────────────┼──────────────────┼─────────────────┤
│ Break buy into │ MycoTWAPOrder │ N parts, │
│ 10x over 1 hr │ │ T minute gaps │
├──────────────────┼──────────────────┼─────────────────┤
│ $100 daily for │ MycoDCAOrder │ Recurring, │
│ 30 days │ │ date-based │
├──────────────────┼──────────────────┼─────────────────┤
│ Buy if oracle │ Custom │ Compose │
│ says bullish │ + Pre-hook │ multiple │
└──────────────────┴──────────────────┴─────────────────┘
IMPLEMENTATION EFFORT:
──────────────────────
MycoConditionalOrder:
├─ Effort: 1-2 days
├─ Complexity: Medium
└─ Dependencies: BondingCurveAdapter, GPv2Order interfaces
MycoLimitOrder:
├─ Effort: 2-3 days
├─ Complexity: Medium-High
└─ Dependencies: Oracle/price feed, limit order tracking
MycoTWAPOrder:
├─ Effort: 3-4 days
├─ Complexity: High
└─ Dependencies: Timing logic, part completion tracking
MycoDCAOrder:
├─ Effort: 2-3 days
├─ Complexity: Medium
└─ Dependencies: Cron-like execution (Watch Tower polling)
Custom + Hooks:
├─ Effort: 4+ days
├─ Complexity: Very High
└─ Dependencies: Oracle, pre/post logic, state management
```
---
**Document Version:** 1.0
**Last Updated:** 2026-04-03
**Status:** Visual Reference Complete