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.
- package/AGENTS.md +37 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/package.json +43 -0
- package/skills/INVENTORY.md +79 -0
- package/skills/README.md +56 -0
- package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +424 -0
- package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +157 -0
- package/skills/checklists/cyfrin-defi-core/SKILL.md +373 -0
- package/skills/checklists/cyfrin-defi-integrations/SKILL.md +412 -0
- package/skills/checklists/cyfrin-gas/SKILL.md +55 -0
- package/skills/checklists/general-audit/SKILL.md +433 -0
- package/skills/methodology/audit-workflow/SKILL.md +129 -0
- package/skills/methodology/report-template/SKILL.md +190 -0
- package/skills/methodology/severity-classification/SKILL.md +179 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +229 -0
- package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +317 -0
- package/skills/protocol-patterns/dao-governance/SKILL.md +281 -0
- package/skills/protocol-patterns/lending-borrowing/SKILL.md +221 -0
- package/skills/protocol-patterns/staking-vesting/SKILL.md +247 -0
- package/skills/references/exploit-reference/SKILL.md +259 -0
- package/skills/references/smartbugs-examples/SKILL.md +296 -0
- package/skills/vulnerability-patterns/access-control/SKILL.md +298 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +59 -0
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +59 -0
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +61 -0
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +55 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +62 -0
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +60 -0
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +59 -0
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +72 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +249 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +51 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +52 -0
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +61 -0
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +60 -0
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +59 -0
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +61 -0
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +61 -0
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +333 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +60 -0
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +66 -0
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +67 -0
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +252 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +65 -0
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +61 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +266 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +72 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +59 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +63 -0
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +52 -0
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +65 -0
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +61 -0
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +63 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +56 -0
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +80 -0
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +69 -0
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +70 -0
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +81 -0
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +77 -0
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +294 -0
- package/src/agents/argus-prompt.ts +407 -0
- package/src/agents/pythia-prompt.ts +134 -0
- package/src/agents/scribe-prompt.ts +87 -0
- package/src/agents/sentinel-prompt.ts +133 -0
- package/src/cli/cli-program.ts +67 -0
- package/src/cli/commands/doctor.ts +83 -0
- package/src/cli/commands/init.ts +46 -0
- package/src/cli/commands/install.ts +55 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/tui-prompts.ts +75 -0
- package/src/cli/types.ts +9 -0
- package/src/config/index.ts +3 -0
- package/src/config/loader.ts +36 -0
- package/src/config/schema.ts +82 -0
- package/src/config/types.ts +4 -0
- package/src/constants/defaults.ts +6 -0
- package/src/create-hooks.ts +84 -0
- package/src/create-managers.ts +26 -0
- package/src/create-tools.ts +30 -0
- package/src/features/audit-enforcer/audit-enforcer.ts +34 -0
- package/src/features/audit-enforcer/index.ts +1 -0
- package/src/features/background-agent/background-manager.ts +200 -0
- package/src/features/background-agent/index.ts +1 -0
- package/src/features/context-monitor/context-monitor.ts +48 -0
- package/src/features/context-monitor/index.ts +4 -0
- package/src/features/context-monitor/tool-output-truncator.ts +17 -0
- package/src/features/error-recovery/index.ts +2 -0
- package/src/features/error-recovery/session-recovery.ts +27 -0
- package/src/features/error-recovery/tool-error-recovery.ts +35 -0
- package/src/features/index.ts +5 -0
- package/src/features/persistent-state/audit-state-manager.ts +121 -0
- package/src/features/persistent-state/index.ts +1 -0
- package/src/hooks/compaction-hook.ts +50 -0
- package/src/hooks/config-handler.ts +116 -0
- package/src/hooks/event-hook-v2.ts +93 -0
- package/src/hooks/event-hook.ts +74 -0
- package/src/hooks/hook-system.ts +9 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/knowledge-sync-hook.ts +57 -0
- package/src/hooks/safe-create-hook.ts +15 -0
- package/src/hooks/system-prompt-hook.ts +126 -0
- package/src/hooks/tool-tracking-hook.ts +234 -0
- package/src/hooks/types.ts +16 -0
- package/src/index.ts +36 -0
- package/src/knowledge/scvd-client.ts +242 -0
- package/src/knowledge/scvd-index.ts +183 -0
- package/src/knowledge/scvd-sync.ts +85 -0
- package/src/managers/index.ts +1 -0
- package/src/managers/types.ts +85 -0
- package/src/plugin-interface.ts +38 -0
- package/src/shared/binary-utils.ts +63 -0
- package/src/shared/deep-merge.ts +71 -0
- package/src/shared/file-utils.ts +56 -0
- package/src/shared/index.ts +5 -0
- package/src/shared/jsonc-parser.ts +39 -0
- package/src/shared/logger.ts +36 -0
- package/src/state/audit-state.ts +27 -0
- package/src/state/finding-store.ts +126 -0
- package/src/state/plugin-state.ts +14 -0
- package/src/state/types.ts +61 -0
- package/src/tools/contract-analyzer-tool.ts +184 -0
- package/src/tools/forge-fuzz-tool.ts +311 -0
- package/src/tools/forge-test-tool.ts +397 -0
- package/src/tools/pattern-checker-tool.ts +337 -0
- package/src/tools/report-generator-tool.ts +308 -0
- package/src/tools/slither-tool.ts +465 -0
- package/src/tools/solodit-search-tool.ts +131 -0
- package/src/tools/sync-knowledge-tool.ts +116 -0
- package/src/utils/project-detector.ts +133 -0
- package/src/utils/solidity-parser.ts +174 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bridges-cross-chain
|
|
3
|
+
description: Cross-chain bridge security guidance for message verification, replay prevention, and validator risk.
|
|
4
|
+
---
|
|
5
|
+
<!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
|
|
6
|
+
|
|
7
|
+
# Cross-Chain Bridge Security Guide
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Bridges transfer assets/messages between blockchains. They are the highest-value targets in DeFi — $2B+ stolen from bridges. Core security concerns: message verification, validator security, and replay attacks.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
19
|
+
│ Chain A │ │ Chain B │
|
|
20
|
+
│ │ │ │
|
|
21
|
+
│ ┌───────────┐ │ Message │ ┌───────────┐ │
|
|
22
|
+
│ │ Bridge │ │ ──────────────────► │ │ Bridge │ │
|
|
23
|
+
│ │ Contract │ │ │ │ Contract │ │
|
|
24
|
+
│ └───────────┘ │ │ └───────────┘ │
|
|
25
|
+
│ │ │ │ │ │
|
|
26
|
+
│ Lock/Burn │ ┌──────────────┐ │ Mint/Unlock │
|
|
27
|
+
│ Tokens │ │ Validators │ │ Tokens │
|
|
28
|
+
│ │ │ / Relayers │ │ │
|
|
29
|
+
└─────────────────┘ └──────────────┘ └─────────────────┘
|
|
30
|
+
│
|
|
31
|
+
Sign attestations
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Critical Security Areas
|
|
37
|
+
|
|
38
|
+
### 1. Message Verification
|
|
39
|
+
|
|
40
|
+
**Attack Vectors:**
|
|
41
|
+
- Forged signatures
|
|
42
|
+
- Invalid merkle proofs
|
|
43
|
+
- Malformed message data
|
|
44
|
+
|
|
45
|
+
**Checklist:**
|
|
46
|
+
- [ ] Is signature verification correct for all edge cases?
|
|
47
|
+
- [ ] Are all required fields validated?
|
|
48
|
+
- [ ] Is the source chain verified?
|
|
49
|
+
- [ ] Is the source contract verified?
|
|
50
|
+
- [ ] Are merkle proofs validated correctly?
|
|
51
|
+
|
|
52
|
+
```solidity
|
|
53
|
+
// VULNERABLE: Incomplete verification
|
|
54
|
+
function processMessage(
|
|
55
|
+
bytes32 messageHash,
|
|
56
|
+
bytes[] calldata signatures
|
|
57
|
+
) external {
|
|
58
|
+
uint256 validSigs;
|
|
59
|
+
for (uint i; i < signatures.length; i++) {
|
|
60
|
+
address signer = recoverSigner(messageHash, signatures[i]);
|
|
61
|
+
if (isValidator[signer]) validSigs++;
|
|
62
|
+
}
|
|
63
|
+
require(validSigs >= threshold, "Not enough sigs");
|
|
64
|
+
// Missing: Check for duplicate signers!
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// SECURE: Track used signatures
|
|
68
|
+
function processMessage(
|
|
69
|
+
bytes32 messageHash,
|
|
70
|
+
bytes[] calldata signatures
|
|
71
|
+
) external {
|
|
72
|
+
uint256 validSigs;
|
|
73
|
+
address lastSigner;
|
|
74
|
+
|
|
75
|
+
for (uint i; i < signatures.length; i++) {
|
|
76
|
+
address signer = recoverSigner(messageHash, signatures[i]);
|
|
77
|
+
require(signer > lastSigner, "Invalid sig order"); // Prevents duplicates
|
|
78
|
+
if (isValidator[signer]) validSigs++;
|
|
79
|
+
lastSigner = signer;
|
|
80
|
+
}
|
|
81
|
+
require(validSigs >= threshold, "Not enough sigs");
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Replay Protection
|
|
86
|
+
|
|
87
|
+
**Attack Vectors:**
|
|
88
|
+
- Replay same message multiple times
|
|
89
|
+
- Replay across chains (chainId not included)
|
|
90
|
+
- Replay after upgrade
|
|
91
|
+
|
|
92
|
+
**Checklist:**
|
|
93
|
+
- [ ] Is each message marked as processed?
|
|
94
|
+
- [ ] Is chainId included in message hash?
|
|
95
|
+
- [ ] Is nonce/sequence number enforced?
|
|
96
|
+
- [ ] Is contract address included in hash?
|
|
97
|
+
|
|
98
|
+
```solidity
|
|
99
|
+
// VULNERABLE: No replay protection
|
|
100
|
+
function executeMessage(bytes calldata message, bytes calldata proof) external {
|
|
101
|
+
require(verifyProof(message, proof), "Invalid proof");
|
|
102
|
+
_execute(message); // Can be called again with same message!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// SECURE: Mark as processed
|
|
106
|
+
mapping(bytes32 => bool) public processedMessages;
|
|
107
|
+
|
|
108
|
+
function executeMessage(bytes calldata message, bytes calldata proof) external {
|
|
109
|
+
bytes32 messageHash = keccak256(message);
|
|
110
|
+
require(!processedMessages[messageHash], "Already processed");
|
|
111
|
+
require(verifyProof(message, proof), "Invalid proof");
|
|
112
|
+
|
|
113
|
+
processedMessages[messageHash] = true;
|
|
114
|
+
_execute(message);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. Validator/Guardian Security
|
|
119
|
+
|
|
120
|
+
**Attack Vectors:**
|
|
121
|
+
- Compromised validator keys
|
|
122
|
+
- Collusion attack (threshold too low)
|
|
123
|
+
- Single point of failure
|
|
124
|
+
|
|
125
|
+
**Checklist:**
|
|
126
|
+
- [ ] Is validator set distributed (different orgs, geographies)?
|
|
127
|
+
- [ ] Is threshold sufficient (e.g., 5/9 minimum)?
|
|
128
|
+
- [ ] Is there key rotation mechanism?
|
|
129
|
+
- [ ] Are validators timelock-protected for removal?
|
|
130
|
+
|
|
131
|
+
### 4. Token Accounting
|
|
132
|
+
|
|
133
|
+
**Attack Vectors:**
|
|
134
|
+
- Mint more tokens than locked
|
|
135
|
+
- Unlock without corresponding lock
|
|
136
|
+
- Fee accounting errors
|
|
137
|
+
|
|
138
|
+
**Checklist:**
|
|
139
|
+
- [ ] Can bridge mint more than total locked?
|
|
140
|
+
- [ ] Is there 1:1 backing for wrapped tokens?
|
|
141
|
+
- [ ] Are fees handled correctly?
|
|
142
|
+
- [ ] Is there a way to pause minting?
|
|
143
|
+
|
|
144
|
+
```solidity
|
|
145
|
+
// Ideal invariant
|
|
146
|
+
assert(wrappedTokenSupply <= originalTokenLockedAmount);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 5. Upgrade Security
|
|
150
|
+
|
|
151
|
+
**Attack Vectors:**
|
|
152
|
+
- Malicious upgrade bypassing governance
|
|
153
|
+
- Storage collision on upgrade
|
|
154
|
+
- Uninitialized implementation
|
|
155
|
+
|
|
156
|
+
**Checklist:**
|
|
157
|
+
- [ ] Is upgrade path protected by timelock?
|
|
158
|
+
- [ ] Is implementation initializer protected?
|
|
159
|
+
- [ ] Are storage slots carefully managed?
|
|
160
|
+
- [ ] Is there emergency pause?
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Real Exploits
|
|
165
|
+
|
|
166
|
+
### Ronin Bridge (Mar 2022) — $625M
|
|
167
|
+
|
|
168
|
+
**What happened:**
|
|
169
|
+
- Attackers compromised 5 of 9 validator keys
|
|
170
|
+
- Signed fraudulent withdrawal messages
|
|
171
|
+
- Drained ETH and USDC
|
|
172
|
+
|
|
173
|
+
**Root cause:** Insufficient validator distribution + social engineering
|
|
174
|
+
|
|
175
|
+
### Wormhole (Feb 2022) — $326M
|
|
176
|
+
|
|
177
|
+
**What happened:**
|
|
178
|
+
- Attacker exploited Solana signature verification bug
|
|
179
|
+
- Forged guardian signatures
|
|
180
|
+
- Minted 120k wETH without deposit
|
|
181
|
+
|
|
182
|
+
**Root cause:** Invalid signature verification on Solana side
|
|
183
|
+
|
|
184
|
+
### Nomad (Aug 2022) — $190M
|
|
185
|
+
|
|
186
|
+
**What happened:**
|
|
187
|
+
- Routine upgrade set trusted root to 0x00
|
|
188
|
+
- Zero was treated as "valid" proof
|
|
189
|
+
- Anyone could submit fake messages as proven
|
|
190
|
+
|
|
191
|
+
**Root cause:** Zero initialization treated as valid state
|
|
192
|
+
|
|
193
|
+
```solidity
|
|
194
|
+
// The Nomad bug pattern
|
|
195
|
+
mapping(bytes32 => uint256) public messages;
|
|
196
|
+
|
|
197
|
+
function process(bytes memory _message) public {
|
|
198
|
+
bytes32 _messageHash = keccak256(_message);
|
|
199
|
+
// messages[_messageHash] == 0 was treated as confirmed!
|
|
200
|
+
require(acceptableRoot(messages[_messageHash]), "not accepted");
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Poly Network (Aug 2021) — $611M
|
|
205
|
+
|
|
206
|
+
**What happened:**
|
|
207
|
+
- Attacker exploited cross-chain contract call
|
|
208
|
+
- Changed keeper to attacker address
|
|
209
|
+
- Drained all chains
|
|
210
|
+
|
|
211
|
+
**Root cause:** Insufficient validation of cross-chain call targets
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Bridge Types & Specific Risks
|
|
216
|
+
|
|
217
|
+
### Lock & Mint
|
|
218
|
+
- Risk: Minting without lock
|
|
219
|
+
- Check: Mint events should have corresponding lock
|
|
220
|
+
|
|
221
|
+
### Burn & Unlock
|
|
222
|
+
- Risk: Unlocking without burn
|
|
223
|
+
- Check: Burn verification before unlock
|
|
224
|
+
|
|
225
|
+
### Liquidity Networks (Hop, Across)
|
|
226
|
+
- Risk: LP manipulation
|
|
227
|
+
- Check: Bonder collateral, fees
|
|
228
|
+
|
|
229
|
+
### Optimistic Bridges (Nomad-style)
|
|
230
|
+
- Risk: Challenge period bypass
|
|
231
|
+
- Check: Fraud proof implementation
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Testing Checklist
|
|
236
|
+
|
|
237
|
+
### Unit Tests
|
|
238
|
+
- [ ] Message encoding/decoding
|
|
239
|
+
- [ ] Signature verification
|
|
240
|
+
- [ ] Replay protection
|
|
241
|
+
- [ ] Access control on critical functions
|
|
242
|
+
|
|
243
|
+
### Integration Tests
|
|
244
|
+
- [ ] Full message flow (both directions)
|
|
245
|
+
- [ ] Multiple concurrent messages
|
|
246
|
+
- [ ] Fee handling
|
|
247
|
+
|
|
248
|
+
### Attack Tests
|
|
249
|
+
- [ ] Message replay attempt
|
|
250
|
+
- [ ] Forged signature
|
|
251
|
+
- [ ] Duplicate signer attack
|
|
252
|
+
- [ ] Cross-chain replay
|
|
253
|
+
|
|
254
|
+
### Invariant Tests
|
|
255
|
+
- [ ] Wrapped supply ≤ locked amount
|
|
256
|
+
- [ ] Each message processed exactly once
|
|
257
|
+
- [ ] Validator count ≥ threshold
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Secure Patterns
|
|
262
|
+
|
|
263
|
+
### Message Structure
|
|
264
|
+
|
|
265
|
+
```solidity
|
|
266
|
+
struct Message {
|
|
267
|
+
uint256 srcChainId;
|
|
268
|
+
uint256 dstChainId;
|
|
269
|
+
address srcContract;
|
|
270
|
+
address dstContract;
|
|
271
|
+
uint256 nonce;
|
|
272
|
+
bytes payload;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function hashMessage(Message memory m) internal pure returns (bytes32) {
|
|
276
|
+
return keccak256(abi.encode(
|
|
277
|
+
m.srcChainId,
|
|
278
|
+
m.dstChainId,
|
|
279
|
+
m.srcContract,
|
|
280
|
+
m.dstContract,
|
|
281
|
+
m.nonce,
|
|
282
|
+
keccak256(m.payload)
|
|
283
|
+
));
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Ordered Signatures (Prevents Duplicates)
|
|
288
|
+
|
|
289
|
+
```solidity
|
|
290
|
+
function verifySignatures(
|
|
291
|
+
bytes32 hash,
|
|
292
|
+
bytes[] calldata sigs
|
|
293
|
+
) internal view returns (bool) {
|
|
294
|
+
address lastSigner = address(0);
|
|
295
|
+
uint256 valid;
|
|
296
|
+
|
|
297
|
+
for (uint i; i < sigs.length; i++) {
|
|
298
|
+
address signer = ECDSA.recover(hash, sigs[i]);
|
|
299
|
+
require(signer > lastSigner, "Signatures not ordered");
|
|
300
|
+
|
|
301
|
+
if (guardians[signer]) valid++;
|
|
302
|
+
lastSigner = signer;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return valid >= threshold;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## References
|
|
312
|
+
|
|
313
|
+
- [L2Beat Bridge Risk Framework](https://l2beat.com/bridges/risk)
|
|
314
|
+
- [Rekt Bridge Leaderboard](https://rekt.news/leaderboard/)
|
|
315
|
+
- [LayerZero Security Model](https://layerzero.gitbook.io/docs/faq/security)
|
|
316
|
+
- [Wormhole Post-Mortem](https://wormholecrypto.medium.com/wormhole-incident-report-02-02-22-ad9b8f21eec6)
|
|
317
|
+
- [Nomad Post-Mortem](https://medium.com/nomad-xyz-blog/nomad-bridge-hack-root-cause-analysis-875ad2e5aacd)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dao-governance
|
|
3
|
+
description: Governance security patterns for voting, timelocks, proposal execution, and quorum safety.
|
|
4
|
+
---
|
|
5
|
+
<!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
|
|
6
|
+
|
|
7
|
+
# Governance Protocol Security Guide
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Governance protocols enable token-based decision making. Core security concerns: voting manipulation, flash loan attacks, proposal execution, and timelock bypasses.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ GOVERNANCE SYSTEM │
|
|
20
|
+
├─────────────────────────────────────────────────────────────┤
|
|
21
|
+
│ │
|
|
22
|
+
│ PROPOSE → VOTE → EXECUTE │
|
|
23
|
+
│ ──────── ──── ─────── │
|
|
24
|
+
│ Create proposal Cast votes After timelock │
|
|
25
|
+
│ Meet threshold Snapshot power Run actions │
|
|
26
|
+
│ │
|
|
27
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
28
|
+
│ │ TIMELOCK │ │
|
|
29
|
+
│ │ Queue → Wait (e.g., 2 days) → Execute │ │
|
|
30
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
31
|
+
│ │
|
|
32
|
+
└─────────────────────────────────────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Critical Security Areas
|
|
38
|
+
|
|
39
|
+
### 1. Flash Loan Voting
|
|
40
|
+
|
|
41
|
+
**Attack Vectors:**
|
|
42
|
+
- Borrow tokens, vote, return in same block
|
|
43
|
+
- Acquire governance majority temporarily
|
|
44
|
+
|
|
45
|
+
**Checklist:**
|
|
46
|
+
- [ ] Is voting power snapshot-based (past block)?
|
|
47
|
+
- [ ] Is there delay between proposal and voting start?
|
|
48
|
+
- [ ] Can voting power be acquired and used in same transaction?
|
|
49
|
+
|
|
50
|
+
```solidity
|
|
51
|
+
// VULNERABLE: Current balance for voting
|
|
52
|
+
function vote(uint256 proposalId, bool support) external {
|
|
53
|
+
uint256 votes = token.balanceOf(msg.sender); // Flashloanable!
|
|
54
|
+
_castVote(proposalId, msg.sender, support, votes);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// SECURE: Historical balance (ERC20Votes)
|
|
58
|
+
function vote(uint256 proposalId, bool support) external {
|
|
59
|
+
uint256 votes = token.getPastVotes(
|
|
60
|
+
msg.sender,
|
|
61
|
+
proposals[proposalId].snapshot // Block when proposal created
|
|
62
|
+
);
|
|
63
|
+
_castVote(proposalId, msg.sender, support, votes);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Proposal Execution
|
|
68
|
+
|
|
69
|
+
**Attack Vectors:**
|
|
70
|
+
- Malicious calldata
|
|
71
|
+
- Reentrancy during execution
|
|
72
|
+
- State manipulation between queue and execute
|
|
73
|
+
|
|
74
|
+
**Checklist:**
|
|
75
|
+
- [ ] Is there a timelock between queue and execute?
|
|
76
|
+
- [ ] Are proposal actions validated?
|
|
77
|
+
- [ ] Is there reentrancy protection?
|
|
78
|
+
- [ ] Can proposal be executed multiple times?
|
|
79
|
+
|
|
80
|
+
```solidity
|
|
81
|
+
// VULNERABLE: No execution state check
|
|
82
|
+
function execute(uint256 proposalId) external {
|
|
83
|
+
Proposal storage p = proposals[proposalId];
|
|
84
|
+
require(p.state == ProposalState.Succeeded, "Not succeeded");
|
|
85
|
+
// Missing: p.state = ProposalState.Executed;
|
|
86
|
+
|
|
87
|
+
for (uint256 i; i < p.targets.length; i++) {
|
|
88
|
+
p.targets[i].call(p.calldatas[i]);
|
|
89
|
+
}
|
|
90
|
+
// Can be called again!
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 3. Quorum & Threshold Manipulation
|
|
95
|
+
|
|
96
|
+
**Attack Vectors:**
|
|
97
|
+
- Lowering quorum via governance
|
|
98
|
+
- Manipulating total supply for quorum calculation
|
|
99
|
+
- Emergency actions without proper threshold
|
|
100
|
+
|
|
101
|
+
**Checklist:**
|
|
102
|
+
- [ ] Is quorum based on total supply or snapshot?
|
|
103
|
+
- [ ] Can quorum/threshold be changed to dangerous levels?
|
|
104
|
+
- [ ] Are there bounds on governance parameters?
|
|
105
|
+
- [ ] Is total supply snapshot-based?
|
|
106
|
+
|
|
107
|
+
```solidity
|
|
108
|
+
// VULNERABLE: Quorum based on current supply
|
|
109
|
+
function quorum() public view returns (uint256) {
|
|
110
|
+
return token.totalSupply() * 4 / 100; // Can be manipulated
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// SECURE: Quorum at snapshot
|
|
114
|
+
function quorum(uint256 blockNumber) public view returns (uint256) {
|
|
115
|
+
return token.getPastTotalSupply(blockNumber) * 4 / 100;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. Timelock Security
|
|
120
|
+
|
|
121
|
+
**Attack Vectors:**
|
|
122
|
+
- Bypassing timelock via emergency functions
|
|
123
|
+
- Timelock too short for community response
|
|
124
|
+
- Admin can cancel queued proposals
|
|
125
|
+
|
|
126
|
+
**Checklist:**
|
|
127
|
+
- [ ] Is timelock long enough (24h minimum, 48h+ recommended)?
|
|
128
|
+
- [ ] Are emergency bypasses properly restricted?
|
|
129
|
+
- [ ] Can queued proposals be canceled maliciously?
|
|
130
|
+
- [ ] Is there grace period after timelock?
|
|
131
|
+
|
|
132
|
+
```solidity
|
|
133
|
+
uint256 public constant MINIMUM_DELAY = 2 days;
|
|
134
|
+
uint256 public constant MAXIMUM_DELAY = 30 days;
|
|
135
|
+
uint256 public constant GRACE_PERIOD = 14 days;
|
|
136
|
+
|
|
137
|
+
function setDelay(uint256 delay) external {
|
|
138
|
+
require(msg.sender == address(this), "Only self");
|
|
139
|
+
require(delay >= MINIMUM_DELAY, "Too short");
|
|
140
|
+
require(delay <= MAXIMUM_DELAY, "Too long");
|
|
141
|
+
delay_ = delay;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 5. Delegation & Voting Power
|
|
146
|
+
|
|
147
|
+
**Attack Vectors:**
|
|
148
|
+
- Double voting via delegation
|
|
149
|
+
- Delegation to zero address
|
|
150
|
+
- Vote power not updating on transfer
|
|
151
|
+
|
|
152
|
+
**Checklist:**
|
|
153
|
+
- [ ] Is delegation handled correctly (no double counting)?
|
|
154
|
+
- [ ] Does voting power update on token transfer?
|
|
155
|
+
- [ ] Can users delegate to themselves?
|
|
156
|
+
- [ ] Is there delegation chain/depth limit?
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Common Vulnerabilities
|
|
161
|
+
|
|
162
|
+
### Beanstalk Attack Pattern
|
|
163
|
+
|
|
164
|
+
```solidity
|
|
165
|
+
// Attack flow:
|
|
166
|
+
// 1. Flash loan governance tokens
|
|
167
|
+
// 2. Create proposal (if allowed in same tx)
|
|
168
|
+
// 3. Vote with flash-loaned tokens
|
|
169
|
+
// 4. Execute proposal immediately (no timelock)
|
|
170
|
+
// 5. Proposal drains funds
|
|
171
|
+
// 6. Return flash loan
|
|
172
|
+
|
|
173
|
+
// ALL of these should fail:
|
|
174
|
+
// - Snapshot should be from past block
|
|
175
|
+
// - Voting delay should prevent same-block voting
|
|
176
|
+
// - Timelock should delay execution
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Proposal Front-Running
|
|
180
|
+
|
|
181
|
+
```solidity
|
|
182
|
+
// VULNERABLE: Proposal can be front-run
|
|
183
|
+
function propose(
|
|
184
|
+
address[] memory targets,
|
|
185
|
+
bytes[] memory calldatas,
|
|
186
|
+
string memory description
|
|
187
|
+
) public returns (uint256) {
|
|
188
|
+
uint256 proposalId = hashProposal(targets, calldatas, keccak256(bytes(description)));
|
|
189
|
+
// Attacker can front-run with same proposal, different description
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Unsafe Delegatecall in Execution
|
|
194
|
+
|
|
195
|
+
```solidity
|
|
196
|
+
// VULNERABLE: delegatecall in governor
|
|
197
|
+
function execute(...) external {
|
|
198
|
+
for (uint i; i < targets.length; i++) {
|
|
199
|
+
(bool success,) = targets[i].delegatecall(calldatas[i]);
|
|
200
|
+
// delegatecall in governance = CRITICAL RISK
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Secure Patterns
|
|
208
|
+
|
|
209
|
+
### OpenZeppelin Governor
|
|
210
|
+
|
|
211
|
+
```solidity
|
|
212
|
+
import "@openzeppelin/contracts/governance/Governor.sol";
|
|
213
|
+
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
|
|
214
|
+
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
|
|
215
|
+
|
|
216
|
+
contract SecureGovernor is Governor, GovernorVotes, GovernorTimelockControl {
|
|
217
|
+
function votingDelay() public pure override returns (uint256) {
|
|
218
|
+
return 1 days; // Time before voting starts
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function votingPeriod() public pure override returns (uint256) {
|
|
222
|
+
return 1 weeks; // Voting duration
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Vote Escrow (veToken)
|
|
228
|
+
|
|
229
|
+
```solidity
|
|
230
|
+
// Lock tokens for voting power (Curve model)
|
|
231
|
+
// Longer lock = more voting power
|
|
232
|
+
// Prevents flash loan attacks by requiring locked tokens
|
|
233
|
+
|
|
234
|
+
struct LockedBalance {
|
|
235
|
+
uint256 amount;
|
|
236
|
+
uint256 unlockTime;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function votingPower(address user) public view returns (uint256) {
|
|
240
|
+
LockedBalance memory lock = locked[user];
|
|
241
|
+
if (lock.unlockTime <= block.timestamp) return 0;
|
|
242
|
+
|
|
243
|
+
uint256 timeLeft = lock.unlockTime - block.timestamp;
|
|
244
|
+
return lock.amount * timeLeft / MAX_LOCK_TIME;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Testing Checklist
|
|
251
|
+
|
|
252
|
+
### Unit Tests
|
|
253
|
+
- [ ] Proposal creation with correct threshold
|
|
254
|
+
- [ ] Voting with snapshot balances
|
|
255
|
+
- [ ] Quorum calculation
|
|
256
|
+
- [ ] Execution after timelock
|
|
257
|
+
|
|
258
|
+
### Integration Tests
|
|
259
|
+
- [ ] Full proposal lifecycle
|
|
260
|
+
- [ ] Delegation and vote casting
|
|
261
|
+
- [ ] Multiple proposals concurrent
|
|
262
|
+
|
|
263
|
+
### Attack Tests
|
|
264
|
+
- [ ] Flash loan voting attempt
|
|
265
|
+
- [ ] Same-block propose + vote
|
|
266
|
+
- [ ] Proposal re-execution attempt
|
|
267
|
+
- [ ] Quorum manipulation
|
|
268
|
+
|
|
269
|
+
### Invariant Tests
|
|
270
|
+
- [ ] Total votes ≤ total voting power at snapshot
|
|
271
|
+
- [ ] Executed proposals can't re-execute
|
|
272
|
+
- [ ] Timelock always enforced
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## References
|
|
277
|
+
|
|
278
|
+
- [OpenZeppelin Governor](https://docs.openzeppelin.com/contracts/governance)
|
|
279
|
+
- [Compound Governor Bravo](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorBravoDelegate.sol)
|
|
280
|
+
- [Beanstalk Exploit Analysis](https://rekt.news/beanstalk-rekt/)
|
|
281
|
+
- [Nouns DAO Fork](https://github.com/nounsDAO/nouns-monorepo)
|