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,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lending-borrowing
|
|
3
|
+
description: Security review framework for lending and borrowing systems including liquidations and accounting.
|
|
4
|
+
---
|
|
5
|
+
<!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
|
|
6
|
+
|
|
7
|
+
# Lending Protocol Security Guide
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Lending protocols (Aave, Compound, Morpho) enable collateralized borrowing. Core security concerns: liquidation logic, interest accrual, oracle reliance, and collateral management.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ LENDING PROTOCOL │
|
|
20
|
+
├─────────────────────────────────────────────────────────────┤
|
|
21
|
+
│ SUPPLY │ BORROW │ LIQUIDATE │
|
|
22
|
+
│ ───────── │ ───────── │ ───────── │
|
|
23
|
+
│ Deposit asset │ Lock collateral │ Seize collateral │
|
|
24
|
+
│ Receive shares │ Receive asset │ Repay debt │
|
|
25
|
+
│ Earn interest │ Pay interest │ Get bonus │
|
|
26
|
+
├─────────────────────────────────────────────────────────────┤
|
|
27
|
+
│ RISK ENGINE │
|
|
28
|
+
│ Health Factor = Collateral Value / Borrowed Value │
|
|
29
|
+
│ If HF < 1: Position is liquidatable │
|
|
30
|
+
└─────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Critical Security Areas
|
|
36
|
+
|
|
37
|
+
### 1. Liquidation Logic
|
|
38
|
+
|
|
39
|
+
**Attack Vectors:**
|
|
40
|
+
- Self-liquidation for profit
|
|
41
|
+
- Liquidation front-running
|
|
42
|
+
- Incorrect health factor calculation
|
|
43
|
+
- Liquidation bonus manipulation
|
|
44
|
+
|
|
45
|
+
**Checklist:**
|
|
46
|
+
- [ ] Can users self-liquidate profitably?
|
|
47
|
+
- [ ] Is health factor calculation correct with all decimal handling?
|
|
48
|
+
- [ ] Is liquidation bonus reasonable (not exploitable)?
|
|
49
|
+
- [ ] Are partial liquidations handled correctly?
|
|
50
|
+
- [ ] Can dust amounts block liquidation?
|
|
51
|
+
|
|
52
|
+
```solidity
|
|
53
|
+
// VULNERABLE: No self-liquidation check
|
|
54
|
+
function liquidate(address borrower, uint256 amount) external {
|
|
55
|
+
require(getHealthFactor(borrower) < 1e18, "Healthy");
|
|
56
|
+
// Missing: require(msg.sender != borrower, "No self-liquidation");
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Interest Rate Model
|
|
61
|
+
|
|
62
|
+
**Attack Vectors:**
|
|
63
|
+
- Interest rate manipulation via large deposits/borrows
|
|
64
|
+
- Accrual timing exploits
|
|
65
|
+
- Compound interest calculation errors
|
|
66
|
+
|
|
67
|
+
**Checklist:**
|
|
68
|
+
- [ ] Is interest accrued before all operations?
|
|
69
|
+
- [ ] Are utilization rate calculations correct?
|
|
70
|
+
- [ ] Can interest rate be manipulated within a transaction?
|
|
71
|
+
- [ ] Are there bounds on interest rates?
|
|
72
|
+
|
|
73
|
+
```solidity
|
|
74
|
+
// VULNERABLE: Interest not accrued before operation
|
|
75
|
+
function withdraw(uint256 amount) external {
|
|
76
|
+
// Missing: accrueInterest();
|
|
77
|
+
uint256 shares = amount * totalShares / totalAssets;
|
|
78
|
+
_burn(msg.sender, shares);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// SECURE
|
|
82
|
+
function withdraw(uint256 amount) external {
|
|
83
|
+
accrueInterest(); // Always accrue first
|
|
84
|
+
uint256 shares = amount * totalShares / totalAssets;
|
|
85
|
+
_burn(msg.sender, shares);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Oracle Dependency
|
|
90
|
+
|
|
91
|
+
**Attack Vectors:**
|
|
92
|
+
- Price oracle manipulation
|
|
93
|
+
- Stale price exploitation
|
|
94
|
+
- Flash loan + oracle attack
|
|
95
|
+
|
|
96
|
+
**Checklist:**
|
|
97
|
+
- [ ] Are oracle prices validated for freshness?
|
|
98
|
+
- [ ] Are price bounds checked?
|
|
99
|
+
- [ ] Can prices be manipulated via flash loans?
|
|
100
|
+
- [ ] Is there circuit breaker for price anomalies?
|
|
101
|
+
|
|
102
|
+
See: [oracle.md](./exploits/oracle.md)
|
|
103
|
+
|
|
104
|
+
### 4. Collateral Management
|
|
105
|
+
|
|
106
|
+
**Attack Vectors:**
|
|
107
|
+
- Depositing worthless collateral
|
|
108
|
+
- Collateral factor manipulation
|
|
109
|
+
- Bad debt accumulation
|
|
110
|
+
|
|
111
|
+
**Checklist:**
|
|
112
|
+
- [ ] Are collateral factors appropriate for asset volatility?
|
|
113
|
+
- [ ] Is there a whitelist for supported collateral?
|
|
114
|
+
- [ ] How is bad debt handled?
|
|
115
|
+
- [ ] Can users withdraw collateral below safe threshold?
|
|
116
|
+
|
|
117
|
+
### 5. Share/Asset Accounting (ERC4626)
|
|
118
|
+
|
|
119
|
+
**Attack Vectors:**
|
|
120
|
+
- Inflation attacks on first deposit
|
|
121
|
+
- Rounding direction exploitation
|
|
122
|
+
- Donation attacks
|
|
123
|
+
|
|
124
|
+
**Checklist:**
|
|
125
|
+
- [ ] Is first depositor protected from inflation attack?
|
|
126
|
+
- [ ] Does rounding favor the protocol (round down on mint, up on burn)?
|
|
127
|
+
- [ ] Are direct asset transfers (donations) handled?
|
|
128
|
+
- [ ] Is totalAssets always ≥ sum of deposits?
|
|
129
|
+
|
|
130
|
+
```solidity
|
|
131
|
+
// Inflation attack protection
|
|
132
|
+
function deposit(uint256 assets) external returns (uint256 shares) {
|
|
133
|
+
require(assets >= MINIMUM_DEPOSIT, "Below minimum");
|
|
134
|
+
shares = totalSupply == 0
|
|
135
|
+
? assets // First deposit
|
|
136
|
+
: assets * totalSupply / totalAssets;
|
|
137
|
+
require(shares > 0, "Zero shares");
|
|
138
|
+
// ...
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Common Vulnerabilities
|
|
145
|
+
|
|
146
|
+
### Reentrancy in Lending
|
|
147
|
+
|
|
148
|
+
```solidity
|
|
149
|
+
// VULNERABLE: CEI violation
|
|
150
|
+
function borrow(uint256 amount) external {
|
|
151
|
+
require(checkCollateral(msg.sender, amount), "Undercollateralized");
|
|
152
|
+
token.transfer(msg.sender, amount); // External call before state update
|
|
153
|
+
borrowBalances[msg.sender] += amount; // State update after
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Incorrect Decimal Handling
|
|
158
|
+
|
|
159
|
+
```solidity
|
|
160
|
+
// VULNERABLE: Assumes all tokens have 18 decimals
|
|
161
|
+
function calculateValue(address token, uint256 amount) public view returns (uint256) {
|
|
162
|
+
uint256 price = oracle.getPrice(token); // Price in USD with 8 decimals
|
|
163
|
+
return amount * price / 1e8; // Wrong if token isn't 18 decimals!
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// SECURE
|
|
167
|
+
function calculateValue(address token, uint256 amount) public view returns (uint256) {
|
|
168
|
+
uint256 price = oracle.getPrice(token);
|
|
169
|
+
uint8 decimals = IERC20Metadata(token).decimals();
|
|
170
|
+
return amount * price / (10 ** decimals);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Liquidation Threshold Edge Cases
|
|
175
|
+
|
|
176
|
+
```solidity
|
|
177
|
+
// VULNERABLE: Dust prevents liquidation
|
|
178
|
+
function liquidate(address borrower, uint256 amount) external {
|
|
179
|
+
uint256 debt = borrowBalances[borrower];
|
|
180
|
+
require(amount <= debt, "Too much");
|
|
181
|
+
// If debt = 100 wei and minimum repay = 100 wei, can't liquidate
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Testing Checklist
|
|
188
|
+
|
|
189
|
+
### Unit Tests
|
|
190
|
+
- [ ] Deposit/withdraw accounting correct
|
|
191
|
+
- [ ] Borrow/repay accounting correct
|
|
192
|
+
- [ ] Interest accrual over time
|
|
193
|
+
- [ ] Liquidation triggers at correct threshold
|
|
194
|
+
- [ ] Health factor calculation
|
|
195
|
+
|
|
196
|
+
### Integration Tests
|
|
197
|
+
- [ ] Oracle integration
|
|
198
|
+
- [ ] Multi-asset interactions
|
|
199
|
+
- [ ] Liquidation bot behavior
|
|
200
|
+
|
|
201
|
+
### Invariant Tests
|
|
202
|
+
- [ ] totalBorrowed ≤ totalSupplied * maxUtilization
|
|
203
|
+
- [ ] Each user's healthFactor > 1 OR liquidatable
|
|
204
|
+
- [ ] Sum of deposits = totalAssets (accounting)
|
|
205
|
+
- [ ] No negative balances
|
|
206
|
+
|
|
207
|
+
### Edge Case Tests
|
|
208
|
+
- [ ] First deposit (empty pool)
|
|
209
|
+
- [ ] Last withdrawal (drain pool)
|
|
210
|
+
- [ ] Zero amount operations
|
|
211
|
+
- [ ] Max uint256 operations
|
|
212
|
+
- [ ] 1 wei operations
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## References
|
|
217
|
+
|
|
218
|
+
- [Aave V3 Security](https://github.com/aave/aave-v3-core/tree/master/audits)
|
|
219
|
+
- [Compound Security Considerations](https://docs.compound.finance/security/)
|
|
220
|
+
- [ERC4626 Security Considerations](https://eips.ethereum.org/EIPS/eip-4626#security-considerations)
|
|
221
|
+
- [Morpho Security](https://docs.morpho.org/morpho-blue/security-and-risk-management)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: staking-vesting
|
|
3
|
+
description: Staking security guidance for reward accounting, lock periods, timing attacks, and withdrawals.
|
|
4
|
+
---
|
|
5
|
+
<!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
|
|
6
|
+
|
|
7
|
+
# Staking Protocol Security Guide
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Staking protocols lock tokens to earn rewards. Core security concerns: reward calculation, timing attacks, withdrawal delays, and token accounting.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ STAKING PROTOCOL │
|
|
20
|
+
├─────────────────────────────────────────────────────────────┤
|
|
21
|
+
│ │
|
|
22
|
+
│ STAKE EARN UNSTAKE │
|
|
23
|
+
│ ────── ──── ─────── │
|
|
24
|
+
│ Lock tokens → Accrue rewards → Withdraw + rewards │
|
|
25
|
+
│ │
|
|
26
|
+
│ rewardPerToken = totalRewards / totalStaked / time │
|
|
27
|
+
│ │
|
|
28
|
+
│ userReward = (rewardPerToken - userRewardPaid) * balance │
|
|
29
|
+
│ │
|
|
30
|
+
└─────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Critical Security Areas
|
|
36
|
+
|
|
37
|
+
### 1. Reward Calculation
|
|
38
|
+
|
|
39
|
+
**Attack Vectors:**
|
|
40
|
+
- Claim rewards multiple times
|
|
41
|
+
- Manipulation of rewardPerToken
|
|
42
|
+
- Precision loss in reward math
|
|
43
|
+
|
|
44
|
+
**Checklist:**
|
|
45
|
+
- [ ] Is rewardPerTokenStored updated before any balance changes?
|
|
46
|
+
- [ ] Is userRewardPerTokenPaid updated when claiming?
|
|
47
|
+
- [ ] Can rewards be claimed multiple times for same period?
|
|
48
|
+
- [ ] Is precision sufficient (multiply before divide)?
|
|
49
|
+
|
|
50
|
+
```solidity
|
|
51
|
+
// Standard Synthetix staking pattern
|
|
52
|
+
uint256 public rewardPerTokenStored;
|
|
53
|
+
mapping(address => uint256) public userRewardPerTokenPaid;
|
|
54
|
+
mapping(address => uint256) public rewards;
|
|
55
|
+
|
|
56
|
+
modifier updateReward(address account) {
|
|
57
|
+
rewardPerTokenStored = rewardPerToken();
|
|
58
|
+
lastUpdateTime = block.timestamp;
|
|
59
|
+
if (account != address(0)) {
|
|
60
|
+
rewards[account] = earned(account);
|
|
61
|
+
userRewardPerTokenPaid[account] = rewardPerTokenStored;
|
|
62
|
+
}
|
|
63
|
+
_;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function earned(address account) public view returns (uint256) {
|
|
67
|
+
return balances[account] *
|
|
68
|
+
(rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18 +
|
|
69
|
+
rewards[account];
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Deposit/Withdrawal Timing
|
|
74
|
+
|
|
75
|
+
**Attack Vectors:**
|
|
76
|
+
- Stake just before rewards, unstake right after
|
|
77
|
+
- Flash loan staking
|
|
78
|
+
- MEV on reward distribution
|
|
79
|
+
|
|
80
|
+
**Checklist:**
|
|
81
|
+
- [ ] Is there a minimum stake duration?
|
|
82
|
+
- [ ] Are rewards distributed over time (not lump sum)?
|
|
83
|
+
- [ ] Can someone stake in same block as reward distribution?
|
|
84
|
+
- [ ] Is there unstaking delay/cooldown?
|
|
85
|
+
|
|
86
|
+
```solidity
|
|
87
|
+
// VULNERABLE: Instant stake and claim
|
|
88
|
+
function distributeReward(uint256 amount) external {
|
|
89
|
+
rewardToken.transferFrom(msg.sender, address(this), amount);
|
|
90
|
+
rewardPerTokenStored += amount * 1e18 / totalStaked;
|
|
91
|
+
// Flash staker can stake before this, claim after
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// SECURE: Drip rewards over time
|
|
95
|
+
function notifyRewardAmount(uint256 reward) external {
|
|
96
|
+
if (block.timestamp >= periodFinish) {
|
|
97
|
+
rewardRate = reward / DURATION;
|
|
98
|
+
} else {
|
|
99
|
+
uint256 remaining = periodFinish - block.timestamp;
|
|
100
|
+
uint256 leftover = remaining * rewardRate;
|
|
101
|
+
rewardRate = (reward + leftover) / DURATION;
|
|
102
|
+
}
|
|
103
|
+
lastUpdateTime = block.timestamp;
|
|
104
|
+
periodFinish = block.timestamp + DURATION;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 3. Token Accounting
|
|
109
|
+
|
|
110
|
+
**Attack Vectors:**
|
|
111
|
+
- Donation attacks (direct transfer)
|
|
112
|
+
- Rebase token issues
|
|
113
|
+
- Fee-on-transfer tokens
|
|
114
|
+
|
|
115
|
+
**Checklist:**
|
|
116
|
+
- [ ] Are direct token transfers handled?
|
|
117
|
+
- [ ] Is actual received amount checked (fee-on-transfer)?
|
|
118
|
+
- [ ] Are rebase tokens supported? If so, how?
|
|
119
|
+
- [ ] Is totalStaked always equal to sum of balances?
|
|
120
|
+
|
|
121
|
+
```solidity
|
|
122
|
+
// VULNERABLE: Assumes full amount received
|
|
123
|
+
function stake(uint256 amount) external {
|
|
124
|
+
stakingToken.transferFrom(msg.sender, address(this), amount);
|
|
125
|
+
balances[msg.sender] += amount; // Wrong for fee-on-transfer!
|
|
126
|
+
totalStaked += amount;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// SECURE: Check actual amount received
|
|
130
|
+
function stake(uint256 amount) external {
|
|
131
|
+
uint256 balanceBefore = stakingToken.balanceOf(address(this));
|
|
132
|
+
stakingToken.transferFrom(msg.sender, address(this), amount);
|
|
133
|
+
uint256 received = stakingToken.balanceOf(address(this)) - balanceBefore;
|
|
134
|
+
|
|
135
|
+
balances[msg.sender] += received;
|
|
136
|
+
totalStaked += received;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 4. Lock/Unlock Mechanisms
|
|
141
|
+
|
|
142
|
+
**Attack Vectors:**
|
|
143
|
+
- Bypassing lock periods
|
|
144
|
+
- Lock period manipulation
|
|
145
|
+
- Emergency withdrawal exploits
|
|
146
|
+
|
|
147
|
+
**Checklist:**
|
|
148
|
+
- [ ] Is lock timestamp immutable after staking?
|
|
149
|
+
- [ ] Can partial unlocks bypass full lock?
|
|
150
|
+
- [ ] Is emergency withdrawal penalized appropriately?
|
|
151
|
+
- [ ] Are there reentrancy risks in unlock?
|
|
152
|
+
|
|
153
|
+
```solidity
|
|
154
|
+
// VULNERABLE: Lock can be extended/reset incorrectly
|
|
155
|
+
function stake(uint256 amount) external {
|
|
156
|
+
balances[msg.sender] += amount;
|
|
157
|
+
lockEnd[msg.sender] = block.timestamp + LOCK_PERIOD; // Resets lock!
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// SECURE: Don't reset existing locks
|
|
161
|
+
function stake(uint256 amount) external {
|
|
162
|
+
balances[msg.sender] += amount;
|
|
163
|
+
if (lockEnd[msg.sender] < block.timestamp) {
|
|
164
|
+
lockEnd[msg.sender] = block.timestamp + LOCK_PERIOD;
|
|
165
|
+
}
|
|
166
|
+
// Existing lock preserved
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 5. Reward Token Handling
|
|
171
|
+
|
|
172
|
+
**Attack Vectors:**
|
|
173
|
+
- Same token for stake and reward (inflation)
|
|
174
|
+
- Reward token donation manipulation
|
|
175
|
+
- Insufficient reward balance
|
|
176
|
+
|
|
177
|
+
**Checklist:**
|
|
178
|
+
- [ ] Is stake token different from reward token?
|
|
179
|
+
- [ ] Is there sufficient reward balance for all claims?
|
|
180
|
+
- [ ] Can reward distribution be griefed?
|
|
181
|
+
- [ ] Are multiple reward tokens handled correctly?
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Common Vulnerabilities
|
|
186
|
+
|
|
187
|
+
### Reentrancy in Claim
|
|
188
|
+
|
|
189
|
+
```solidity
|
|
190
|
+
// VULNERABLE: External call before state update
|
|
191
|
+
function claim() external {
|
|
192
|
+
uint256 reward = earned(msg.sender);
|
|
193
|
+
rewardToken.transfer(msg.sender, reward); // External call first
|
|
194
|
+
rewards[msg.sender] = 0; // State update after
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Division Before Multiplication
|
|
199
|
+
|
|
200
|
+
```solidity
|
|
201
|
+
// VULNERABLE: Precision loss
|
|
202
|
+
function rewardPerToken() public view returns (uint256) {
|
|
203
|
+
if (totalStaked == 0) return rewardPerTokenStored;
|
|
204
|
+
return rewardPerTokenStored +
|
|
205
|
+
(rewardRate / totalStaked) * (block.timestamp - lastUpdate); // Wrong order!
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// SECURE
|
|
209
|
+
function rewardPerToken() public view returns (uint256) {
|
|
210
|
+
if (totalStaked == 0) return rewardPerTokenStored;
|
|
211
|
+
return rewardPerTokenStored +
|
|
212
|
+
rewardRate * (block.timestamp - lastUpdate) * 1e18 / totalStaked;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Testing Checklist
|
|
219
|
+
|
|
220
|
+
### Unit Tests
|
|
221
|
+
- [ ] Stake/unstake accounting
|
|
222
|
+
- [ ] Reward accrual over time
|
|
223
|
+
- [ ] Multiple stakers, proportional rewards
|
|
224
|
+
- [ ] Claim resets user state correctly
|
|
225
|
+
|
|
226
|
+
### Integration Tests
|
|
227
|
+
- [ ] Multiple reward periods
|
|
228
|
+
- [ ] Stakers joining mid-period
|
|
229
|
+
- [ ] Edge cases (first staker, last staker)
|
|
230
|
+
|
|
231
|
+
### Invariant Tests
|
|
232
|
+
- [ ] totalStaked = sum(balances)
|
|
233
|
+
- [ ] Rewards claimable ≤ reward balance
|
|
234
|
+
- [ ] No user can claim more than entitled
|
|
235
|
+
|
|
236
|
+
### Attack Tests
|
|
237
|
+
- [ ] Flash stake attack
|
|
238
|
+
- [ ] Double claim attempt
|
|
239
|
+
- [ ] Donation manipulation
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## References
|
|
244
|
+
|
|
245
|
+
- [Synthetix Staking Rewards](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol)
|
|
246
|
+
- [Convex Staking](https://github.com/convex-eth/platform)
|
|
247
|
+
- [MasterChef (SushiSwap)](https://github.com/sushiswap/sushiswap/blob/master/contracts/MasterChef.sol)
|