web3crit-scanner 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +685 -0
  2. package/bin/web3crit +10 -0
  3. package/package.json +59 -0
  4. package/src/analyzers/control-flow.js +256 -0
  5. package/src/analyzers/data-flow.js +720 -0
  6. package/src/analyzers/exploit-chain.js +751 -0
  7. package/src/analyzers/immunefi-classifier.js +515 -0
  8. package/src/analyzers/poc-validator.js +396 -0
  9. package/src/analyzers/solodit-enricher.js +1122 -0
  10. package/src/cli.js +546 -0
  11. package/src/detectors/access-control-enhanced.js +458 -0
  12. package/src/detectors/base-detector.js +213 -0
  13. package/src/detectors/callback-reentrancy.js +362 -0
  14. package/src/detectors/cross-contract-reentrancy.js +697 -0
  15. package/src/detectors/delegatecall.js +167 -0
  16. package/src/detectors/deprecated-functions.js +62 -0
  17. package/src/detectors/flash-loan.js +408 -0
  18. package/src/detectors/frontrunning.js +553 -0
  19. package/src/detectors/gas-griefing.js +701 -0
  20. package/src/detectors/governance-attacks.js +366 -0
  21. package/src/detectors/integer-overflow.js +487 -0
  22. package/src/detectors/oracle-manipulation.js +524 -0
  23. package/src/detectors/permit-exploits.js +368 -0
  24. package/src/detectors/precision-loss.js +408 -0
  25. package/src/detectors/price-manipulation-advanced.js +548 -0
  26. package/src/detectors/proxy-vulnerabilities.js +651 -0
  27. package/src/detectors/readonly-reentrancy.js +473 -0
  28. package/src/detectors/rebasing-token-vault.js +416 -0
  29. package/src/detectors/reentrancy-enhanced.js +359 -0
  30. package/src/detectors/selfdestruct.js +259 -0
  31. package/src/detectors/share-manipulation.js +412 -0
  32. package/src/detectors/signature-replay.js +409 -0
  33. package/src/detectors/storage-collision.js +446 -0
  34. package/src/detectors/timestamp-dependence.js +494 -0
  35. package/src/detectors/toctou.js +427 -0
  36. package/src/detectors/token-standard-compliance.js +465 -0
  37. package/src/detectors/unchecked-call.js +214 -0
  38. package/src/detectors/vault-inflation.js +421 -0
  39. package/src/index.js +42 -0
  40. package/src/package-lock.json +2874 -0
  41. package/src/package.json +39 -0
  42. package/src/scanner-enhanced.js +816 -0
