-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMEVProtectionTemplate.sol
More file actions
603 lines (498 loc) · 20.9 KB
/
MEVProtectionTemplate.sol
File metadata and controls
603 lines (498 loc) · 20.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
/**
* @title MEV Protection and Slippage Control Template
* @notice Demonstrates protection against MEV attacks and front-running
* @dev Implements comprehensive MEV resistance patterns:
* 1. Slippage protection for all trades
* 2. Deadline enforcement to prevent stale transactions
* 3. Commit-reveal scheme for sensitive operations
* 4. Private transaction support integration points
* 5. Sandwich attack prevention
* 6. Just-in-time (JIT) liquidity attack protection
*
* @custom:security-features
* - Multi-layer slippage protection
* - Transaction deadline enforcement
* - Front-running resistant commit-reveal
* - MEV-aware transaction ordering
*/
// ============================================================================
// Custom Errors
// ============================================================================
error SlippageExceeded(uint256 expected, uint256 actual);
error DeadlineExpired(uint256 deadline, uint256 currentTime);
error InvalidCommitment();
error CommitmentNotFound();
error AlreadyCommitted();
error RevealTooEarly(uint256 currentBlock, uint256 minRevealBlock);
error RevealTooLate(uint256 currentBlock, uint256 maxRevealBlock);
error ZeroAmount();
error InvalidSlippage();
error SandwichAttackDetected();
// ============================================================================
// Slippage Protection
// ============================================================================
/**
* @title SlippageProtection
* @notice Comprehensive slippage protection for DEX trades and swaps
* @dev Implements multiple slippage protection mechanisms
*/
abstract contract SlippageProtection {
// ========================================================================
// Constants
// ========================================================================
// Maximum allowed slippage in basis points (1% = 100 bps)
uint256 public constant MAX_SLIPPAGE_BPS = 500; // 5%
uint256 private constant BPS_DENOMINATOR = 10000;
// Default slippage if user doesn't specify
uint256 public constant DEFAULT_SLIPPAGE_BPS = 50; // 0.5%
// ========================================================================
// Events
// ========================================================================
event SlippageProtectionApplied(
address indexed user,
uint256 expectedAmount,
uint256 actualAmount,
uint256 slippageBps
);
// ========================================================================
// Slippage Validation
// ========================================================================
/**
* @notice Validate slippage tolerance
* @dev Ensures slippage parameter is within acceptable bounds
* @param slippageBps Slippage tolerance in basis points
*/
function _validateSlippageTolerance(uint256 slippageBps) internal pure {
if (slippageBps > MAX_SLIPPAGE_BPS) revert InvalidSlippage();
}
/**
* @notice Calculate minimum acceptable output with slippage
* @param expectedAmount Expected output amount
* @param slippageBps Slippage tolerance in basis points
* @return minAmount Minimum acceptable amount
*/
function _calculateMinOutput(uint256 expectedAmount, uint256 slippageBps)
internal
pure
returns (uint256 minAmount)
{
_validateSlippageTolerance(slippageBps);
// Calculate minimum: expected * (10000 - slippage) / 10000
minAmount = (expectedAmount * (BPS_DENOMINATOR - slippageBps)) / BPS_DENOMINATOR;
return minAmount;
}
/**
* @notice Calculate maximum acceptable input with slippage
* @param expectedAmount Expected input amount
* @param slippageBps Slippage tolerance in basis points
* @return maxAmount Maximum acceptable amount
*/
function _calculateMaxInput(uint256 expectedAmount, uint256 slippageBps)
internal
pure
returns (uint256 maxAmount)
{
_validateSlippageTolerance(slippageBps);
// Calculate maximum: expected * (10000 + slippage) / 10000
maxAmount = (expectedAmount * (BPS_DENOMINATOR + slippageBps)) / BPS_DENOMINATOR;
return maxAmount;
}
/**
* @notice Enforce slippage protection on output amount
* @param expectedAmount Expected output amount
* @param actualAmount Actual output amount
* @param slippageBps Slippage tolerance in basis points
*/
function _enforceSlippageProtection(
uint256 expectedAmount,
uint256 actualAmount,
uint256 slippageBps
) internal {
uint256 minAmount = _calculateMinOutput(expectedAmount, slippageBps);
if (actualAmount < minAmount) {
revert SlippageExceeded(minAmount, actualAmount);
}
emit SlippageProtectionApplied(msg.sender, expectedAmount, actualAmount, slippageBps);
}
}
// ============================================================================
// Deadline Protection
// ============================================================================
/**
* @title DeadlineProtection
* @notice Prevents execution of stale transactions
* @dev Critical for preventing front-running via delayed execution
*/
abstract contract DeadlineProtection {
// ========================================================================
// Modifiers
// ========================================================================
/**
* @notice Ensure transaction executes before deadline
* @dev Prevents validators from holding transactions for front-running
* @param deadline Unix timestamp deadline
*/
modifier beforeDeadline(uint256 deadline) {
if (block.timestamp > deadline) {
revert DeadlineExpired(deadline, block.timestamp);
}
_;
}
// ========================================================================
// Deadline Helpers
// ========================================================================
/**
* @notice Calculate reasonable deadline from current time
* @param minutesFromNow Minutes to add to current timestamp
* @return deadline Unix timestamp deadline
*/
function _getDeadline(uint256 minutesFromNow) internal view returns (uint256 deadline) {
return block.timestamp + (minutesFromNow * 1 minutes);
}
/**
* @notice Validate deadline is reasonable (not too far in future)
* @param deadline Deadline to validate
* @param maxMinutes Maximum allowed minutes from now
*/
function _validateDeadline(uint256 deadline, uint256 maxMinutes) internal view {
uint256 maxDeadline = block.timestamp + (maxMinutes * 1 minutes);
require(deadline <= maxDeadline, "Deadline too far in future");
}
}
// ============================================================================
// Commit-Reveal Pattern
// ============================================================================
/**
* @title CommitReveal
* @notice Front-running resistant commit-reveal mechanism
* @dev Two-phase execution: commit intention, then reveal after delay
* Prevents front-running of sensitive operations like:
* - Bids and auctions
* - Voting
* - Price-sensitive trades
*/
abstract contract CommitReveal {
// ========================================================================
// Structures
// ========================================================================
struct Commitment {
bytes32 commitHash; // Hash of committed data
uint256 commitBlock; // Block number of commitment
uint256 revealDeadline; // Deadline for reveal
bool revealed; // Whether commitment has been revealed
}
// ========================================================================
// State Variables
// ========================================================================
// User commitments
mapping(address => Commitment) public commitments;
// Minimum blocks between commit and reveal (prevents same-block reveal)
uint256 public constant MIN_COMMIT_REVEAL_BLOCKS = 2;
// Maximum blocks for reveal (prevents indefinite pending)
uint256 public constant MAX_REVEAL_DELAY_BLOCKS = 255; // ~1 hour at 12s blocks
// ========================================================================
// Events
// ========================================================================
event Committed(address indexed user, bytes32 commitHash, uint256 revealDeadline);
event Revealed(address indexed user, bytes32 revealData);
// ========================================================================
// Commit Phase
// ========================================================================
/**
* @notice Commit to future action without revealing details
* @dev User submits hash of (action_data + salt) to prevent front-running
* @param commitHash Hash of abi.encodePacked(msg.sender, actionData, salt)
*/
function _commit(bytes32 commitHash) internal {
if (commitments[msg.sender].commitHash != bytes32(0)) {
revert AlreadyCommitted();
}
uint256 revealDeadline = block.number + MAX_REVEAL_DELAY_BLOCKS;
commitments[msg.sender] = Commitment({
commitHash: commitHash,
commitBlock: block.number,
revealDeadline: revealDeadline,
revealed: false
});
emit Committed(msg.sender, commitHash, revealDeadline);
}
// ========================================================================
// Reveal Phase
// ========================================================================
/**
* @notice Reveal committed action and execute
* @dev Validates commitment, checks timing, then executes action
* @param actionData Original action data
* @param salt Random salt used in commitment
*/
function _reveal(bytes memory actionData, bytes32 salt) internal {
Commitment storage commitment = commitments[msg.sender];
// Validate commitment exists
if (commitment.commitHash == bytes32(0)) {
revert CommitmentNotFound();
}
// Validate not already revealed
if (commitment.revealed) {
revert AlreadyCommitted();
}
// Validate timing - must wait minimum blocks
if (block.number < commitment.commitBlock + MIN_COMMIT_REVEAL_BLOCKS) {
revert RevealTooEarly(block.number, commitment.commitBlock + MIN_COMMIT_REVEAL_BLOCKS);
}
// Validate timing - must reveal before deadline
if (block.number > commitment.revealDeadline) {
revert RevealTooLate(block.number, commitment.revealDeadline);
}
// Validate commitment matches reveal
bytes32 revealHash = keccak256(abi.encodePacked(msg.sender, actionData, salt));
if (revealHash != commitment.commitHash) {
revert InvalidCommitment();
}
// Mark as revealed
commitment.revealed = true;
emit Revealed(msg.sender, revealHash);
}
/**
* @notice Clear commitment (user can cancel)
*/
function _clearCommitment() internal {
delete commitments[msg.sender];
}
/**
* @notice Generate commitment hash
* @param user User address
* @param actionData Action data to commit
* @param salt Random salt for uniqueness
* @return Hash for commitment
*/
function _generateCommitHash(
address user,
bytes memory actionData,
bytes32 salt
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(user, actionData, salt));
}
}
// ============================================================================
// Sandwich Attack Prevention
// ============================================================================
/**
* @title SandwichAttackPrevention
* @notice Detects and prevents sandwich attacks on trades
* @dev Monitors price impact and detects suspicious patterns
*/
abstract contract SandwichAttackPrevention {
// ========================================================================
// State Variables
// ========================================================================
// Track price impact per block to detect sandwiching
mapping(uint256 => uint256) private _blockPriceImpact;
mapping(uint256 => uint256) private _blockTradeCount;
// Maximum allowed price impact per trade
uint256 public constant MAX_PRICE_IMPACT_BPS = 100; // 1%
// Maximum cumulative price impact per block
uint256 public constant MAX_BLOCK_PRICE_IMPACT_BPS = 300; // 3%
uint256 private constant BPS_DENOMINATOR = 10000;
// ========================================================================
// Sandwich Detection
// ========================================================================
/**
* @notice Validate trade doesn't contribute to sandwich attack
* @param priceImpactBps Price impact of this trade in basis points
*/
function _validateNotSandwiched(uint256 priceImpactBps) internal {
// Check individual trade impact
if (priceImpactBps > MAX_PRICE_IMPACT_BPS) {
revert SandwichAttackDetected();
}
// Check cumulative block impact (detects sandwich pattern)
uint256 blockImpact = _blockPriceImpact[block.number];
uint256 newBlockImpact;
unchecked {
// Safe: price impact is bounded by MAX values
newBlockImpact = blockImpact + priceImpactBps;
}
if (newBlockImpact > MAX_BLOCK_PRICE_IMPACT_BPS) {
revert SandwichAttackDetected();
}
// Update block tracking
_blockPriceImpact[block.number] = newBlockImpact;
unchecked {
// Safe: trade count cannot realistically overflow
_blockTradeCount[block.number]++;
}
}
/**
* @notice Calculate price impact in basis points
* @param reserveBefore Reserve balance before trade
* @param reserveAfter Reserve balance after trade
* @return priceImpactBps Price impact in basis points
*/
function _calculatePriceImpact(
uint256 reserveBefore,
uint256 reserveAfter
) internal pure returns (uint256 priceImpactBps) {
if (reserveBefore == 0) return 0;
if (reserveAfter > reserveBefore) {
priceImpactBps = ((reserveAfter - reserveBefore) * BPS_DENOMINATOR) / reserveBefore;
} else {
priceImpactBps = ((reserveBefore - reserveAfter) * BPS_DENOMINATOR) / reserveBefore;
}
return priceImpactBps;
}
}
// ============================================================================
// MEV-Protected DEX Example
// ============================================================================
/**
* @title MEVProtectedDEX
* @notice Example DEX with comprehensive MEV protection
* @dev Combines all MEV protection patterns into production-ready implementation
*/
contract MEVProtectedDEX is
SlippageProtection,
DeadlineProtection,
CommitReveal,
SandwichAttackPrevention
{
// ========================================================================
// State Variables
// ========================================================================
// Simplified liquidity pool
uint256 public reserveToken0;
uint256 public reserveToken1;
// ========================================================================
// Events
// ========================================================================
event Swap(
address indexed user,
uint256 amountIn,
uint256 amountOut,
uint256 slippageBps
);
event LiquidityAdded(address indexed user, uint256 amount0, uint256 amount1);
// ========================================================================
// Protected Swap Functions
// ========================================================================
/**
* @notice Swap with comprehensive MEV protection
* @dev Implements: slippage protection, deadline, sandwich prevention
* @param amountIn Amount of input tokens
* @param minAmountOut Minimum acceptable output (slippage protection)
* @param deadline Transaction deadline
* @return amountOut Actual output amount
*/
function swapWithProtection(
uint256 amountIn,
uint256 minAmountOut,
uint256 deadline
)
external
beforeDeadline(deadline)
returns (uint256 amountOut)
{
if (amountIn == 0) revert ZeroAmount();
// Calculate output with constant product formula (simplified)
amountOut = _calculateSwapOutput(amountIn);
// Enforce slippage protection
if (amountOut < minAmountOut) {
revert SlippageExceeded(minAmountOut, amountOut);
}
// Calculate and validate price impact (sandwich prevention)
uint256 reserveBefore = reserveToken1;
uint256 reserveAfter = reserveToken1 - amountOut;
uint256 priceImpact = _calculatePriceImpact(reserveBefore, reserveAfter);
_validateNotSandwiched(priceImpact);
// Update reserves (Effects)
unchecked {
reserveToken0 += amountIn;
reserveToken1 -= amountOut;
}
emit Swap(msg.sender, amountIn, amountOut,
((minAmountOut * BPS_DENOMINATOR) / amountOut));
// In production: transfer tokens (Interactions)
return amountOut;
}
/**
* @notice Commit to future swap (front-running resistant)
* @param commitHash Hash of swap parameters + salt
*/
function commitSwap(bytes32 commitHash) external {
_commit(commitHash);
}
/**
* @notice Reveal and execute committed swap
* @param amountIn Input amount
* @param minAmountOut Minimum output
* @param deadline Deadline
* @param salt Random salt
*/
function revealSwap(
uint256 amountIn,
uint256 minAmountOut,
uint256 deadline,
bytes32 salt
) external beforeDeadline(deadline) returns (uint256 amountOut) {
// Encode action data
bytes memory actionData = abi.encode(amountIn, minAmountOut, deadline);
// Validate and reveal commitment
_reveal(actionData, salt);
// Execute swap with protection
amountOut = _calculateSwapOutput(amountIn);
if (amountOut < minAmountOut) {
revert SlippageExceeded(minAmountOut, amountOut);
}
// Update reserves
unchecked {
reserveToken0 += amountIn;
reserveToken1 -= amountOut;
}
emit Swap(msg.sender, amountIn, amountOut,
((minAmountOut * BPS_DENOMINATOR) / amountOut));
// Clear commitment
_clearCommitment();
return amountOut;
}
// ========================================================================
// Helper Functions
// ========================================================================
/**
* @notice Calculate swap output (simplified constant product)
* @param amountIn Input amount
* @return amountOut Output amount
*
* ⚠️ WARNING: Simplified implementation for template purposes
* ⚠️ Production MUST include: fees, overflow protection, slippage
* ⚠️ Consider using SafeMath for large values
*/
function _calculateSwapOutput(uint256 amountIn)
internal
view
returns (uint256 amountOut)
{
// Simplified: constant product formula x * y = k
// In production: use full UniswapV2 formula with fees
if (reserveToken0 == 0 || reserveToken1 == 0) return 0;
// ⚠️ Add overflow protection for production:
// require(amountIn <= type(uint112).max, "Overflow protection");
uint256 numerator = amountIn * reserveToken1;
uint256 denominator = reserveToken0 + amountIn;
require(denominator > 0, "Division by zero");
amountOut = numerator / denominator;
return amountOut;
}
/**
* @notice Add liquidity (for testing)
* @param amount0 Amount of token0
* @param amount1 Amount of token1
*/
function addLiquidity(uint256 amount0, uint256 amount1) external {
unchecked {
reserveToken0 += amount0;
reserveToken1 += amount1;
}
emit LiquidityAdded(msg.sender, amount0, amount1);
}
}