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,61 @@
1
+ ---
2
+ name: insufficient-gas-griefing
3
+ description: - Contract relays or forwards calls on behalf of users (meta-transactions, multisig execution, relayer patterns)
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Insufficient Gas Griefing
8
+
9
+ ## Preconditions
10
+ - Contract relays or forwards calls on behalf of users (meta-transactions, multisig execution, relayer patterns)
11
+ - The relayer/executor controls how much gas is forwarded to the sub-call
12
+ - Replay protection (nonce/hash marking) occurs before or regardless of sub-call success
13
+ - No minimum gas requirement is enforced before the sub-call
14
+
15
+ ## Vulnerable Pattern
16
+ ```solidity
17
+ function execute(address target, bytes calldata data, uint256 gasLimit) external {
18
+ // Replay protection BEFORE sub-call — marks as executed regardless
19
+ require(!executed[nonce], "already executed");
20
+ executed[nonce] = true;
21
+ nonce++;
22
+
23
+ // Relayer can provide just enough gas for the outer tx to succeed
24
+ // but insufficient gas for the inner call — it silently fails
25
+ (bool success,) = target.call{gas: gasLimit}(data);
26
+ // success is false, but the nonce is already consumed
27
+ // The action is permanently censored
28
+ }
29
+ ```
30
+
31
+ ## Detection Heuristics
32
+ 1. Identify relayer/meta-transaction patterns: functions that execute calls on behalf of other users
33
+ 2. Check if the function marks a nonce/hash as used BEFORE the sub-call succeeds — this enables permanent censorship
34
+ 3. Check if there's a `gasleft()` validation before the sub-call (e.g., `require(gasleft() >= requiredGas + overhead)`)
35
+ 4. Look for multisig `execute` functions where the executor controls gas forwarding
36
+ 5. Check if `.call{gas: X}` is used where `X` comes from the caller — the caller can set it too low
37
+
38
+ ## False Positives
39
+ - Replay protection only marks the nonce after confirming sub-call success
40
+ - The function enforces a minimum gas requirement before the sub-call
41
+ - The sub-call failure is propagated (e.g., `require(success)`) so the outer tx also reverts, preserving the nonce
42
+ - The gas parameter is fixed or validated against a minimum
43
+
44
+ ## Remediation
45
+ - Enforce minimum gas before sub-calls: `require(gasleft() >= gasLimit + OVERHEAD)`
46
+ - Only mark nonces/hashes as used AFTER confirming sub-call success
47
+ - Propagate sub-call failures to revert the outer transaction when appropriate
48
+ - Use EIP-150 rule awareness: the caller retains 1/64 of gas, so forward at least `gasLimit * 64/63`
49
+ ```solidity
50
+ function execute(address target, bytes calldata data, uint256 gasLimit) external {
51
+ require(gasleft() >= gasLimit + 10000, "insufficient gas");
52
+
53
+ (bool success, bytes memory result) = target.call{gas: gasLimit}(data);
54
+
55
+ // Only mark as executed if sub-call succeeded
56
+ if (success) {
57
+ executed[nonce] = true;
58
+ nonce++;
59
+ }
60
+ }
61
+ ```
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: lack-of-precision
3
+ description: - Contract performs integer arithmetic (division, fee calculations, reward distributions)
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Lack of Precision
8
+
9
+ ## Preconditions
10
+ - Contract performs integer arithmetic (division, fee calculations, reward distributions)
11
+ - Division is performed before multiplication, OR
12
+ - Numerators can be smaller than denominators (producing zero), OR
13
+ - No fixed-point scaling (WAD/RAY) is used for fractional calculations
14
+
15
+ ## Vulnerable Pattern
16
+ ```solidity
17
+ function calculateFee(uint256 amount, uint256 daysEarly) external view returns (uint256) {
18
+ // Division BEFORE multiplication — truncates intermediate result
19
+ uint256 dailyRate = amount / 365; // Loses precision
20
+ uint256 fee = dailyRate * daysEarly; // Error compounds
21
+
22
+ // Correct: amount * daysEarly / 365 (multiply first)
23
+ return fee;
24
+ }
25
+
26
+ function distribute(uint256 reward, uint256 totalShares) external {
27
+ for (uint256 i = 0; i < holders.length; i++) {
28
+ // If reward < totalShares, this is always 0
29
+ uint256 share = reward / totalShares * balances[holders[i]];
30
+ _transfer(holders[i], share);
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Detection Heuristics
36
+ 1. Search for division operations (`/`) in arithmetic expressions
37
+ 2. Check if division appears before multiplication in the same expression — this loses precision
38
+ 3. Check if the numerator can be smaller than the denominator — the result truncates to zero
39
+ 4. Look for fee, reward, interest, or share calculations without scaling factors (e.g., 1e18)
40
+ 5. Check rounding direction: does truncation favor the protocol or the user? In fee/debt calculations, rounding should favor the protocol; in reward/credit calculations, it should favor the user
41
+
42
+ ## False Positives
43
+ - Multiplication is performed before division in the correct order
44
+ - Fixed-point math libraries (WAD = 1e18, RAY = 1e27) are used to maintain precision
45
+ - The numerator is guaranteed to be larger than the denominator by prior validation
46
+ - The precision loss is intentionally accepted and documented (e.g., dust amounts)
47
+
48
+ ## Remediation
49
+ - Always multiply before dividing: `amount * rate / divisor` instead of `amount / divisor * rate`
50
+ - Use fixed-point math with scaling factors (1e18 for WAD, 1e27 for RAY)
51
+ - Round in favor of the protocol for fees/debts, in favor of users for rewards/credits
52
+ - Use `mulDiv` from OpenZeppelin or PRBMath for safe full-precision multiplication then division
53
+ ```solidity
54
+ // Correct: multiply first, then divide
55
+ uint256 fee = amount * daysEarly / 365;
56
+
57
+ // With scaling for precision
58
+ uint256 WAD = 1e18;
59
+ uint256 scaledRate = (amount * WAD) / totalSupply;
60
+ uint256 reward = (scaledRate * userBalance) / WAD;
61
+ ```
@@ -0,0 +1,333 @@
1
+ ---
2
+ name: logic-errors
3
+ description: Protocol logic bug patterns, exploit examples, and invariant-driven review strategies.
4
+ ---
5
+
6
+ <!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
7
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
8
+
9
+ # Logic Bug Exploits
10
+
11
+ ## Overview
12
+
13
+ Logic bugs are flaws in business logic that allow unintended behavior. Unlike pattern-based vulnerabilities (reentrancy, overflow), these require understanding the protocol's intended behavior to identify.
14
+
15
+ **Key insight:** Logic bugs are the hardest to find automatically and cause the largest losses.
16
+
17
+ ---
18
+
19
+ ## Attack Patterns
20
+
21
+ ### 1. Incorrect State Machine
22
+
23
+ ```solidity
24
+ // VULNERABLE: Can withdraw multiple times
25
+ enum Status { Pending, Approved, Withdrawn }
26
+ mapping(uint256 => Status) public requestStatus;
27
+
28
+ function withdraw(uint256 requestId) external {
29
+ require(requestStatus[requestId] == Status.Approved, "Not approved");
30
+ // Missing: requestStatus[requestId] = Status.Withdrawn;
31
+ payable(msg.sender).transfer(amounts[requestId]);
32
+ }
33
+ ```
34
+
35
+ ### 2. Order of Operations
36
+
37
+ ```solidity
38
+ // VULNERABLE: Update after external call
39
+ function redeem(uint256 shares) external {
40
+ uint256 assets = convertToAssets(shares);
41
+ asset.transfer(msg.sender, assets); // External call first
42
+ _burn(msg.sender, shares); // State update after
43
+ }
44
+
45
+ // SECURE: CEI pattern
46
+ function redeem(uint256 shares) external {
47
+ uint256 assets = convertToAssets(shares);
48
+ _burn(msg.sender, shares); // State update first
49
+ asset.transfer(msg.sender, assets); // External call after
50
+ }
51
+ ```
52
+
53
+ ### 3. Accounting Errors
54
+
55
+ ```solidity
56
+ // VULNERABLE: Doesn't account for existing balance
57
+ function deposit() external payable {
58
+ balances[msg.sender] = msg.value; // Overwrites, doesn't add!
59
+ }
60
+
61
+ // SECURE
62
+ function deposit() external payable {
63
+ balances[msg.sender] += msg.value;
64
+ }
65
+ ```
66
+
67
+ ### 4. Precision Loss
68
+
69
+ ```solidity
70
+ // VULNERABLE: Division before multiplication
71
+ function calculateReward(uint256 amount, uint256 rate) external pure returns (uint256) {
72
+ return amount / 1000 * rate; // Loses precision
73
+ }
74
+
75
+ // SECURE: Multiplication before division
76
+ function calculateReward(uint256 amount, uint256 rate) external pure returns (uint256) {
77
+ return amount * rate / 1000;
78
+ }
79
+ ```
80
+
81
+ ### 5. Edge Case Handling
82
+
83
+ ```solidity
84
+ // VULNERABLE: Division by zero when totalSupply is 0
85
+ function pricePerShare() external view returns (uint256) {
86
+ return totalAssets() / totalSupply(); // Reverts if no shares
87
+ }
88
+
89
+ // SECURE
90
+ function pricePerShare() external view returns (uint256) {
91
+ uint256 supply = totalSupply();
92
+ return supply == 0 ? 1e18 : totalAssets() * 1e18 / supply;
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Real Exploits
99
+
100
+ ### Nomad Bridge (Aug 2022) — $190M
101
+
102
+ **What happened:**
103
+ - Routine upgrade initialized `confirmAt` mapping with zero
104
+ - Zero was a valid "confirmed" state
105
+ - Anyone could submit fake messages as "already proven"
106
+ - Mass exploitation by hundreds of addresses
107
+
108
+ **Root cause:** Invalid initialization treated as valid state
109
+
110
+ ```solidity
111
+ // The bug: messages[_messageHash] = 0 was treated as confirmed
112
+ function process(bytes memory _message) public returns (bool _success) {
113
+ bytes32 _messageHash = keccak256(_message);
114
+ require(acceptableRoot(messages[_messageHash]), "not accepted");
115
+ // 0 passed acceptableRoot check!
116
+ }
117
+ ```
118
+
119
+ **Lesson:** Be explicit about valid states. Zero should not be a valid confirmed state.
120
+
121
+ ### Compound (Sept 2021) — $80M+
122
+
123
+ **What happened:**
124
+ - Upgrade introduced bug in reward distribution
125
+ - `comptroller.compAccrued()` returned wrong values
126
+ - Users could claim excessive COMP rewards
127
+
128
+ **Root cause:** Incorrect accounting in upgrade
129
+
130
+ **Lesson:** Test upgrades extensively. State transitions are dangerous.
131
+
132
+ ### Level Finance (May 2023) — $1.1M
133
+
134
+ **What happened:**
135
+ - Referral reward calculation bug
136
+ - Could claim referral rewards multiple times
137
+ - Self-referral exploit
138
+
139
+ **Root cause:** Missing state update after reward claim
140
+
141
+ ### Saddle Finance (Apr 2022) — $10M
142
+
143
+ **What happened:**
144
+ - Virtual price calculation bug in meta-pool
145
+ - Attacker manipulated LP token value
146
+ - Withdrew more than deposited
147
+
148
+ **Root cause:** Incorrect share calculation with rounding
149
+
150
+ ---
151
+
152
+ ## Detection Checklist
153
+
154
+ ### State Machine
155
+ - [ ] Are all state transitions explicitly handled?
156
+ - [ ] Can states be skipped or repeated?
157
+ - [ ] Is there a state that represents "completed" to prevent re-execution?
158
+ - [ ] Are default/zero values handled as invalid states?
159
+
160
+ ### Accounting
161
+ - [ ] Does deposit add to existing balance (not overwrite)?
162
+ - [ ] Does withdrawal subtract correctly?
163
+ - [ ] Are shares/assets conversions precise?
164
+ - [ ] Is totalSupply/totalAssets always consistent?
165
+
166
+ ### Math & Precision
167
+ - [ ] Is multiplication done before division?
168
+ - [ ] Are there division-by-zero possibilities?
169
+ - [ ] Is precision loss handled (rounding direction)?
170
+ - [ ] Are there overflow possibilities in intermediate calculations?
171
+
172
+ ### Edge Cases
173
+ - [ ] What happens with zero amounts?
174
+ - [ ] What happens with very small/large values?
175
+ - [ ] What happens on first deposit (empty pool)?
176
+ - [ ] What happens on last withdrawal (drain pool)?
177
+ - [ ] What happens with self-referral/self-liquidation?
178
+
179
+ ### Protocol-Specific
180
+ - [ ] Can rewards be claimed multiple times?
181
+ - [ ] Can positions be liquidated incorrectly?
182
+ - [ ] Are fees calculated and deducted correctly?
183
+ - [ ] Can users bypass intended restrictions?
184
+
185
+ ## Finding Logic Bugs
186
+
187
+ ### 1. Understand Intended Behavior
188
+ - Read docs, comments, specs
189
+ - What SHOULD happen?
190
+ - What are the invariants?
191
+
192
+ ### 2. Trace State Changes
193
+ - Follow every state variable modification
194
+ - Map out the state machine
195
+ - Find missing transitions
196
+
197
+ ### 3. Test Boundaries
198
+ - Zero values
199
+ - Max values
200
+ - First/last operations
201
+ - Empty states
202
+
203
+ ### 4. Question Assumptions
204
+ - "Users won't do X" — They will
205
+ - "This value can't be zero" — It can
206
+ - "This is called after Y" — Is it enforced?
207
+
208
+ ### 5. Think Like an Attacker
209
+ - What's the most valuable exploit?
210
+ - What if I could control X?
211
+ - What if I did operations out of order?
212
+
213
+ ---
214
+
215
+ ## Secure Patterns
216
+
217
+ ### Explicit State Machine
218
+
219
+ ```solidity
220
+ enum State { Created, Active, Completed, Cancelled }
221
+
222
+ modifier inState(State expected) {
223
+ require(state == expected, "Invalid state");
224
+ _;
225
+ }
226
+
227
+ function complete() external inState(State.Active) {
228
+ state = State.Completed; // Explicit transition
229
+ // Do completion logic
230
+ }
231
+ ```
232
+
233
+ ### Invariant Checks
234
+
235
+ ```solidity
236
+ function _checkInvariants() internal view {
237
+ assert(totalAssets >= totalLiabilities);
238
+ assert(totalShares == 0 || totalAssets > 0);
239
+ // Add protocol-specific invariants
240
+ }
241
+
242
+ function deposit(uint256 assets) external {
243
+ // ... deposit logic ...
244
+ _checkInvariants();
245
+ }
246
+ ```
247
+
248
+ ### CEI Pattern (Checks-Effects-Interactions)
249
+
250
+ ```solidity
251
+ function withdraw(uint256 amount) external {
252
+ // CHECKS
253
+ require(balances[msg.sender] >= amount, "Insufficient");
254
+
255
+ // EFFECTS
256
+ balances[msg.sender] -= amount;
257
+
258
+ // INTERACTIONS
259
+ payable(msg.sender).transfer(amount);
260
+ }
261
+ ```
262
+
263
+ ---
264
+
265
+ ## References
266
+
267
+ - [Nomad Hack Analysis (samczsun)](https://www.paradigm.xyz/2022/08/how-to-drain-almost-1-billion)
268
+ - [Compound Bug Post-Mortem](https://www.comp.xyz/t/compound-comptroller-bug-post-mortem/2284)
269
+ - [Trail of Bits: Not So Smart Contracts](https://github.com/crytic/not-so-smart-contracts)
270
+
271
+ ## Validation Heuristics (kadenzipfel)
272
+
273
+ ## Preconditions
274
+ - Contract uses `require()` statements for input or state validation
275
+ - The `require` condition is overly restrictive (rejects valid inputs) or too loose (accepts invalid inputs)
276
+ - OR: `require()` validates return values from external contracts whose behavior doesn't match assumptions
277
+
278
+ ## Vulnerable Pattern
279
+ ```solidity
280
+ // Overly restrictive: blocks legitimate use case
281
+ function withdraw(uint256 amount) external {
282
+ // Fails if user has exactly the required amount (should be >=)
283
+ require(balances[msg.sender] > amount, "insufficient");
284
+ balances[msg.sender] -= amount;
285
+ payable(msg.sender).transfer(amount);
286
+ }
287
+
288
+ // External contract assumption mismatch
289
+ function processPayment(IERC20 token, uint256 amount) external {
290
+ // Assumes transfer returns true, but some tokens don't return a value
291
+ // require reverts on tokens like USDT
292
+ require(token.transfer(msg.sender, amount), "failed");
293
+ }
294
+
295
+ // Missing error message
296
+ function setRate(uint256 rate) external {
297
+ require(rate > 0); // No error message — difficult to debug
298
+ }
299
+ ```
300
+
301
+ ## Detection Heuristics
302
+ 1. For each `require()`, verify the condition matches the documented business logic (e.g., `>` vs `>=`, `<` vs `<=`)
303
+ 2. Check if `require()` validates an external call's return value — verify the external contract actually returns what's expected
304
+ 3. Look for `require()` without error messages — while not a vulnerability, it makes debugging difficult
305
+ 4. Check if overly strict requirements can DoS critical paths (e.g., withdrawals, liquidations)
306
+ 5. Check chained contract calls: does an upstream contract provide inputs that could fail downstream `require` checks?
307
+
308
+ ## False Positives
309
+ - The `require` condition exactly matches the specification
310
+ - The strictness is intentional and documented
311
+ - The error message is omitted in a pre-0.8.4 contract for gas optimization (custom errors not yet available)
312
+
313
+ ## Remediation
314
+ - Verify each `require` condition against the specification: `>` vs `>=`, `<` vs `<=`
315
+ - Add descriptive error messages or use custom errors (Solidity >=0.8.4)
316
+ - For external call validations, use SafeERC20 or similar wrappers that handle non-standard return values
317
+ ```solidity
318
+ function withdraw(uint256 amount) external {
319
+ require(balances[msg.sender] >= amount, "insufficient balance");
320
+ balances[msg.sender] -= amount;
321
+ payable(msg.sender).transfer(amount);
322
+ }
323
+
324
+ // Solidity >=0.8.4: custom errors for gas efficiency
325
+ error InsufficientBalance(uint256 available, uint256 requested);
326
+
327
+ function withdrawV2(uint256 amount) external {
328
+ if (balances[msg.sender] < amount)
329
+ revert InsufficientBalance(balances[msg.sender], amount);
330
+ balances[msg.sender] -= amount;
331
+ payable(msg.sender).transfer(amount);
332
+ }
333
+ ```
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: missing-protection-signature-replay
3
+ description: - Contract verifies ECDSA signatures for authorization
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Missing Protection Against Signature Replay
8
+
9
+ ## Preconditions
10
+ - Contract verifies ECDSA signatures for authorization
11
+ - The signed message does not include a nonce, OR does not include the contract address, OR does not include the chain ID
12
+ - No mechanism tracks which signatures have been processed
13
+
14
+ ## Vulnerable Pattern
15
+ ```solidity
16
+ function executeWithSig(address to, uint256 amount, bytes memory sig) external {
17
+ // Missing: nonce, contract address, and chain ID in hash
18
+ // Same signature can be replayed on same contract, other contracts, or other chains
19
+ bytes32 hash = keccak256(abi.encodePacked(to, amount));
20
+ address signer = ECDSA.recover(hash, sig);
21
+ require(signer == authorizer, "invalid sig");
22
+
23
+ // No nonce tracking — same signature can be submitted repeatedly
24
+ _transfer(to, amount);
25
+ }
26
+ ```
27
+
28
+ ## Detection Heuristics
29
+ 1. Search for `ecrecover` or `ECDSA.recover` usage
30
+ 2. Examine what is included in the signed hash — check for:
31
+ - Nonce: is there a per-signer incrementing nonce? Flag if absent (enables same-contract replay)
32
+ - Contract address (`address(this)`): flag if absent (enables cross-contract replay)
33
+ - Chain ID (`block.chainid`): flag if absent (enables cross-chain replay)
34
+ 3. Check if processed signatures/hashes are tracked in a mapping to prevent reuse
35
+ 4. Check if EIP-712 domain separator is used (it includes contract address and chain ID automatically)
36
+ 5. Verify that the nonce is incremented BEFORE execution, not after (to prevent reentrancy-based replay)
37
+
38
+ ## False Positives
39
+ - EIP-712 domain separator is used with proper nonce tracking (covers address + chainId + nonce)
40
+ - The signed message includes all three: nonce, contract address, and chain ID
41
+ - The signature authorizes a one-time action that is inherently non-replayable (e.g., EIP-2612 permit with deadline and nonce)
42
+
43
+ ## Remediation
44
+ - Include nonce, `address(this)`, and `block.chainid` in the signed message hash
45
+ - Track processed nonces per signer in a mapping
46
+ - Use EIP-712 structured data signing with a domain separator
47
+ ```solidity
48
+ mapping(address => uint256) public nonces;
49
+
50
+ function executeWithSig(address to, uint256 amount, bytes memory sig) external {
51
+ uint256 nonce = nonces[msg.sender]++;
52
+ bytes32 hash = keccak256(abi.encodePacked(
53
+ to, amount, nonce, address(this), block.chainid
54
+ ));
55
+ bytes32 ethHash = ECDSA.toEthSignedMessageHash(hash);
56
+ address signer = ECDSA.recover(ethHash, sig);
57
+ require(signer == authorizer, "invalid sig");
58
+ _transfer(to, amount);
59
+ }
60
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: msgvalue-loop
3
+ description: - `msg.value` is referenced inside a loop (`for`, `while`) or in a function called multiple times within a single external call
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # msg.value Reuse in Loops
8
+
9
+ ## Preconditions
10
+ - `msg.value` is referenced inside a loop (`for`, `while`) or in a function called multiple times within a single external call
11
+ - The contract has an existing ETH balance or the logic assumes `msg.value` is "spent" per iteration
12
+
13
+ ## Vulnerable Pattern
14
+ ```solidity
15
+ function batchBuy(uint256[] calldata ids) external payable {
16
+ for (uint256 i = 0; i < ids.length; i++) {
17
+ // msg.value is the SAME on every iteration — it never decreases
18
+ // If price == 1 ETH and user sends 1 ETH, they can buy N items
19
+ require(msg.value >= price, "insufficient payment");
20
+ _mint(msg.sender, ids[i]);
21
+ }
22
+ // User paid 1 ETH but bought N items
23
+ }
24
+
25
+ // Payable multicall — same issue
26
+ function multicall(bytes[] calldata calls) external payable {
27
+ for (uint256 i = 0; i < calls.length; i++) {
28
+ // msg.value forwarded to each sub-call — reused each time
29
+ (bool s,) = address(this).delegatecall(calls[i]);
30
+ require(s);
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Detection Heuristics
36
+ 1. Search for `msg.value` usage inside `for`, `while`, or `do-while` loops
37
+ 2. Search for `msg.value` in functions that are called via `delegatecall` in a loop (multicall patterns)
38
+ 3. Check if `msg.value` is used in a `require` check inside a loop — passes on every iteration after a single payment
39
+ 4. Search for internal functions that reference `msg.value` and are called multiple times from a payable external function
40
+ 5. Check if the contract subtracts from a local tracking variable instead of relying on `msg.value` directly
41
+
42
+ ## False Positives
43
+ - The function tracks remaining value in a local variable and decrements it per iteration (e.g., `remaining -= price`)
44
+ - `msg.value` is only referenced once outside any loop
45
+ - The loop is guaranteed to execute exactly once
46
+ - The function validates total cost against `msg.value` before the loop (e.g., `require(msg.value == price * ids.length)`)
47
+
48
+ ## Remediation
49
+ - Track remaining ETH in a local variable and decrement per operation
50
+ - Validate total cost upfront: `require(msg.value == price * count)`
51
+ - In multicall patterns, ensure `msg.value` is consumed only once, or use a tracking variable
52
+ ```solidity
53
+ function batchBuy(uint256[] calldata ids) external payable {
54
+ uint256 totalCost = price * ids.length;
55
+ require(msg.value >= totalCost, "insufficient payment");
56
+
57
+ for (uint256 i = 0; i < ids.length; i++) {
58
+ _mint(msg.sender, ids[i]);
59
+ }
60
+
61
+ // Refund excess
62
+ if (msg.value > totalCost) {
63
+ payable(msg.sender).transfer(msg.value - totalCost);
64
+ }
65
+ }
66
+ ```
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: off-by-one
3
+ description: - Contract uses loops with boundary conditions, comparison operators at thresholds, or array index calculations
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Off-By-One Errors
8
+
9
+ ## Preconditions
10
+ - Contract uses loops with boundary conditions, comparison operators at thresholds, or array index calculations
11
+ - The boundary/comparison is off by exactly one from the intended behavior
12
+
13
+ ## Vulnerable Pattern
14
+ ```solidity
15
+ // Skips the last element
16
+ function processAll() external {
17
+ for (uint256 i = 0; i < users.length - 1; i++) {
18
+ // Should be i < users.length
19
+ // Last user is never processed
20
+ _distribute(users[i]);
21
+ }
22
+ }
23
+
24
+ // Off-by-one in threshold check
25
+ function liquidate(uint256 ratio) external {
26
+ // Should be ratio < MIN_RATIO (liquidate when below)
27
+ // Using <= means accounts AT the minimum are also liquidated
28
+ require(ratio <= MIN_RATIO, "healthy");
29
+ _liquidate();
30
+ }
31
+
32
+ // Out-of-bounds access
33
+ function getLastUser() external view returns (address) {
34
+ return users[users.length]; // Should be users.length - 1
35
+ }
36
+ ```
37
+
38
+ ## Detection Heuristics
39
+ 1. For every loop, check the boundary condition: `< length` vs `<= length` vs `< length - 1`
40
+ 2. `< length - 1` skips the last element — flag unless intentional
41
+ 3. `<= length` goes out of bounds on array access — flag always
42
+ 4. For comparison operators at thresholds (`>`, `>=`, `<`, `<=`), verify the boundary matches the specification (e.g., "greater than" vs "greater than or equal to")
43
+ 5. Check pagination logic for fence-post errors: does the first page start at 0 or 1? Does the last batch include the final element?
44
+ 6. Look for `length - 1` on arrays that could be empty — this underflows to `type(uint256).max` in unchecked contexts or reverts in checked contexts
45
+
46
+ ## False Positives
47
+ - The boundary is intentionally exclusive (e.g., `< length - 1` to skip the sentinel/last element by design)
48
+ - The comparison operator matches the documented specification exactly
49
+ - The code is iterating over pairs (`i < length - 1` to compare `arr[i]` with `arr[i+1]`)
50
+
51
+ ## Remediation
52
+ - Verify each boundary condition against the specification or documented intent
53
+ - Be explicit about inclusive vs exclusive bounds in comments
54
+ - Guard against empty array underflow: check `length > 0` before `length - 1`
55
+ ```solidity
56
+ function processAll() external {
57
+ for (uint256 i = 0; i < users.length; i++) {
58
+ _distribute(users[i]);
59
+ }
60
+ }
61
+
62
+ // Explicit about boundary semantics
63
+ function liquidate(uint256 ratio) external {
64
+ require(ratio < MIN_RATIO, "healthy"); // Strictly below = liquidatable
65
+ _liquidate();
66
+ }
67
+ ```