@@ -0,0 +1,553 @@
1
+ const BaseDetector = require('./base-detector');
2
+
3
+ /**
4
+ * Front-Running Vulnerability Detector (Enhanced)
5
+ * Detects patterns susceptible to MEV and front-running attacks
6
+ * with improved context awareness to reduce false positives.
7
+ */
8
+ class FrontRunningDetector extends BaseDetector {
9
+ constructor() {
10
+ super(
11
+ 'Front-Running Vulnerability',
12
+ 'Detects patterns vulnerable to MEV and front-running attacks',
13
+ 'HIGH'
14
+ );
15
+ this.currentContract = null;
16
+ this.currentFunction = null;
17
+ this.currentFunctionNode = null;
18
+ this.contractCode = '';
19
+ }
20
+
21
+ async detect(ast, sourceCode, fileName, cfg, dataFlow) {
22
+ this.findings = [];
23
+ this.ast = ast;
24
+ this.sourceCode = sourceCode;
25
+ this.fileName = fileName;
26
+ this.sourceLines = sourceCode.split('\n');
27
+ this.cfg = cfg;
28
+ this.dataFlow = dataFlow;
29
+
30
+ this.traverse(ast);
31
+
32
+ return this.findings;
33
+ }
34
+
35
+ visitContractDefinition(node) {
36
+ this.currentContract = node.name;
37
+ this.contractCode = this.getCodeSnippet(node.loc);
38
+ }
39
+
40
+ visitFunctionDefinition(node) {
41
+ this.currentFunction = node.name || 'constructor';
42
+ this.currentFunctionNode = node;
43
+
44
+ // Skip internal/private functions (not front-runnable by external actors)
45
+ if (node.visibility === 'private' || node.visibility === 'internal') {
46
+ return;
47
+ }
48
+
49
+ // Check function parameters for sensitive patterns
50
+ this.checkSwapFunction(node);
51
+
52
+ // Check function body for front-running patterns
53
+ if (node.body) {
54
+ this.checkFunctionBody(node);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Check swap functions for proper slippage protection
60
+ */
61
+ checkSwapFunction(node) {
62
+ const funcName = (node.name || '').toLowerCase();
63
+ const funcCode = node.body ? this.getCodeSnippet(node.loc) : '';
64
+
65
+ // Check if this is a swap-like function
66
+ const isSwapFunction = funcName.includes('swap') ||
67
+ funcName.includes('exchange') ||
68
+ funcName.includes('trade') ||
69
+ funcName.includes('buy') ||
70
+ funcName.includes('sell');
71
+
72
+ if (!isSwapFunction) return;
73
+
74
+ // Check for slippage protection in parameters
75
+ const hasSlippageProtection = this.checkSlippageProtection(node, funcCode);
76
+
77
+ if (!hasSlippageProtection.hasProtection) {
78
+ // Check if it calls an external swap with protection
79
+ const delegatesToProtected = this.delegatesToProtectedSwap(funcCode);
80
+
81
+ if (!delegatesToProtected) {
82
+ this.reportMissingSlippageProtection(node, hasSlippageProtection);
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Check for slippage protection mechanisms
89
+ */
90
+ checkSlippageProtection(node, funcCode) {
91
+ const result = {
92
+ hasProtection: false,
93
+ hasMinAmount: false,
94
+ hasDeadline: false,
95
+ hasSlippageTolerance: false
96
+ };
97
+
98
+ if (!node.parameters) return result;
99
+
100
+ // Check parameters
101
+ for (const param of node.parameters) {
102
+ const paramName = (param.name || '').toLowerCase();
103
+
104
+ if (paramName.includes('min') && (paramName.includes('amount') || paramName.includes('out') || paramName.includes('return'))) {
105
+ result.hasMinAmount = true;
106
+ }
107
+ if (paramName.includes('deadline') || paramName.includes('expiry') || paramName.includes('validuntil')) {
108
+ result.hasDeadline = true;
109
+ }
110
+ if (paramName.includes('slippage') || paramName.includes('tolerance')) {
111
+ result.hasSlippageTolerance = true;
112
+ }
113
+ }
114
+
115
+ // Check function body for slippage checks
116
+ if (funcCode) {
117
+ // require(amountOut >= minAmount) patterns
118
+ if (/require\s*\([^)]*>=\s*\w*(min|Min)/i.test(funcCode)) {
119
+ result.hasMinAmount = true;
120
+ }
121
+ // Deadline checks
122
+ if (/require\s*\([^)]*block\.timestamp\s*[<>=]/i.test(funcCode) ||
123
+ /require\s*\([^)]*deadline/i.test(funcCode)) {
124
+ result.hasDeadline = true;
125
+ }
126
+ }
127
+
128
+ result.hasProtection = result.hasMinAmount || result.hasSlippageTolerance;
129
+
130
+ return result;
131
+ }
132
+
133
+ /**
134
+ * Check if function delegates to a protected swap (e.g., Uniswap Router)
135
+ */
136
+ delegatesToProtectedSwap(funcCode) {
137
+ // Known protected swap patterns
138
+ const protectedPatterns = [
139
+ /swapExactTokensForTokens\s*\([^)]*,\s*\w+\s*,/, // Uniswap V2 with minAmountOut
140
+ /swapExactETHForTokens\s*\{/,
141
+ /exactInputSingle\s*\(/, // Uniswap V3
142
+ /exactInput\s*\(/,
143
+ /\.swap\s*\([^)]*amountOutMin/i,
144
+ ];
145
+
146
+ return protectedPatterns.some(p => p.test(funcCode));
147
+ }
148
+
149
+ checkFunctionBody(node) {
150
+ const funcCode = this.getCodeSnippet(node.loc);
151
+ const funcName = (node.name || '').toLowerCase();
152
+
153
+ // 1. Check for weak commit-reveal patterns
154
+ this.checkCommitReveal(node, funcCode, funcName);
155
+
156
+ // 2. Check for auction patterns
157
+ this.checkAuctionPattern(node, funcCode, funcName);
158
+
159
+ // 3. Check for ERC20 approve (only in specific risky contexts)
160
+ this.checkApprovePattern(node, funcCode);
161
+
162
+ // 4. Check for signature replay issues
163
+ this.checkSignatureReplay(node, funcCode);
164
+ }
165
+
166
+ /**
167
+ * Check for weak commit-reveal implementations
168
+ */
169
+ checkCommitReveal(node, funcCode, funcName) {
170
+ // Must be a reveal function with hash verification
171
+ const isRevealFunction = funcName.includes('reveal') ||
172
+ (funcCode.includes('keccak256') && /commit|reveal|secret/i.test(funcCode));
173
+
174
+ if (!isRevealFunction) return;
175
+
176
+ // Check for proper block delay
177
+ const hasBlockDelay = /block\.number\s*[->]\s*commit.*block/i.test(funcCode) ||
178
+ /commitBlock.*\+\s*\d+/i.test(funcCode) ||
179
+ /require.*block\.number\s*>=?\s*\w+\s*\+\s*\d+/i.test(funcCode);
180
+
181
+ // Check for timestamp delay (less secure but still a protection)
182
+ const hasTimestampDelay = /block\.timestamp\s*>=?\s*\w+\s*\+\s*\d+/i.test(funcCode);
183
+
184
+ // Check for known secure patterns
185
+ const hasSecurePattern = /revealDeadline|commitPeriod|REVEAL_DELAY/i.test(funcCode);
186
+
187
+ if (!hasBlockDelay && !hasTimestampDelay && !hasSecurePattern) {
188
+ this.reportWeakCommitReveal(node);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Check for vulnerable auction patterns
194
+ */
195
+ checkAuctionPattern(node, funcCode, funcName) {
196
+ const isAuctionFunction = funcName.includes('bid') ||
197
+ funcName.includes('auction') ||
198
+ funcName.includes('offer');
199
+
200
+ if (!isAuctionFunction) return;
201
+
202
+ // Skip if it's just checking bids (view function)
203
+ if (node.stateMutability === 'view' || node.stateMutability === 'pure') {
204
+ return;
205
+ }
206
+
207
+ // Check for sealed/commit patterns
208
+ const hasSealedBid = /commit|sealed|hash|blind/i.test(funcCode);
209
+
210
+ // Check for private mempool usage indicators
211
+ const hasPrivateSubmission = /flashbots|private|confidential/i.test(funcCode);
212
+
213
+ if (!hasSealedBid && !hasPrivateSubmission) {
214
+ // Check if bid amount is a direct parameter (front-runnable)
215
+ const hasBidAmountParam = node.parameters &&
216
+ node.parameters.some(p => /amount|value|bid/i.test(p.name || ''));
217
+
218
+ if (hasBidAmountParam || /msg\.value/.test(funcCode)) {
219
+ this.reportVisibleBidding(node);
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Check for ERC20 approve front-running (only flag risky patterns)
226
+ */
227
+ checkApprovePattern(node, funcCode) {
228
+ // Only check if function contains approve
229
+ if (!funcCode.includes('.approve(')) return;
230
+
231
+ // Check for safe patterns that mitigate the issue
232
+
233
+ // Pattern 1: Uses increaseAllowance/decreaseAllowance instead
234
+ if (/increaseAllowance|decreaseAllowance/i.test(this.contractCode)) {
235
+ // Contract uses safe allowance patterns
236
+ return;
237
+ }
238
+
239
+ // Pattern 2: Sets to 0 first, then to new value
240
+ const setsToZeroFirst = /\.approve\s*\([^,]+,\s*0\s*\)/i.test(funcCode) &&
241
+ /\.approve\s*\([^,]+,\s*[^0]/i.test(funcCode);
242
+ if (setsToZeroFirst) {
243
+ return;
244
+ }
245
+
246
+ // Pattern 3: Uses SafeERC20
247
+ if (/safeApprove|safeIncreaseAllowance|forceApprove/i.test(funcCode)) {
248
+ return;
249
+ }
250
+
251
+ // Pattern 4: Initial approval (setting from 0)
252
+ // If the function is named like "initialize" or happens in constructor, it's likely safe
253
+ if (/constructor|initialize|init|setup/i.test(this.currentFunction)) {
254
+ return;
255
+ }
256
+
257
+ // Pattern 5: Approval to trusted addresses only (routers, etc)
258
+ if (/ROUTER|UNISWAP|SUSHISWAP|PANCAKE/i.test(funcCode)) {
259
+ // Approving to known routers - common safe pattern
260
+ return;
261
+ }
262
+
263
+ // Only flag if this looks like user-facing allowance change
264
+ const funcName = this.currentFunction.toLowerCase();
265
+ const isUserFacing = /approve|allowance|permit/i.test(funcName) ||
266
+ node.visibility === 'external' ||
267
+ node.visibility === 'public';
268
+
269
+ if (isUserFacing) {
270
+ this.reportApprovalFrontRunning(node, funcCode);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Check for signature replay vulnerabilities
276
+ */
277
+ checkSignatureReplay(node, funcCode) {
278
+ // Check for signature verification
279
+ const hasSignatureVerification = /ecrecover|ECDSA\.recover|SignatureChecker/i.test(funcCode);
280
+
281
+ if (!hasSignatureVerification) return;
282
+
283
+ // Check for replay protection
284
+ const replayProtection = {
285
+ hasNonce: /nonce/i.test(funcCode),
286
+ hasDeadline: /deadline|expir|validUntil/i.test(funcCode),
287
+ hasChainId: /chainId|chainid|block\.chainid/i.test(funcCode),
288
+ marksUsed: /used\[|usedNonces|usedSignatures|invalidate/i.test(funcCode),
289
+ hasEIP712: /DOMAIN_SEPARATOR|_domainSeparator|EIP712/i.test(funcCode)
290
+ };
291
+
292
+ const protectionCount = Object.values(replayProtection).filter(v => v).length;
293
+
294
+ // EIP712 typically includes chain ID and proper domain
295
+ if (replayProtection.hasEIP712) {
296
+ // Still check for nonce/deadline even with EIP712
297
+ if (!replayProtection.hasNonce && !replayProtection.marksUsed) {
298
+ this.reportSignatureReplayMissingNonce(node);
299
+ }
300
+ return;
301
+ }
302
+
303
+ // Need at least nonce OR marking as used, plus deadline/chainId
304
+ if (!replayProtection.hasNonce && !replayProtection.marksUsed) {
305
+ this.reportSignatureReplay(node, replayProtection);
306
+ } else if (!replayProtection.hasDeadline && !replayProtection.hasChainId) {
307
+ // Has nonce but missing other protections
308
+ this.reportSignatureReplayWeak(node, replayProtection);
309
+ }
310
+ }
311
+
312
+ reportMissingSlippageProtection(node, analysis) {
313
+ const missingParts = [];
314
+ if (!analysis.hasMinAmount && !analysis.hasSlippageTolerance) {
315
+ missingParts.push('minimum output amount');
316
+ }
317
+ if (!analysis.hasDeadline) {
318
+ missingParts.push('deadline');
319
+ }
320
+
321
+ this.addFinding({
322
+ title: 'Missing Slippage Protection in Swap',
323
+ description: `Function '${this.currentFunction}' performs token swaps without ${missingParts.join(' or ')}. Transactions can be sandwiched by MEV bots, resulting in users receiving fewer tokens than expected.`,
324
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
325
+ line: node.loc ? node.loc.start.line : 0,
326
+ column: node.loc ? node.loc.start.column : 0,
327
+ code: this.getCodeSnippet(node.loc),
328
+ severity: 'HIGH',
329
+ confidence: 'HIGH',
330
+ exploitable: true,
331
+ exploitabilityScore: 85,
332
+ attackVector: 'sandwich-attack',
333
+ recommendation: `Add 'minAmountOut' parameter and require that output >= minAmountOut. Add 'deadline' parameter and require block.timestamp <= deadline. Consider using a DEX aggregator with built-in MEV protection.`,
334
+ references: [
335
+ 'https://docs.uniswap.org/contracts/v2/guides/smart-contract-integration/trading-from-a-smart-contract',
336
+ 'https://www.paradigm.xyz/2020/08/ethereum-is-a-dark-forest'
337
+ ],
338
+ foundryPoC: this.generateSandwichPoC()
339
+ });
340
+ }
341
+
342
+ reportWeakCommitReveal(node) {
343
+ this.addFinding({
344
+ title: 'Weak Commit-Reveal Pattern',
345
+ description: `Commit-reveal implementation without sufficient block delay. Attackers watching the mempool can front-run reveal transactions in the same block by paying higher gas.`,
346
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
347
+ line: node.loc ? node.loc.start.line : 0,
348
+ column: node.loc ? node.loc.start.column : 0,
349
+ code: this.getCodeSnippet(node.loc),
350
+ severity: 'HIGH',
351
+ confidence: 'MEDIUM',
352
+ exploitable: true,
353
+ exploitabilityScore: 70,
354
+ attackVector: 'commit-reveal-frontrun',
355
+ recommendation: 'Require minimum block delay (e.g., 2+ blocks) between commit and reveal. Store commitBlock and require block.number >= commitBlock + DELAY. Consider using Flashbots Protect for private submission.',
356
+ references: [
357
+ 'https://swcregistry.io/docs/SWC-114'
358
+ ]
359
+ });
360
+ }
361
+
362
+ reportVisibleBidding(node) {
363
+ this.addFinding({
364
+ title: 'Visible Bid Amount - Front-Running Risk',
365
+ description: `Auction bid amount is visible in mempool before execution. Competitors can see bids and front-run with higher amounts, or miners can reorder transactions for profit.`,
366
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
367
+ line: node.loc ? node.loc.start.line : 0,
368
+ column: node.loc ? node.loc.start.column : 0,
369
+ code: this.getCodeSnippet(node.loc),
370
+ severity: 'MEDIUM',
371
+ confidence: 'MEDIUM',
372
+ exploitable: true,
373
+ exploitabilityScore: 60,
374
+ attackVector: 'auction-frontrun',
375
+ recommendation: 'Implement sealed-bid auction: (1) Commit phase: users submit hash(bid, salt), (2) Reveal phase: users reveal actual bid after commit deadline. Alternatively, use private transaction submission via Flashbots.',
376
+ references: [
377
+ 'https://ethereum.org/en/developers/docs/mev/'
378
+ ]
379
+ });
380
+ }
381
+
382
+ reportApprovalFrontRunning(node, funcCode) {
383
+ this.addFinding({
384
+ title: 'ERC20 Approve Race Condition',
385
+ description: `Direct use of approve() when changing non-zero allowances creates a race condition. A malicious spender can front-run the approve transaction to use both old and new allowances.`,
386
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
387
+ line: node.loc ? node.loc.start.line : 0,
388
+ column: node.loc ? node.loc.start.column : 0,
389
+ code: this.extractApproveCode(funcCode),
390
+ severity: 'MEDIUM',
391
+ confidence: 'MEDIUM',
392
+ exploitable: true,
393
+ exploitabilityScore: 45,
394
+ attackVector: 'approve-race-condition',
395
+ recommendation: 'Use increaseAllowance()/decreaseAllowance() from OpenZeppelin, or set allowance to 0 first with require(currentAllowance == 0 || newAllowance == 0). Consider using permit() for gasless approvals.',
396
+ references: [
397
+ 'https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20-increaseAllowance-address-uint256-'
398
+ ]
399
+ });
400
+ }
401
+
402
+ reportSignatureReplay(node, protection) {
403
+ this.addFinding({
404
+ title: 'Signature Replay Vulnerability',
405
+ description: `Signature verification without proper replay protection. Signed messages can be replayed multiple times (missing nonce) or across chains (missing chainId).`,
406
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
407
+ line: node.loc ? node.loc.start.line : 0,
408
+ column: node.loc ? node.loc.start.column : 0,
409
+ code: this.getCodeSnippet(node.loc),
410
+ severity: 'CRITICAL',
411
+ confidence: 'HIGH',
412
+ exploitable: true,
413
+ exploitabilityScore: 90,
414
+ attackVector: 'signature-replay',
415
+ recommendation: 'Implement EIP-712 typed data signing. Include: (1) nonce that increments per-signer, (2) deadline/expiry timestamp, (3) chainId from block.chainid. Track used signatures/nonces in mapping.',
416
+ references: [
417
+ 'https://swcregistry.io/docs/SWC-121',
418
+ 'https://eips.ethereum.org/EIPS/eip-712'
419
+ ],
420
+ foundryPoC: this.generateSignatureReplayPoC()
421
+ });
422
+ }
423
+
424
+ reportSignatureReplayMissingNonce(node) {
425
+ this.addFinding({
426
+ title: 'Signature Missing Nonce/Usage Tracking',
427
+ description: `EIP-712 signature implementation without nonce or usage tracking. While EIP-712 provides domain separation, signatures can still be replayed if not invalidated after use.`,
428
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
429
+ line: node.loc ? node.loc.start.line : 0,
430
+ column: node.loc ? node.loc.start.column : 0,
431
+ code: this.getCodeSnippet(node.loc),
432
+ severity: 'HIGH',
433
+ confidence: 'MEDIUM',
434
+ exploitable: true,
435
+ exploitabilityScore: 70,
436
+ recommendation: 'Add incrementing nonce per-signer OR track used signature hashes in mapping. Include nonce in the signed struct.',
437
+ references: [
438
+ 'https://eips.ethereum.org/EIPS/eip-712'
439
+ ]
440
+ });
441
+ }
442
+
443
+ reportSignatureReplayWeak(node, protection) {
444
+ const missing = [];
445
+ if (!protection.hasDeadline) missing.push('deadline');
446
+ if (!protection.hasChainId) missing.push('chainId');
447
+
448
+ this.addFinding({
449
+ title: 'Weak Signature Replay Protection',
450
+ description: `Signature has nonce but missing ${missing.join(' and ')}. Signatures may be held indefinitely or replayed on other chains.`,
451
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
452
+ line: node.loc ? node.loc.start.line : 0,
453
+ column: node.loc ? node.loc.start.column : 0,
454
+ code: this.getCodeSnippet(node.loc),
455
+ severity: 'MEDIUM',
456
+ confidence: 'MEDIUM',
457
+ exploitable: true,
458
+ exploitabilityScore: 50,
459
+ recommendation: `Add ${missing.join(' and ')} to the signed message. Use block.chainid for cross-chain protection.`,
460
+ references: [
461
+ 'https://eips.ethereum.org/EIPS/eip-712'
462
+ ]
463
+ });
464
+ }
465
+
466
+ extractApproveCode(funcCode) {
467
+ // Extract just the approve-related lines
468
+ const lines = funcCode.split('\n');
469
+ const approveLines = lines.filter(l => /\.approve\s*\(/.test(l));
470
+ return approveLines.join('\n') || funcCode.substring(0, 150);
471
+ }
472
+
473
+ generateSandwichPoC() {
474
+ return `// SPDX-License-Identifier: MIT
475
+ pragma solidity ^0.8.0;
476
+
477
+ import "forge-std/Test.sol";
478
+
479
+ /**
480
+ * Proof of Concept: Sandwich Attack on Unprotected Swap
481
+ * Demonstrates how MEV bots profit from missing slippage protection
482
+ */
483
+ contract SandwichAttackExploit is Test {
484
+ address constant TARGET = address(0); // Vulnerable contract
485
+ address constant DEX = address(0); // DEX being used
486
+
487
+ function testSandwichAttack() public {
488
+ // Attacker monitors mempool for unprotected swaps
489
+
490
+ // Step 1: FRONTRUN - Buy tokens before victim
491
+ // This increases the price
492
+ // DEX.swap(ETH_AMOUNT, 0); // No minOut needed for attacker
493
+
494
+ // Step 2: Victim's transaction executes at worse price
495
+ // (simulated - in reality this is the pending tx)
496
+
497
+ // Step 3: BACKRUN - Sell tokens after victim
498
+ // Attacker profits from price increase caused by victim
499
+ // DEX.swap(TOKENS_BOUGHT, 0);
500
+
501
+ // Profit = tokens received in step 3 - ETH spent in step 1
502
+ // Victim receives fewer tokens due to price manipulation
503
+ }
504
+ }`;
505
+ }
506
+
507
+ generateSignatureReplayPoC() {
508
+ return `// SPDX-License-Identifier: MIT
509
+ pragma solidity ^0.8.0;
510
+
511
+ import "forge-std/Test.sol";
512
+
513
+ /**
514
+ * Proof of Concept: Signature Replay Attack
515
+ * Demonstrates replaying a valid signature multiple times
516
+ */
517
+ contract SignatureReplayExploit is Test {
518
+ address constant TARGET = address(0);
519
+
520
+ function testSignatureReplay() public {
521
+ // Assume we have a valid signature for some action
522
+ bytes memory signature; // = captured from previous transaction
523
+
524
+ // First use - legitimate
525
+ // TARGET.executeWithSignature(data, signature);
526
+
527
+ // Replay 1 - should fail but succeeds without nonce
528
+ // TARGET.executeWithSignature(data, signature);
529
+
530
+ // Replay 2 - continues to succeed
531
+ // TARGET.executeWithSignature(data, signature);
532
+
533
+ // Attacker can replay indefinitely until signature is manually invalidated
534
+ }
535
+
536
+ function testCrossChainReplay() public {
537
+ // On Mainnet, user signs transaction
538
+ bytes memory signature; // = signed on mainnet
539
+
540
+ // Without chainId in signature, same signature works on:
541
+ // - Polygon fork
542
+ // vm.chainId(137);
543
+ // TARGET.executeWithSignature(data, signature); // Works!
544
+
545
+ // - Arbitrum fork
546
+ // vm.chainId(42161);
547
+ // TARGET.executeWithSignature(data, signature); // Works!
548
+ }
549
+ }`;
550
+ }
551
+ }
552
+
553
+ module.exports = FrontRunningDetector;