solidity-argus 0.1.0

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 (131) hide show
  1. package/AGENTS.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +249 -0
  4. package/package.json +43 -0
  5. package/skills/INVENTORY.md +79 -0
  6. package/skills/README.md +56 -0
  7. package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +424 -0
  8. package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +157 -0
  9. package/skills/checklists/cyfrin-defi-core/SKILL.md +373 -0
  10. package/skills/checklists/cyfrin-defi-integrations/SKILL.md +412 -0
  11. package/skills/checklists/cyfrin-gas/SKILL.md +55 -0
  12. package/skills/checklists/general-audit/SKILL.md +433 -0
  13. package/skills/methodology/audit-workflow/SKILL.md +129 -0
  14. package/skills/methodology/report-template/SKILL.md +190 -0
  15. package/skills/methodology/severity-classification/SKILL.md +179 -0
  16. package/skills/protocol-patterns/amm-dex/SKILL.md +229 -0
  17. package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +317 -0
  18. package/skills/protocol-patterns/dao-governance/SKILL.md +281 -0
  19. package/skills/protocol-patterns/lending-borrowing/SKILL.md +221 -0
  20. package/skills/protocol-patterns/staking-vesting/SKILL.md +247 -0
  21. package/skills/references/exploit-reference/SKILL.md +259 -0
  22. package/skills/references/smartbugs-examples/SKILL.md +296 -0
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +298 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +59 -0
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +59 -0
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +61 -0
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +55 -0
  28. package/skills/vulnerability-patterns/default-visibility/SKILL.md +62 -0
  29. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +60 -0
  30. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +59 -0
  31. package/skills/vulnerability-patterns/dos-revert/SKILL.md +72 -0
  32. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +249 -0
  33. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +51 -0
  34. package/skills/vulnerability-patterns/hash-collision/SKILL.md +52 -0
  35. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +61 -0
  36. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +60 -0
  37. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +59 -0
  38. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +61 -0
  39. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +61 -0
  40. package/skills/vulnerability-patterns/logic-errors/SKILL.md +333 -0
  41. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +60 -0
  42. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +66 -0
  43. package/skills/vulnerability-patterns/off-by-one/SKILL.md +67 -0
  44. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +252 -0
  45. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +65 -0
  46. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +61 -0
  47. package/skills/vulnerability-patterns/reentrancy/SKILL.md +266 -0
  48. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +72 -0
  49. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +59 -0
  50. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +63 -0
  51. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +52 -0
  52. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +65 -0
  53. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +61 -0
  54. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +63 -0
  55. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +56 -0
  56. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +80 -0
  57. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +69 -0
  58. package/skills/vulnerability-patterns/unused-variables/SKILL.md +70 -0
  59. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +81 -0
  60. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +77 -0
  61. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +294 -0
  62. package/src/agents/argus-prompt.ts +407 -0
  63. package/src/agents/pythia-prompt.ts +134 -0
  64. package/src/agents/scribe-prompt.ts +87 -0
  65. package/src/agents/sentinel-prompt.ts +133 -0
  66. package/src/cli/cli-program.ts +67 -0
  67. package/src/cli/commands/doctor.ts +83 -0
  68. package/src/cli/commands/init.ts +46 -0
  69. package/src/cli/commands/install.ts +55 -0
  70. package/src/cli/index.ts +13 -0
  71. package/src/cli/tui-prompts.ts +75 -0
  72. package/src/cli/types.ts +9 -0
  73. package/src/config/index.ts +3 -0
  74. package/src/config/loader.ts +36 -0
  75. package/src/config/schema.ts +82 -0
  76. package/src/config/types.ts +4 -0
  77. package/src/constants/defaults.ts +6 -0
  78. package/src/create-hooks.ts +84 -0
  79. package/src/create-managers.ts +26 -0
  80. package/src/create-tools.ts +30 -0
  81. package/src/features/audit-enforcer/audit-enforcer.ts +34 -0
  82. package/src/features/audit-enforcer/index.ts +1 -0
  83. package/src/features/background-agent/background-manager.ts +200 -0
  84. package/src/features/background-agent/index.ts +1 -0
  85. package/src/features/context-monitor/context-monitor.ts +48 -0
  86. package/src/features/context-monitor/index.ts +4 -0
  87. package/src/features/context-monitor/tool-output-truncator.ts +17 -0
  88. package/src/features/error-recovery/index.ts +2 -0
  89. package/src/features/error-recovery/session-recovery.ts +27 -0
  90. package/src/features/error-recovery/tool-error-recovery.ts +35 -0
  91. package/src/features/index.ts +5 -0
  92. package/src/features/persistent-state/audit-state-manager.ts +121 -0
  93. package/src/features/persistent-state/index.ts +1 -0
  94. package/src/hooks/compaction-hook.ts +50 -0
  95. package/src/hooks/config-handler.ts +116 -0
  96. package/src/hooks/event-hook-v2.ts +93 -0
  97. package/src/hooks/event-hook.ts +74 -0
  98. package/src/hooks/hook-system.ts +9 -0
  99. package/src/hooks/index.ts +5 -0
  100. package/src/hooks/knowledge-sync-hook.ts +57 -0
  101. package/src/hooks/safe-create-hook.ts +15 -0
  102. package/src/hooks/system-prompt-hook.ts +126 -0
  103. package/src/hooks/tool-tracking-hook.ts +234 -0
  104. package/src/hooks/types.ts +16 -0
  105. package/src/index.ts +36 -0
  106. package/src/knowledge/scvd-client.ts +242 -0
  107. package/src/knowledge/scvd-index.ts +183 -0
  108. package/src/knowledge/scvd-sync.ts +85 -0
  109. package/src/managers/index.ts +1 -0
  110. package/src/managers/types.ts +85 -0
  111. package/src/plugin-interface.ts +38 -0
  112. package/src/shared/binary-utils.ts +63 -0
  113. package/src/shared/deep-merge.ts +71 -0
  114. package/src/shared/file-utils.ts +56 -0
  115. package/src/shared/index.ts +5 -0
  116. package/src/shared/jsonc-parser.ts +39 -0
  117. package/src/shared/logger.ts +36 -0
  118. package/src/state/audit-state.ts +27 -0
  119. package/src/state/finding-store.ts +126 -0
  120. package/src/state/plugin-state.ts +14 -0
  121. package/src/state/types.ts +61 -0
  122. package/src/tools/contract-analyzer-tool.ts +184 -0
  123. package/src/tools/forge-fuzz-tool.ts +311 -0
  124. package/src/tools/forge-test-tool.ts +397 -0
  125. package/src/tools/pattern-checker-tool.ts +337 -0
  126. package/src/tools/report-generator-tool.ts +308 -0
  127. package/src/tools/slither-tool.ts +465 -0
  128. package/src/tools/solodit-search-tool.ts +131 -0
  129. package/src/tools/sync-knowledge-tool.ts +116 -0
  130. package/src/utils/project-detector.ts +133 -0
  131. package/src/utils/solidity-parser.ts +174 -0
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: oracle-manipulation
3
+ description: Oracle manipulation techniques, case studies, and secure pricing integration controls for DeFi.
4
+ ---
5
+
6
+ <!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
7
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
8
+
9
+ # Oracle Manipulation Exploits
10
+
11
+ ## Overview
12
+
13
+ Oracle manipulation attacks exploit price feeds to extract value. Attackers manipulate the price source, then use the manipulated price to profit (liquidations, swaps, borrows).
14
+
15
+ **Total Losses:** $500M+ across DeFi
16
+
17
+ ---
18
+
19
+ ## Attack Patterns
20
+
21
+ ### 1. Spot Price Manipulation
22
+
23
+ Using AMM spot prices directly instead of TWAPs.
24
+
25
+ ```solidity
26
+ // VULNERABLE: Spot price from Uniswap
27
+ function getPrice() external view returns (uint256) {
28
+ (uint112 reserve0, uint112 reserve1,) = pair.getReserves();
29
+ return reserve1 * 1e18 / reserve0; // Manipulatable in same tx!
30
+ }
31
+ ```
32
+
33
+ **Fix:** Use TWAP (Time-Weighted Average Price) or Chainlink.
34
+
35
+ ### 2. Flash Loan + Price Manipulation
36
+
37
+ 1. Flash loan large amount
38
+ 2. Swap to manipulate AMM price
39
+ 3. Interact with victim protocol using manipulated price
40
+ 4. Swap back, repay flash loan
41
+ 5. Profit
42
+
43
+ ### 3. Stale Price Data
44
+
45
+ ```solidity
46
+ // VULNERABLE: No freshness check
47
+ function getPrice(address token) external view returns (uint256) {
48
+ (, int256 price,,,) = priceFeed.latestRoundData();
49
+ return uint256(price); // Could be hours old!
50
+ }
51
+
52
+ // SECURE: Freshness validation
53
+ function getPrice(address token) external view returns (uint256) {
54
+ (
55
+ uint80 roundId,
56
+ int256 price,
57
+ ,
58
+ uint256 updatedAt,
59
+ uint80 answeredInRound
60
+ ) = priceFeed.latestRoundData();
61
+
62
+ require(price > 0, "Invalid price");
63
+ require(updatedAt > block.timestamp - MAX_DELAY, "Stale price");
64
+ require(answeredInRound >= roundId, "Stale round");
65
+
66
+ return uint256(price);
67
+ }
68
+ ```
69
+
70
+ ### 4. TWAP Manipulation (Low Liquidity)
71
+
72
+ Even TWAPs can be manipulated if:
73
+ - Liquidity is low
74
+ - TWAP window is short
75
+ - Attacker controls multiple blocks (MEV)
76
+
77
+ ---
78
+
79
+ ## Real Exploits
80
+
81
+ ### Harvest Finance (Oct 2020) — $34M
82
+
83
+ **What happened:**
84
+ - Attacker used flash loans to manipulate Curve pool prices
85
+ - Harvest used Curve spot price for USDC/USDT
86
+ - Deposited at manipulated (low) price, withdrew at normal price
87
+
88
+ **Root cause:** Spot price oracle on low-liquidity pool
89
+
90
+ **Lesson:** Never use spot prices. Use TWAPs with sufficient window.
91
+
92
+ ### Mango Markets (Oct 2022) — $116M
93
+
94
+ **What happened:**
95
+ - Attacker manipulated MNGO perpetual price on Mango
96
+ - Used inflated collateral value to borrow all assets
97
+ - Price manipulation was within protocol rules (no external oracle)
98
+
99
+ **Root cause:** Self-referential oracle (own perp price as collateral value)
100
+
101
+ **Lesson:** Don't use your own market prices as oracle for that same market.
102
+
103
+ ### Inverse Finance (Apr 2022) — $15.6M
104
+
105
+ **What happened:**
106
+ - Attacker manipulated INV/ETH SushiSwap price
107
+ - Used manipulated price to borrow DOLA
108
+ - TWAP window was only 25 minutes
109
+
110
+ **Root cause:** Short TWAP window on low-liquidity pair
111
+
112
+ **Lesson:** TWAP window must be long enough to make manipulation unprofitable.
113
+
114
+ ### bZx (Multiple 2020) — $8M+
115
+
116
+ **What happened:**
117
+ - Series of attacks using flash loans to manipulate prices
118
+ - Borrowed, manipulated, liquidated in single transaction
119
+
120
+ **Root cause:** Spot price oracles vulnerable to flash loan manipulation
121
+
122
+ ---
123
+
124
+ ## Detection Checklist
125
+
126
+ - [ ] Does the contract use spot prices from AMMs?
127
+ - [ ] Is Chainlink used? Are staleness checks implemented?
128
+ - [ ] If TWAP is used, what's the window? (< 30 min = risky)
129
+ - [ ] Is the oracle source low liquidity?
130
+ - [ ] Can price be manipulated within a single transaction?
131
+ - [ ] Are there freshness checks on price data?
132
+ - [ ] Is `answeredInRound >= roundId` checked?
133
+ - [ ] Does the protocol validate price sanity (bounds, deviation)?
134
+
135
+ ## Secure Patterns
136
+
137
+ ### Chainlink with Full Validation
138
+
139
+ ```solidity
140
+ function getPrice(address token) public view returns (uint256) {
141
+ AggregatorV3Interface feed = priceFeeds[token];
142
+ require(address(feed) != address(0), "No feed");
143
+
144
+ (
145
+ uint80 roundId,
146
+ int256 price,
147
+ ,
148
+ uint256 updatedAt,
149
+ uint80 answeredInRound
150
+ ) = feed.latestRoundData();
151
+
152
+ // Validation
153
+ require(price > 0, "Invalid price");
154
+ require(updatedAt > 0, "Round not complete");
155
+ require(answeredInRound >= roundId, "Stale round");
156
+ require(block.timestamp - updatedAt < HEARTBEAT, "Stale price");
157
+
158
+ return uint256(price);
159
+ }
160
+ ```
161
+
162
+ ### Circuit Breakers
163
+
164
+ ```solidity
165
+ uint256 public lastPrice;
166
+ uint256 public constant MAX_DEVIATION = 10; // 10%
167
+
168
+ function getPriceWithCircuitBreaker() external returns (uint256) {
169
+ uint256 newPrice = oracle.getPrice();
170
+
171
+ if (lastPrice > 0) {
172
+ uint256 deviation = newPrice > lastPrice
173
+ ? (newPrice - lastPrice) * 100 / lastPrice
174
+ : (lastPrice - newPrice) * 100 / lastPrice;
175
+ require(deviation <= MAX_DEVIATION, "Price deviation too high");
176
+ }
177
+
178
+ lastPrice = newPrice;
179
+ return newPrice;
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## References
186
+
187
+ - [samczsun: Taking undercollateralized loans](https://samczsun.com/taking-undercollateralized-loans-for-fun-and-for-profit/)
188
+ - [Chainlink Best Practices](https://docs.chain.link/data-feeds/selecting-data-feeds)
189
+ - [Euler Oracle Manipulation (Omniscia)](https://omniscia.io/reports/euler-finance-oracle-manipulation/)
190
+
191
+ ## Supplemental Heuristics (kadenzipfel)
192
+
193
+ ## Preconditions
194
+ - Transaction inputs are visible in the public mempool before block inclusion
195
+ - The outcome of the function depends on transaction ordering
196
+ - An attacker can profit by observing and front-running (or sandwiching) the victim's transaction
197
+
198
+ ## Vulnerable Pattern
199
+ ```solidity
200
+ // DEX swap without slippage protection
201
+ function swap(address tokenIn, address tokenOut, uint256 amountIn) external {
202
+ // No minimum output amount — attacker sandwiches:
203
+ // 1. Front-run: buy tokenOut (price goes up)
204
+ // 2. Victim's swap executes at worse price
205
+ // 3. Back-run: sell tokenOut (profit from price impact)
206
+ uint256 amountOut = getAmountOut(amountIn);
207
+ IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
208
+ IERC20(tokenOut).transfer(msg.sender, amountOut);
209
+ }
210
+
211
+ // On-chain secret submission
212
+ function submitAnswer(bytes32 answer) external {
213
+ // Answer visible in mempool — anyone can copy it
214
+ require(keccak256(abi.encodePacked(answer)) == targetHash);
215
+ _reward(msg.sender);
216
+ }
217
+
218
+ // ERC20 approval race condition
219
+ // User approves 100, wants to change to 50
220
+ // Attacker sees approve(50) in mempool, spends 100, then spends 50 = 150 total
221
+ ```
222
+
223
+ ## Detection Heuristics
224
+ 1. Search for DEX/swap functions: check for slippage protection (`minAmountOut` parameter) and deadline parameters — flag if absent
225
+ 2. Search for on-chain submissions of secrets, answers, or bids — these are observable in the mempool
226
+ 3. Search for ERC20 `approve` patterns: check if the contract sets allowance to zero before setting a new value
227
+ 4. Look for auction/bidding logic where observing others' bids provides an advantage
228
+ 5. Identify any function where the order of execution matters and inputs are publicly visible before inclusion
229
+
230
+ ## False Positives
231
+ - Transaction is submitted via a private mempool (Flashbots Protect, MEV Blocker)
232
+ - Commit-reveal scheme is used: commitment hash submitted first, reveal in a later block
233
+ - Slippage protection with `minAmountOut` and `deadline` parameters are present
234
+ - The function's outcome is order-independent (e.g., simple deposit into a vault at a fixed rate)
235
+
236
+ ## Remediation
237
+ - For DEX operations: require `minAmountOut` and `deadline` parameters for slippage protection
238
+ - For secret submissions: use commit-reveal schemes (hash commitment first, reveal later)
239
+ - For ERC20 approvals: use `increaseAllowance`/`decreaseAllowance` or set to zero first
240
+ - Consider Flashbots or private transaction relays for MEV-sensitive operations
241
+ ```solidity
242
+ function swap(
243
+ address tokenIn, address tokenOut, uint256 amountIn,
244
+ uint256 minAmountOut, // Slippage protection
245
+ uint256 deadline // Time protection
246
+ ) external {
247
+ require(block.timestamp <= deadline, "expired");
248
+ uint256 amountOut = getAmountOut(amountIn);
249
+ require(amountOut >= minAmountOut, "slippage");
250
+ // ... execute swap
251
+ }
252
+ ```
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: outdated-compiler-version
3
+ description: - Contract is compiled with a Solidity version significantly behind the latest stable release
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Outdated Compiler Version
8
+
9
+ ## Preconditions
10
+ - Contract is compiled with a Solidity version significantly behind the latest stable release
11
+ - The compiler version used has known bugs or is missing important security features
12
+
13
+ ## Vulnerable Pattern
14
+ ```solidity
15
+ // Old version missing built-in overflow checks
16
+ pragma solidity 0.7.6;
17
+
18
+ contract Token {
19
+ mapping(address => uint256) public balances;
20
+
21
+ function transfer(address to, uint256 amount) external {
22
+ // No overflow protection — 0.7.x lacks built-in checks
23
+ // Requires manual SafeMath usage
24
+ balances[msg.sender] -= amount;
25
+ balances[to] += amount;
26
+ }
27
+ }
28
+
29
+ // Slightly newer but still has known bugs
30
+ pragma solidity 0.8.0;
31
+ // 0.8.0 had ABI encoding bugs fixed in later patches
32
+ ```
33
+
34
+ ## Detection Heuristics
35
+ 1. Check the `pragma solidity` version in all contract files
36
+ 2. Compare against the latest stable Solidity release — flag if significantly outdated
37
+ 3. Cross-reference the exact version against the Solidity known bugs list
38
+ 4. Check if the contract misses key safety features from newer versions:
39
+ - <0.8.0: no built-in overflow/underflow checks
40
+ - <0.8.4: no custom errors (gas efficiency)
41
+ - <0.8.20: no PUSH0 opcode support
42
+ 5. Flag if the version has known critical bugs (check https://solidity.readthedocs.io/en/latest/bugs.html)
43
+
44
+ ## False Positives
45
+ - The version is intentionally chosen for compatibility with a specific deployment target or toolchain
46
+ - The version is recent (within 1-2 minor versions of latest) and has no known critical bugs
47
+ - The project has documented reasons for the specific version choice
48
+
49
+ ## Remediation
50
+ - Upgrade to the latest stable Solidity version unless there's a specific compatibility constraint
51
+ - Cross-reference the current version against the known bugs list before and after upgrading
52
+ - When upgrading across major boundaries (e.g., 0.7 to 0.8), review all arithmetic for compatibility with built-in overflow checks
53
+ ```solidity
54
+ // Use latest stable version
55
+ pragma solidity 0.8.24;
56
+
57
+ contract Token {
58
+ mapping(address => uint256) public balances;
59
+
60
+ function transfer(address to, uint256 amount) external {
61
+ balances[msg.sender] -= amount; // Built-in overflow check
62
+ balances[to] += amount;
63
+ }
64
+ }
65
+ ```
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: overflow-underflow
3
+ description: - Solidity <0.8.0 without SafeMath, OR
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Integer Overflow and Underflow
8
+
9
+ ## Preconditions
10
+ - Solidity <0.8.0 without SafeMath, OR
11
+ - Arithmetic inside `unchecked { }` blocks, OR
12
+ - Arithmetic inside `assembly { }` / Yul blocks, OR
13
+ - Type downcasting (e.g., `uint8(uint256Var)`), OR
14
+ - Shift operators (`<<`, `>>`) on values near type boundaries
15
+
16
+ ## Vulnerable Pattern
17
+ ```solidity
18
+ // Pre-0.8.0: wraps silently
19
+ uint256 balance = 0;
20
+ balance -= 1; // Underflows to 2^256 - 1
21
+
22
+ // Post-0.8.0 bypass via unchecked block
23
+ unchecked {
24
+ uint256 x = type(uint256).max;
25
+ x += 1; // Wraps to 0, no revert
26
+ }
27
+
28
+ // Type downcast truncation (all versions)
29
+ uint256 big = 256;
30
+ uint8 small = uint8(big); // Silently truncates to 0
31
+
32
+ // Assembly arithmetic (all versions)
33
+ assembly {
34
+ let x := sub(0, 1) // Underflows to max uint256
35
+ }
36
+ ```
37
+
38
+ ## Detection Heuristics
39
+ 1. Check the Solidity version: if <0.8.0, flag ALL arithmetic operations not wrapped in SafeMath
40
+ 2. If >=0.8.0, search for `unchecked` blocks and audit every arithmetic operation inside them
41
+ 3. Search for `assembly` blocks and audit all `add`, `sub`, `mul`, `div`, `shl`, `shr` operations within
42
+ 4. Search for type downcasts: patterns like `uint8(`, `uint16(`, `int8(`, etc. — verify the source value is bounds-checked before casting
43
+ 5. Search for shift operations (`<<`, `>>`) and verify the operand cannot overflow the target type
44
+ 6. Check if overflow-induced reverts on critical paths could cause DoS
45
+
46
+ ## False Positives
47
+ - Solidity >=0.8.0 arithmetic outside of `unchecked` and `assembly` blocks (automatically checked)
48
+ - `unchecked` blocks used only for loop counter increments (`i++`) where overflow is impossible due to bounded loop
49
+ - Downcasts where the value is validated to fit the target type before casting (e.g., `require(x <= type(uint8).max)`)
50
+ - Assembly arithmetic on values with proven bounds
51
+
52
+ ## Remediation
53
+ - For Solidity <0.8.0: use OpenZeppelin's `SafeMath` for all arithmetic
54
+ - For >=0.8.0: minimize `unchecked` blocks to only provably safe operations (e.g., bounded loop counters)
55
+ - Use OpenZeppelin's `SafeCast` library for all type downcasts
56
+ - Validate bounds before assembly arithmetic
57
+ ```solidity
58
+ // Safe downcast
59
+ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
60
+ uint8 small = SafeCast.toUint8(big); // Reverts if big > 255
61
+ ```
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: reentrancy
3
+ description: Reentrancy attack patterns, real incidents, and defensive coding checks for Solidity protocols.
4
+ ---
5
+
6
+ <!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
7
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
8
+
9
+ # Reentrancy Exploits
10
+
11
+ ## Overview
12
+
13
+ Reentrancy occurs when an external call allows the callee to re-enter the calling function before state updates complete.
14
+
15
+ **Types:**
16
+ 1. **Classic reentrancy** - Same function called again
17
+ 2. **Cross-function** - Different function in same contract
18
+ 3. **Cross-contract** - Function in different contract sharing state
19
+ 4. **Read-only** - View function returns stale state during reentrancy
20
+
21
+ ---
22
+
23
+ ## The DAO Hack (2016)
24
+
25
+ **Loss:** ~$60M (3.6M ETH)
26
+ **Root Cause:** Classic reentrancy
27
+
28
+ ### Vulnerable Code
29
+ ```solidity
30
+ function withdraw(uint _amount) public {
31
+ require(balances[msg.sender] >= _amount);
32
+
33
+ // External call BEFORE state update
34
+ (bool success,) = msg.sender.call{value: _amount}("");
35
+ require(success);
36
+
37
+ // State update AFTER - never reached during reentrancy
38
+ balances[msg.sender] -= _amount;
39
+ }
40
+ ```
41
+
42
+ ### Attack
43
+ ```solidity
44
+ contract Attacker {
45
+ DAO dao;
46
+
47
+ function attack() external payable {
48
+ dao.deposit{value: 1 ether}();
49
+ dao.withdraw(1 ether);
50
+ }
51
+
52
+ receive() external payable {
53
+ if (address(dao).balance >= 1 ether) {
54
+ dao.withdraw(1 ether); // Re-enter before balance update
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ### Fix: CEI Pattern
61
+ ```solidity
62
+ function withdraw(uint _amount) public {
63
+ require(balances[msg.sender] >= _amount);
64
+
65
+ balances[msg.sender] -= _amount; // State update FIRST
66
+
67
+ (bool success,) = msg.sender.call{value: _amount}("");
68
+ require(success);
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Cream Finance (2021)
75
+
76
+ **Loss:** ~$130M
77
+ **Root Cause:** Cross-contract reentrancy via AMP token (ERC777)
78
+
79
+ ### Vulnerable Pattern
80
+ ```solidity
81
+ // Cream's borrow function
82
+ function borrow(uint amount) external {
83
+ // Check collateral
84
+ require(getAccountLiquidity(msg.sender) >= amount);
85
+
86
+ // Transfer tokens (ERC777 calls receiver hook)
87
+ token.transfer(msg.sender, amount); // REENTRANCY HERE
88
+
89
+ // Update borrow balance
90
+ borrowBalances[msg.sender] += amount;
91
+ }
92
+ ```
93
+
94
+ ### Attack Flow
95
+ 1. Attacker deposits collateral
96
+ 2. Calls `borrow()` for AMP token
97
+ 3. AMP's ERC777 hook triggers during transfer
98
+ 4. In hook, attacker deposits more collateral + borrows again
99
+ 5. Second borrow succeeds because first borrow's balance not updated yet
100
+ 6. Repeat until drained
101
+
102
+ ### Fix
103
+ ```solidity
104
+ function borrow(uint amount) external nonReentrant {
105
+ require(getAccountLiquidity(msg.sender) >= amount);
106
+ borrowBalances[msg.sender] += amount; // Update FIRST
107
+ token.transfer(msg.sender, amount);
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Fei Protocol / Ondo Finance (2022)
114
+
115
+ **Loss:** ~$80M
116
+ **Root Cause:** Read-only reentrancy
117
+
118
+ ### Concept
119
+ ```solidity
120
+ // Protocol A's view function
121
+ function getExchangeRate() public view returns (uint) {
122
+ return totalAssets / totalShares; // Reads state
123
+ }
124
+
125
+ // Protocol B uses this
126
+ function deposit() external {
127
+ uint rate = protocolA.getExchangeRate(); // Gets STALE rate
128
+ uint shares = amount * rate;
129
+ // ...
130
+ }
131
+ ```
132
+
133
+ ### Attack Flow
134
+ 1. Attacker calls `withdraw()` on Protocol A
135
+ 2. During callback (before state update), call Protocol B
136
+ 3. Protocol B queries Protocol A's exchange rate
137
+ 4. Rate is stale (doesn't reflect pending withdrawal)
138
+ 5. Attacker gets more shares than deserved
139
+
140
+ ### Fix
141
+ - Check invariants in view functions
142
+ - Use mutex that view functions also respect
143
+ - Snapshot state at start of transaction
144
+
145
+ ---
146
+
147
+ ## Rari Fuse (2022)
148
+
149
+ **Loss:** ~$80M
150
+ **Root Cause:** Cross-function reentrancy
151
+
152
+ ### Pattern
153
+ ```solidity
154
+ function exitMarket(address cToken) external {
155
+ // Check no borrows
156
+ require(borrowBalances[msg.sender][cToken] == 0);
157
+
158
+ // Update membership
159
+ markets[msg.sender][cToken] = false;
160
+ }
161
+
162
+ function borrow(address cToken, uint amount) external {
163
+ require(markets[msg.sender][cToken] == true); // Must be in market
164
+
165
+ // Transfer - REENTRANCY POINT
166
+ cToken.transfer(msg.sender, amount);
167
+
168
+ borrowBalances[msg.sender][cToken] += amount;
169
+ }
170
+ ```
171
+
172
+ ### Attack
173
+ 1. Enter market, deposit collateral
174
+ 2. Call `borrow()`
175
+ 3. In callback, call `exitMarket()` - passes because borrow balance not yet updated
176
+ 4. Now out of market but have borrowed funds
177
+ 5. Collateral released, borrow never repaid
178
+
179
+ ---
180
+
181
+ ## Detection Checklist
182
+
183
+ - [ ] Map all external calls in each function
184
+ - [ ] Check state updates happen BEFORE external calls
185
+ - [ ] Verify `nonReentrant` modifier on vulnerable functions
186
+ - [ ] Check for ERC777/ERC721 callback hooks
187
+ - [ ] Look for cross-function shared state
188
+ - [ ] Check view functions for read-only reentrancy
189
+ - [ ] Verify flash loan callbacks don't enable reentrancy
190
+
191
+ ---
192
+
193
+ ## Patterns by Token Type
194
+
195
+ | Token Type | Reentrancy Vector |
196
+ |------------|-------------------|
197
+ | ETH | `call{value:}()` |
198
+ | ERC777 | `tokensToSend`, `tokensReceived` hooks |
199
+ | ERC721 | `onERC721Received` |
200
+ | ERC1155 | `onERC1155Received`, batch operations |
201
+ | Flash loans | Callback functions |
202
+
203
+ ---
204
+
205
+ ## Resources
206
+
207
+ - **DeFiHackLabs:** `submodules/DeFiHackLabs/test/Reentrancy/`
208
+ - **learn-evm-attacks:** `submodules/learn-evm-attacks/test/Reentrancy/`
209
+ - **SWC-107:** <https://swcregistry.io/docs/SWC-107>
210
+
211
+ ## Detection Heuristics (kadenzipfel)
212
+
213
+ ## Preconditions
214
+ - Contract makes an external call (ETH transfer, token transfer, `.call()`, `.send()`, `.transfer()`, callback hook)
215
+ - State is modified after the external call, not before
216
+ - No reentrancy guard (`nonReentrant` modifier) on the function
217
+ - For cross-function: two or more functions share state, and at least one makes an external call before updating that shared state
218
+ - For cross-contract: Contract B reads Contract A's state, and A makes an external call before updating it
219
+ - For read-only: Contract A has a reentrancy guard but updates state after an external call; Contract B reads A's state without sharing the same lock
220
+
221
+ ## Vulnerable Pattern
222
+ ```solidity
223
+ // Single-function reentrancy
224
+ function withdraw() external {
225
+ uint256 bal = balances[msg.sender];
226
+ // External call BEFORE state update
227
+ (bool success,) = msg.sender.call{value: bal}("");
228
+ require(success);
229
+ // State update AFTER external call — attacker reenters withdraw()
230
+ // and balances[msg.sender] is still the original value
231
+ balances[msg.sender] = 0;
232
+ }
233
+
234
+ // Hidden external calls that trigger callbacks:
235
+ // ERC721._safeMint() -> onERC721Received()
236
+ // ERC1155.safeTransferFrom() -> onERC1155Received()
237
+ // ERC777 token transfers -> tokensReceived() hook
238
+ ```
239
+
240
+ ## Detection Heuristics
241
+ 1. Identify all external calls: `.call()`, `.send()`, `.transfer()`, token transfers, `_safeMint()`, `_safeTransfer()`, ERC777/ERC1155 safe transfers
242
+ 2. For each external call, check if any state variable is written AFTER the call in the same function
243
+ 3. If state is written after an external call, check if a `nonReentrant` guard is present on the function — flag if absent
244
+ 4. Check for cross-function reentrancy: does the function share state with other functions that could be called during the reentrant window?
245
+ 5. Check for cross-contract reentrancy: does any other contract read this contract's state that is stale during the external call?
246
+ 6. Check for hidden callbacks: `_safeMint`, `_safeTransfer`, ERC777 hooks, ERC1155 hooks — these are external calls even though they don't look like `.call()`
247
+
248
+ ## False Positives
249
+ - State is updated BEFORE the external call (checks-effects-interactions pattern correctly followed)
250
+ - `nonReentrant` modifier is applied to the function
251
+ - The external call target is a trusted, immutable contract (e.g., WETH) with no callback mechanism
252
+ - The function is `view`/`pure` and cannot modify state
253
+ - The only state read after reentry is already finalized (e.g., immutable variables)
254
+
255
+ ## Remediation
256
+ - Apply the checks-effects-interactions pattern: perform all state changes before any external call
257
+ - Add OpenZeppelin's `ReentrancyGuard` with `nonReentrant` modifier to all functions that make external calls
258
+ - For cross-contract reentrancy, use a shared reentrancy lock across contracts or ensure state is finalized before external calls
259
+ ```solidity
260
+ function withdraw() external nonReentrant {
261
+ uint256 bal = balances[msg.sender];
262
+ balances[msg.sender] = 0; // State update BEFORE external call
263
+ (bool success,) = msg.sender.call{value: bal}("");
264
+ require(success);
265
+ }
266
+ ```