smart-contracts/backlog/docs/CoW_Quick_Reference.md

304 lines
8.4 KiB
Markdown

# CoW Protocol + MycoFi: Quick Reference Card
## One-Page Overview
### What is CoW?
**Intent-based DEX with batch auctions.** Users sign intents (not transactions). Solvers compete to execute batches. MEV-protected. Solvers pay gas.
### Your Integration
```
User Intent (private)
↓ Watch Tower polls
↓ MycoConditionalOrder.getTradeableOrder()
↓ BondingCurveAdapter.quoteBuy/quoteSell
↓ Solver competition
↓ GPv2Settlement.settle() executes
↓ User receives tokens (MEV-safe)
```
---
## Contract Reference
| Contract | Address (Base) | Role |
|----------|---|---|
| GPv2Settlement | `0x9008D19f...` | Settlement executor |
| GPv2VaultRelayer | `0xc92e8bdf...` | Token pull intermediary |
| MycoBondingCurve | `0x...` (deploy) | Pricing logic |
| BondingCurveAdapter | `0x...` (deploy) | Pre-interaction hook |
| MycoConditionalOrder | `0x...` (deploy) | Order type handler |
---
## Key Interfaces
### IConditionalOrder (implement this)
```solidity
function getTradeableOrder(
address owner, // Safe wallet
address sender, // Usually Watch Tower
bytes calldata staticInput, // Encoded params
bytes calldata offchainInput // Encoded quote + expiry
) external view returns (GPv2Order.Data);
function verify(...) external view; // Validation
```
### GPv2Order.Data (return this)
```solidity
struct Data {
address sellToken; // USDC for buy
address buyToken; // MYCO for buy
address receiver; // Order recipient
uint256 sellAmount; // Exact input
uint256 buyAmount; // Minimum output
uint32 validTo; // Expiry timestamp
bytes32 appData; // Metadata
uint256 feeAmount; // Usually 0
bytes32 kind; // KIND_SELL or KIND_BUY
bool partiallyFillable; // Usually false
bytes32 sellTokenBalance; // BALANCE_ERC20
bytes32 buyTokenBalance; // BALANCE_ERC20
}
```
---
## Error Handling
```solidity
// In getTradeableOrder():
// Temporary (retry next block)
if (quote_expired) revert PollTryNextBlock("Quote expired");
if (insufficient_balance) revert PollTryNextBlock("Balance changed");
// Current state invalid (try later)
if (quote_below_minimum) revert OrderNotValid("Quote below min");
// Stop polling (permanent issue)
if (handler_misconfigured) revert PollNever("Config error");
```
---
## Stateless Handler Pattern
```solidity
contract MycoConditionalOrder is IConditionalOrder {
// NO state variables (except immutable refs)
// All logic is view-only in getTradeableOrder()
function getTradeableOrder(
address owner,
address,
bytes calldata staticInput, // Order params
bytes calldata offchainInput // Live quote from Watch Tower
) external view returns (GPv2Order.Data memory) {
// 1. Decode staticInput (set once at order creation)
OrderData memory params = abi.decode(staticInput, (OrderData));
// 2. Decode offchainInput (fresh quote from Watch Tower)
OffchainData memory quote = abi.decode(offchainInput, (OffchainData));
// 3. Validate
if (block.timestamp > quote.validTo) revert OrderNotValid("Expired");
if (quote.quotedOutput < params.minOutputAmount) revert OrderNotValid("Below min");
// 4. Generate order
return GPv2Order.Data({
sellToken: params.direction == BUY ? USDC : MYCO,
buyToken: params.direction == BUY ? MYCO : USDC,
sellAmount: params.inputAmount,
buyAmount: quote.quotedOutput, // From Watch Tower
receiver: params.receiver,
validTo: quote.validTo,
// ... other fields
});
}
}
```
---
## Watch Tower Polling Lifecycle
```
Every block (every 2 sec on Base):
FOR each handler in registry:
FOR each order:
staticInput = stored params
offchainInput = adapter.quoteBuy() + timestamp
TRY getTradeableOrder(owner, sender, static, offchain)
✓ Valid? → Submit to solvers
✗ PollTryNextBlock? → Retry next block
✗ OrderNotValid? → Skip, retry later
✗ PollNever? → Remove from polling
offchainInput = BondingCurveAdapter.quoteBuy()
Submit to solver network
```
---
## Token Flow During Settlement
```
Settlement.settle() called by solver:
1. BEFORE INTERACTIONS
└─ Nothing (for basic buy/sell)
2. INPUT TRANSFERS (via VaultRelayer)
└─ VaultRelayer pulls 500 USDC from user's Vault
└─ Transfers to BondingCurveAdapter
3. INTERACTIONS
└─ BondingCurveAdapter.executeBuyForCoW(500 USDC, 900 MYCO min)
├─ Calls curve.buy(500 USDC)
├─ Curve mints 1000 MYCO to adapter
└─ Returns 1000 to settlement
4. OUTPUT TRANSFERS (via VaultRelayer)
└─ VaultRelayer pulls 1000 MYCO from adapter
└─ Transfers to user
5. AFTER INTERACTIONS
└─ Nothing (for basic buy/sell)
Result: User -500 USDC, +1000 MYCO
```
---
## Critical Security Checklist
- [ ] Users approve **Balancer Vault** (NOT Settlement)
- [ ] Adapter has `onlySettlement` modifier
- [ ] Quote expiry validated (block.timestamp <= quote.validTo)
- [ ] Min output enforced (quote.output >= params.minOutputAmount)
- [ ] Reentrancy guarded on hook functions
- [ ] No permanent state in handler (stateless)
- [ ] VaultRelayer can only transfer TO Settlement
---
## MEV Protection Summary
| Mechanism | How it Works |
|-----------|-------------|
| **Private Order Flow** | Intents never in public mempool → Bots can't see you |
| **Uniform Clearing Prices** | All USDC→MYCO trades in batch at same price → Order can't be reordered for profit |
| **Coincidence of Wants** | Alice ↔ Bob direct match → No AMM slippage → No MEV possible |
---
## Testing Quick Start
```solidity
// Test handler returns valid GPv2Order
GPv2Order.Data memory order = handler.getTradeableOrder(
owner, watchtower, staticInput, offchainInput
);
assert(order.buyAmount >= minOutput);
assert(order.validTo > block.timestamp);
// Test adapter executes correctly
usdc.transfer(adapter, 500e6);
uint256 mycoOut = adapter.executeBuyForCoW(500e6, 900e18);
assert(mycoToken.balanceOf(adapter) >= 900e18);
// Test quote functions
uint256 quote = adapter.quoteBuy(500e6);
assert(quote == curve.calculateBuyReturn(500e6));
```
---
## Deployment Checklist
Base Mainnet Addresses:
```
✓ USDC: 0x833589fC4D06F649c466dB920d0135aa6Df1cDEA
✓ GPv2Settlement: 0x9008D19f58AAbD9eD0D60971565AA8510560ab41
✓ VaultRelayer: 0xc92e8bdf79f0507f65a392b0ab4667716bfe0110
Deploy (in order):
1. MycoToken
2. MycoBondingCurve (set basePrice, coeff, exponent, fee, treasury)
3. BondingCurveAdapter
4. MycoConditionalOrder
5. Register in ComposableCoW (request Watch Tower access)
```
---
## API Rate Limits
```
Quote requests: 10/second
Order submission: 5/second
General endpoints: 100/minute
```
---
## Common Errors & Solutions
| Error | Cause | Fix |
|-------|-------|-----|
| `OrderNotValid("Quote expired")` | Watch Tower quote >30min old | Fetch fresh quote |
| `PollTryNextBlock("Insufficient balance")` | User moved tokens | Wait for balance recovery |
| `OnlySettlement()` revert | Called adapter directly | Only Settlement can call |
| `SlippageExceeded` | Quote < minOutputAmount | Increase minOut or retry |
---
## Links
- Docs: https://docs.cow.fi/
- GitHub: https://github.com/cowprotocol/
- SDK: `@cowprotocol/sdk` (npm)
- Forum: https://forum.cow.fi/
- API Base: https://api.cow.fi/base/
---
## Key Metrics to Track
```
Per order:
- Quote latency (target <100ms)
- Time to execution (target <60s)
- Actual price vs quote (should be >=)
- Gas paid by solver (should be <fees collected)
Per batch (daily):
- Orders submitted / executed / failed
- Average surplus per order
- MEV extraction (should be ~0)
- Watch Tower polling rate
Monthly:
- Total volume
- User count
- Protocol revenue (accumulated fees)
```
---
## Golden Rules
1. **Handlers are stateless** All logic in `getTradeableOrder()` view function
2. **Quotes are ephemeral** Each poll gets fresh quote from adapter
3. **Errors must be clear** Use correct error type (PollTryNextBlock vs OrderNotValid)
4. **Fund access is protected** Users approve Vault, not Settlement
5. **Solvers are untrusted** But can't break anything (settlement atomic)
---
**Version:** 1.0
**Updated:** 2026-04-03
**Status:** Ready to reference during implementation