- M-2: update body to show current deployer-only setFeeDestination() implementation and conditional locking; mark as partially resolved; downgrade severity from Medium to Low; update conclusion entry - I-1: mark as resolved — Recentered event declared at line 66 and emitted at line 224 of LiquidityManager.sol - I-2: correct VWAP direction (records on sells/ETH outflow, not buys); update stale line reference from 146-158 to 177-191 - deployment.md §6.5: replace vague 'assess severity' step 1 with concrete action (upgrade optimizer to bear defaults via §6.2) - deployment.md §8 timeline: remove stale 'Set recenter access' row; update 'First recenter' dependency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
288 lines
9.6 KiB
Markdown
288 lines
9.6 KiB
Markdown
# KRAIKEN Mainnet Deployment Runbook
|
|
|
|
**Target chain:** Base (L2)
|
|
**Contract version:** V2 (OptimizerV3 w/ directional VWAP)
|
|
|
|
---
|
|
|
|
## 1. Pre-Deployment Checklist
|
|
|
|
- [ ] All tests pass: `cd onchain && forge test`
|
|
- [ ] Gas snapshot baseline: `forge snapshot`
|
|
- [ ] Security review complete (see `analysis/SECURITY_REVIEW.md`)
|
|
- [ ] Storage layout verified for UUPS upgrade (see `analysis/STORAGE_LAYOUT.md`)
|
|
- [ ] Floor ratchet mitigation status confirmed (branch `fix/floor-ratchet`)
|
|
- [ ] Multisig wallet ready for `feeDestination` (Gnosis Safe on Base)
|
|
- [ ] Deployer wallet funded with sufficient ETH for gas (~0.05 ETH)
|
|
- [ ] LiquidityManager funding wallet ready (initial ETH seed for pool positions)
|
|
- [ ] `.secret` seed phrase file present in `onchain/` (deployer account)
|
|
- [ ] Base RPC endpoint configured and tested
|
|
- [ ] Etherscan/Basescan API key ready for contract verification
|
|
- [ ] kraiken-lib version updated: `COMPATIBLE_CONTRACT_VERSIONS` includes `2`
|
|
|
|
---
|
|
|
|
## 2. Contract Deployment Order
|
|
|
|
All contracts are deployed in a single broadcast transaction via `DeployBaseMainnet.sol`:
|
|
|
|
```
|
|
1. Kraiken token (ERC20 + ERC20Permit)
|
|
2. Stake contract (Kraiken address, feeDestination)
|
|
3. Kraiken.setStakingPool(Stake)
|
|
4. Uniswap V3 Pool (create or use existing, FEE=10000)
|
|
5. Pool initialization (1 cent starting price)
|
|
6. OptimizerV3 implementation + ERC1967Proxy
|
|
7. LiquidityManager (factory, WETH, Kraiken, OptimizerProxy)
|
|
8. LiquidityManager.setFeeDestination(multisig)
|
|
9. Kraiken.setLiquidityManager(LiquidityManager)
|
|
```
|
|
|
|
### Deploy Command
|
|
|
|
```bash
|
|
cd onchain
|
|
|
|
# Verify configuration first
|
|
cat script/DeployBaseMainnet.sol # Check feeDest, weth, v3Factory
|
|
|
|
# Dry run (no broadcast)
|
|
forge script script/DeployBaseMainnet.sol \
|
|
--rpc-url $BASE_RPC \
|
|
--sender $(cast wallet address --mnemonic "$(cat .secret)")
|
|
|
|
# Live deployment
|
|
forge script script/DeployBaseMainnet.sol \
|
|
--rpc-url $BASE_RPC \
|
|
--broadcast \
|
|
--verify \
|
|
--etherscan-api-key $BASESCAN_API_KEY \
|
|
--slow
|
|
```
|
|
|
|
**Critical:** The `--slow` flag submits transactions one at a time, waiting for confirmation. This prevents nonce issues on Base.
|
|
|
|
### Record Deployment Addresses
|
|
|
|
After deployment, save all addresses from console output:
|
|
```bash
|
|
# Update deployments file
|
|
cat >> deployments-mainnet.json << 'EOF'
|
|
{
|
|
"chain": "base",
|
|
"chainId": 8453,
|
|
"kraiken": "0x...",
|
|
"stake": "0x...",
|
|
"pool": "0x...",
|
|
"liquidityManager": "0x...",
|
|
"optimizerProxy": "0x...",
|
|
"optimizerImpl": "0x...",
|
|
"feeDestination": "0x...",
|
|
"deployer": "0x...",
|
|
"deployedAt": "2026-XX-XX",
|
|
"txHash": "0x..."
|
|
}
|
|
EOF
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Post-Deployment Setup
|
|
|
|
### 3.1 Fund LiquidityManager
|
|
|
|
The LM needs ETH to create initial positions:
|
|
|
|
```bash
|
|
# Send ETH to LiquidityManager (unwrapped — it will wrap to WETH internally)
|
|
cast send $LIQUIDITY_MANAGER --value 10ether \
|
|
--rpc-url $BASE_RPC \
|
|
--mnemonic "$(cat .secret)"
|
|
```
|
|
|
|
### 3.2 Trigger First Recenter
|
|
|
|
`recenter()` is permissionless — any address may call it. The 60-second cooldown (`MIN_RECENTER_INTERVAL`) and TWAP oracle check are always enforced.
|
|
|
|
```bash
|
|
# Wait for pool to accumulate some TWAP history (~5 minutes of trades)
|
|
# Anyone can trigger the first recenter; txnBot will take over ongoing calls
|
|
cast send $LIQUIDITY_MANAGER "recenter()" \
|
|
--rpc-url $BASE_RPC \
|
|
--from $TXNBOT_ADDRESS
|
|
```
|
|
|
|
### 3.4 Configure txnBot
|
|
|
|
Update `services/txnBot/` configuration for Base mainnet:
|
|
- Set `LIQUIDITY_MANAGER` address
|
|
- Set `KRAIKEN` address
|
|
- Set RPC to Base mainnet
|
|
- Deploy txnBot service
|
|
|
|
### 3.5 Configure Ponder Indexer
|
|
|
|
```bash
|
|
# Update kraiken-lib/src/version.ts
|
|
export const COMPATIBLE_CONTRACT_VERSIONS = [2];
|
|
|
|
# Update Ponder config for Base mainnet addresses
|
|
# Set PONDER_NETWORK=BASE in environment
|
|
```
|
|
|
|
### 3.6 Update Frontend
|
|
|
|
- Update contract addresses in web-app configuration
|
|
- Update kraiken-lib ABIs: `cd onchain && forge build` then rebuild kraiken-lib
|
|
- Deploy frontend to production
|
|
|
|
---
|
|
|
|
## 4. Optimizer Upgrade Procedure
|
|
|
|
If upgrading an existing Optimizer proxy to OptimizerV3:
|
|
|
|
```bash
|
|
cd onchain
|
|
|
|
# Set proxy address
|
|
export OPTIMIZER_PROXY=0x...
|
|
|
|
# Dry run
|
|
forge script script/UpgradeOptimizer.sol \
|
|
--rpc-url $BASE_RPC
|
|
|
|
# Execute upgrade
|
|
forge script script/UpgradeOptimizer.sol \
|
|
--rpc-url $BASE_RPC \
|
|
--broadcast \
|
|
--verify \
|
|
--etherscan-api-key $BASESCAN_API_KEY
|
|
|
|
# Verify post-upgrade
|
|
cast call $OPTIMIZER_PROXY "getLiquidityParams()" --rpc-url $BASE_RPC
|
|
```
|
|
|
|
**Expected output:** Bear-mode defaults (CI=0, AS=0.3e18, AW=100, DD=0.3e18) since staking will be <91%.
|
|
|
|
---
|
|
|
|
## 5. Verification Steps
|
|
|
|
Run these checks after deployment to confirm everything is wired correctly:
|
|
|
|
```bash
|
|
# 1. Kraiken token
|
|
cast call $KRAIKEN "VERSION()" --rpc-url $BASE_RPC # Should return 2
|
|
cast call $KRAIKEN "peripheryContracts()" --rpc-url $BASE_RPC # LM + Stake addresses
|
|
|
|
# 2. LiquidityManager
|
|
cast call $LM "feeDestination()" --rpc-url $BASE_RPC # Should be multisig
|
|
cast call $LM "lastRecenterTime()" --rpc-url $BASE_RPC # Should be non-zero after first recenter
|
|
cast call $LM "positions(0)" --rpc-url $BASE_RPC # Floor position (after recenter)
|
|
cast call $LM "positions(1)" --rpc-url $BASE_RPC # Anchor position
|
|
cast call $LM "positions(2)" --rpc-url $BASE_RPC # Discovery position
|
|
|
|
# 3. OptimizerV3 (through proxy)
|
|
cast call $OPTIMIZER "getLiquidityParams()" --rpc-url $BASE_RPC
|
|
|
|
# 4. Pool state
|
|
cast call $POOL "slot0()" --rpc-url $BASE_RPC # Current tick, price
|
|
cast call $POOL "liquidity()" --rpc-url $BASE_RPC # Total liquidity
|
|
|
|
# 5. Stake contract
|
|
cast call $STAKE "nextPositionId()" --rpc-url $BASE_RPC # Should be 0 initially
|
|
|
|
# 6. ETH balance
|
|
cast balance $LM --rpc-url $BASE_RPC # Should show funded amount
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Emergency Procedures
|
|
|
|
### 6.1 Pause Recentering
|
|
|
|
**NOTE:** `recenter()` is permissionless — there is no access-control switch to block it. The only mechanism that prevents a recenter is the 60-second `MIN_RECENTER_INTERVAL` cooldown and the TWAP oracle check. There is no admin function to revoke or grant access.
|
|
|
|
In an attack scenario the most effective response is to upgrade or replace the contract (see §6.3 / §6.4). Existing positions remain in place and continue earning fees regardless of recenter activity.
|
|
|
|
### 6.2 Upgrade Optimizer to Safe Defaults
|
|
|
|
Deploy a minimal "safe" optimizer that always returns bear parameters:
|
|
|
|
```bash
|
|
# Deploy SafeOptimizer with hardcoded bear params
|
|
# Upgrade proxy to SafeOptimizer
|
|
OPTIMIZER_PROXY=$OPTIMIZER forge script script/UpgradeOptimizer.sol \
|
|
--rpc-url $BASE_RPC --broadcast
|
|
```
|
|
|
|
### 6.3 Emergency Parameter Override
|
|
|
|
If the optimizer needs temporary override, deploy a new implementation with hardcoded safe parameters:
|
|
- CI=0, AS=30% (0.3e18), AW=100, DD=0.3e18 (bear defaults)
|
|
- These were verified safe across all 1050 parameter sweep combinations
|
|
|
|
### 6.4 Rollback Plan
|
|
|
|
**There is no rollback for deployed contracts.** Mitigation options:
|
|
- Upgrade optimizer proxy to revert to V1/V2 logic
|
|
- Revoke recenter access to freeze positions
|
|
- The LiquidityManager itself is NOT upgradeable (by design — immutable control)
|
|
- In worst case: deploy entirely new contract set, migrate liquidity
|
|
|
|
### 6.5 Known Attack Response: Floor Ratchet
|
|
|
|
If floor ratchet extraction is detected (rapid recenters + floor tick creeping toward current price):
|
|
1. **Immediately** upgrade the optimizer to safe bear-mode defaults (§6.2) — this maximises floor distance (AW=100 → 7000-tick clearance) and makes ratchet extraction significantly harder while a patched LiquidityManager is prepared. Note: there is no access-control switch on `recenter()`; the 60s cooldown is the only rate limiter
|
|
2. Assess floor position state via `positions(0)`
|
|
3. Deploy patched LiquidityManager if fix is ready
|
|
4. Current mitigation: bear-mode parameters (AW=100) create 7000-tick floor distance, making ratchet extraction significantly harder
|
|
|
|
---
|
|
|
|
## 7. Monitoring Setup
|
|
|
|
### On-Chain Monitoring
|
|
|
|
Track these metrics via Ponder or direct RPC polling:
|
|
|
|
| Metric | How | Alert Threshold |
|
|
|--------|-----|-----------------|
|
|
| Floor tick distance | `positions(0).tickLower - currentTick` | < 2000 ticks |
|
|
| Recenter frequency | Count `recenter()` calls per hour | > 10/hour |
|
|
| LM ETH balance | `address(LM).balance + WETH.balanceOf(LM)` | < 1 ETH (most ETH is in pool positions) |
|
|
| VWAP drift | `getVWAP()` vs current price | > 50% divergence |
|
|
| Optimizer mode | `getLiquidityParams()` return values | Unexpected bull in low-staking |
|
|
| Fee revenue | WETH transfers to feeDestination | Sudden drop to 0 |
|
|
|
|
### Off-Chain Monitoring
|
|
|
|
- txnBot health: `GET /api/txn/status` — should return healthy
|
|
- Ponder indexing: `GET /api/graphql` — query `stats` entity
|
|
- Frontend version check: `useVersionCheck()` composable validates contract VERSION
|
|
|
|
### Alerting Triggers
|
|
|
|
1. **Critical:** Floor position liquidity = 0 (no floor protection)
|
|
2. **Critical:** recenter() reverts for > 1 hour
|
|
3. **High:** > 20 recenters in 1 hour (potential manipulation)
|
|
4. **Medium:** VWAP compression triggered (high cumulative volume)
|
|
5. **Low:** Optimizer returns bull mode (verify staking metrics justify it)
|
|
|
|
---
|
|
|
|
## 8. Deployment Timeline
|
|
|
|
| Step | Duration | Dependency |
|
|
|------|----------|------------|
|
|
| Deploy contracts | ~2 min | Funded deployer wallet |
|
|
| Verify on Basescan | ~5 min | Deployment complete |
|
|
| Fund LiquidityManager | ~1 min | Deployment complete |
|
|
| Wait for TWAP history | ~5-10 min | Pool initialized |
|
|
| First recenter | ~1 min | TWAP history accumulated |
|
|
| Deploy txnBot | ~5 min | Addresses configured |
|
|
| Deploy Ponder | ~10 min | Addresses + kraiken-lib updated |
|
|
| Deploy frontend | ~5 min | Ponder running |
|
|
| **Total** | **~30-40 min** | |
|