DeFi Protocol Anomaly Detection System
Most DeFi exploits last seconds to minutes. Euler Finance ($197M)—5 transactions in ~45 seconds. Curve ($62M)—hours. If monitoring doesn't respond in this window—funds lost. Anomaly detection: catch attack in progress or before, give time to pause.
System Architecture
Three monitoring levels:
Level 1—Real-time mempool (<1 block): Monitor pending transactions for suspicious patterns. Earliest warning but technically complex.
Level 2—Per-block (<15 sec on Ethereum): Analyze new blocks for events, state changes, anomalous volumes. Main monitoring level.
Level 3—Aggregated (minutes-hours): Trend analysis, cross-protocol correlations. Detects slow-developing attacks.
On-Chain Circuit Breaker
Rate Limiting
contract ProtocolWithCircuitBreaker {
uint256 public maxWithdrawPerBlock;
uint256 public maxWithdrawPerHour;
uint256 private _withdrawnThisBlock;
uint256 private _withdrawnThisHour;
uint256 private _lastBlockNumber;
uint256 private _lastHourTimestamp;
modifier withinRateLimit(uint256 amount) {
_updateRateLimitCounters();
require(
_withdrawnThisBlock + amount <= maxWithdrawPerBlock,
"Block rate limit exceeded"
);
require(
_withdrawnThisHour + amount <= maxWithdrawPerHour,
"Hour rate limit exceeded"
);
_withdrawnThisBlock += amount;
_withdrawnThisHour += amount;
_;
}
function withdraw(uint256 amount) external whenNotPaused withinRateLimit(amount) {
// ... logic
}
}
Rate limits don't block normal work—just reject excess. At 2-3x typical volume.
Auto-Pause via Keeper
OpenZeppelin Defender Autotasks—serverless functions responding to on-chain events:
async function(credentials) {
const provider = new ethers.providers.JsonRpcProvider(credentials.secrets.ALCHEMY_URL);
const vault = new ethers.Contract(VAULT_ADDRESS, VAULT_ABI, provider);
const currentTVL = await vault.totalAssets();
const previousTVL = await storage.get('previousTVL') || currentTVL;
const dropPercent = (previousTVL - currentTVL) * 100n / previousTVL;
if (dropPercent > 20n) {
const signer = credentials.relayer.getSigner();
const guardian = new ethers.Contract(GUARDIAN_ADDRESS, GUARDIAN_ABI, signer);
await guardian.emergencyPause();
await notifySlack(`TVL dropped ${dropPercent}%—paused`);
}
await storage.put('previousTVL', currentTVL.toString());
}
Anomaly Detection: Rules and Models
Rule-Based Detection
Fast and transparent. Specific conditions matching known attack patterns.
Invariant Monitoring
Math invariants always hold. For lending: totalDebt <= totalDeposits + accruedInterest.
Invariant violation = something wrong. Either bug or active attack.
ML Approach
Isolation Forest for anomalous transactions—uses 6 features: gas used, value, internal calls count, contracts called, token transfers, time since last tx from address.
from sklearn.ensemble import IsolationForest
model = IsolationForest(contamination=0.01)
model.fit(normal_transactions_features)
def score_transaction(tx):
features = extract_features(tx)
score = model.score_samples([features])[0]
return score
Combine: ML for flagging + rules for verification + human review for pause decision.
Alerting and Response
Severity Levels
| Severity | Trigger | Action | Time |
|---|---|---|---|
| P1 Critical | Active attack, fund loss | Pause + calls | < 2 min |
| P2 High | Invariant violation, oracle manipulation | Pause + review | < 15 min |
| P3 Medium | Anomaly volume, unusual behavior | Review | < 4 hours |
| P4 Low | Statistical deviation | Weekly review | Async |
PagerDuty + Telegram
P1/P2: PagerDuty with on-call rotation. P3: Telegram group. P4: daily Slack digest.
Incident Response Playbook
Document with clear steps for each attack scenario. Test regularly.
Stack and Timeline
Python 3.11 + web3.py or TypeScript + viem. PostgreSQL for history, Redis for counters. PagerDuty + Telegram, OZ Defender Autotasks.
MVP (rule-based + alerting + circuit breaker): 4-6 weeks.
Full system (ML + auto-pause + dashboard): 10-14 weeks.







