agentic-team-templates 0.3.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/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Smart Contract Security
|
|
2
|
+
|
|
3
|
+
Comprehensive security guidelines covering attack vectors, defense patterns, and audit preparation.
|
|
4
|
+
|
|
5
|
+
## Common Attack Vectors
|
|
6
|
+
|
|
7
|
+
### Reentrancy
|
|
8
|
+
|
|
9
|
+
**Vulnerability**: External calls allow malicious contracts to re-enter your function before state updates complete.
|
|
10
|
+
|
|
11
|
+
```solidity
|
|
12
|
+
// Vulnerable
|
|
13
|
+
function withdraw() external {
|
|
14
|
+
uint256 balance = balances[msg.sender];
|
|
15
|
+
(bool success,) = msg.sender.call{value: balance}(""); // Attacker re-enters here
|
|
16
|
+
require(success);
|
|
17
|
+
balances[msg.sender] = 0; // Too late!
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Secure: CEI + ReentrancyGuard
|
|
21
|
+
function withdraw() external nonReentrant {
|
|
22
|
+
uint256 balance = balances[msg.sender];
|
|
23
|
+
balances[msg.sender] = 0; // State update first
|
|
24
|
+
(bool success,) = msg.sender.call{value: balance}("");
|
|
25
|
+
if (!success) revert TransferFailed();
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Flash Loan Attacks
|
|
30
|
+
|
|
31
|
+
**Vulnerability**: On-chain price oracles can be manipulated within a single transaction.
|
|
32
|
+
|
|
33
|
+
```solidity
|
|
34
|
+
// Vulnerable: Using spot price
|
|
35
|
+
function getCollateralValue(address user) public view returns (uint256) {
|
|
36
|
+
uint256 tokenBalance = collateral[user];
|
|
37
|
+
uint256 spotPrice = amm.getSpotPrice(); // Can be manipulated!
|
|
38
|
+
return tokenBalance * spotPrice;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Secure: Time-weighted average price (TWAP)
|
|
42
|
+
function getCollateralValue(address user) public view returns (uint256) {
|
|
43
|
+
uint256 tokenBalance = collateral[user];
|
|
44
|
+
uint256 twapPrice = oracle.getTwapPrice(1 hours); // Resistant to manipulation
|
|
45
|
+
return tokenBalance * twapPrice;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Front-Running / MEV
|
|
50
|
+
|
|
51
|
+
**Vulnerability**: Transactions in the mempool can be observed and exploited.
|
|
52
|
+
|
|
53
|
+
**Defenses**:
|
|
54
|
+
|
|
55
|
+
```solidity
|
|
56
|
+
// Commit-reveal for sensitive operations
|
|
57
|
+
mapping(bytes32 => uint256) public commits;
|
|
58
|
+
|
|
59
|
+
function commit(bytes32 hash) external {
|
|
60
|
+
commits[hash] = block.timestamp;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function reveal(uint256 value, bytes32 salt) external {
|
|
64
|
+
bytes32 hash = keccak256(abi.encode(msg.sender, value, salt));
|
|
65
|
+
if (commits[hash] == 0) revert NotCommitted();
|
|
66
|
+
if (block.timestamp < commits[hash] + MIN_DELAY) revert TooEarly();
|
|
67
|
+
|
|
68
|
+
delete commits[hash];
|
|
69
|
+
// Execute with revealed value
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Slippage protection
|
|
73
|
+
function swap(
|
|
74
|
+
address tokenIn,
|
|
75
|
+
address tokenOut,
|
|
76
|
+
uint256 amountIn,
|
|
77
|
+
uint256 minAmountOut, // User specifies minimum acceptable output
|
|
78
|
+
uint256 deadline // Transaction expires
|
|
79
|
+
) external {
|
|
80
|
+
if (block.timestamp > deadline) revert Expired();
|
|
81
|
+
|
|
82
|
+
uint256 amountOut = _executeSwap(tokenIn, tokenOut, amountIn);
|
|
83
|
+
if (amountOut < minAmountOut) revert SlippageExceeded();
|
|
84
|
+
|
|
85
|
+
// Transfer tokens
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Integer Overflow/Underflow
|
|
90
|
+
|
|
91
|
+
Solidity 0.8+ has built-in checks, but be aware of `unchecked` blocks:
|
|
92
|
+
|
|
93
|
+
```solidity
|
|
94
|
+
// Safe by default in 0.8+
|
|
95
|
+
uint256 a = type(uint256).max;
|
|
96
|
+
uint256 b = a + 1; // Reverts
|
|
97
|
+
|
|
98
|
+
// Dangerous: unchecked arithmetic
|
|
99
|
+
unchecked {
|
|
100
|
+
uint256 c = a + 1; // Wraps to 0!
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Only use unchecked when you've proven overflow is impossible
|
|
104
|
+
function incrementCounter() external {
|
|
105
|
+
unchecked {
|
|
106
|
+
// Safe because we check first
|
|
107
|
+
if (counter >= type(uint256).max) revert CounterOverflow();
|
|
108
|
+
counter++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Access Control Bypass
|
|
114
|
+
|
|
115
|
+
```solidity
|
|
116
|
+
// Vulnerable: tx.origin
|
|
117
|
+
function withdraw() external {
|
|
118
|
+
require(tx.origin == owner); // Can be bypassed via phishing!
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Secure: msg.sender
|
|
122
|
+
function withdraw() external {
|
|
123
|
+
require(msg.sender == owner);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Even better: Role-based access control
|
|
127
|
+
function withdraw() external onlyRole(ADMIN_ROLE) {
|
|
128
|
+
// Only admins can call
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Denial of Service
|
|
133
|
+
|
|
134
|
+
```solidity
|
|
135
|
+
// Vulnerable: Unbounded loop
|
|
136
|
+
function distributeRewards() external {
|
|
137
|
+
for (uint256 i = 0; i < recipients.length; i++) {
|
|
138
|
+
payable(recipients[i]).transfer(rewards[i]); // One failure stops all
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Secure: Pull pattern with pagination
|
|
143
|
+
function claimReward() external {
|
|
144
|
+
uint256 reward = pendingRewards[msg.sender];
|
|
145
|
+
if (reward == 0) revert NoReward();
|
|
146
|
+
|
|
147
|
+
pendingRewards[msg.sender] = 0;
|
|
148
|
+
(bool success,) = msg.sender.call{value: reward}("");
|
|
149
|
+
if (!success) revert TransferFailed();
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Storage Collision (Upgradeable Contracts)
|
|
154
|
+
|
|
155
|
+
```solidity
|
|
156
|
+
// Vulnerable: Adding variables incorrectly
|
|
157
|
+
contract V1 {
|
|
158
|
+
uint256 public value; // Slot 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
contract V2 is V1 {
|
|
162
|
+
address public newAdmin; // Slot 1
|
|
163
|
+
uint256 public newValue; // Slot 2
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If you add a variable in the middle, it corrupts storage!
|
|
167
|
+
contract V2Bad is V1 {
|
|
168
|
+
uint256 public newValue; // Slot 1 - OVERWRITES existing data!
|
|
169
|
+
address public newAdmin; // Slot 2
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Secure: Storage gaps
|
|
173
|
+
contract V1 {
|
|
174
|
+
uint256 public value;
|
|
175
|
+
uint256[49] private __gap; // Reserve slots for future use
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Security Checklist
|
|
180
|
+
|
|
181
|
+
### Before Writing Code
|
|
182
|
+
|
|
183
|
+
- [ ] Threat model documented
|
|
184
|
+
- [ ] Attack vectors identified
|
|
185
|
+
- [ ] Trust assumptions explicit
|
|
186
|
+
- [ ] Upgrade strategy decided
|
|
187
|
+
|
|
188
|
+
### During Development
|
|
189
|
+
|
|
190
|
+
- [ ] Checks-Effects-Interactions pattern
|
|
191
|
+
- [ ] Reentrancy guards on state-changing functions
|
|
192
|
+
- [ ] Input validation on all external functions
|
|
193
|
+
- [ ] Access control on sensitive functions
|
|
194
|
+
- [ ] Events emitted for all state changes
|
|
195
|
+
- [ ] No floating pragma
|
|
196
|
+
- [ ] No assembly without documentation
|
|
197
|
+
- [ ] No `tx.origin` for authentication
|
|
198
|
+
- [ ] No `selfdestruct` unless absolutely necessary
|
|
199
|
+
|
|
200
|
+
### Before Deployment
|
|
201
|
+
|
|
202
|
+
- [ ] Slither reports zero high/medium findings
|
|
203
|
+
- [ ] Mythril analysis complete
|
|
204
|
+
- [ ] Fuzz tests with >100k runs
|
|
205
|
+
- [ ] Mainnet fork tests pass
|
|
206
|
+
- [ ] External audit completed
|
|
207
|
+
- [ ] Bug bounty program ready
|
|
208
|
+
|
|
209
|
+
### After Deployment
|
|
210
|
+
|
|
211
|
+
- [ ] Monitoring alerts configured
|
|
212
|
+
- [ ] Incident response plan documented
|
|
213
|
+
- [ ] Admin keys secured (multisig, hardware wallet)
|
|
214
|
+
- [ ] Gradual rollout (testnet → limited mainnet → full)
|
|
215
|
+
|
|
216
|
+
## Secure Development Workflow
|
|
217
|
+
|
|
218
|
+
### 1. Static Analysis
|
|
219
|
+
|
|
220
|
+
Run on every commit:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Slither - detects 40+ vulnerability patterns
|
|
224
|
+
slither . --exclude-dependencies
|
|
225
|
+
|
|
226
|
+
# Mythril - symbolic execution
|
|
227
|
+
myth analyze contracts/MyContract.sol
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 2. Invariant Testing
|
|
231
|
+
|
|
232
|
+
Define properties that must always hold:
|
|
233
|
+
|
|
234
|
+
```solidity
|
|
235
|
+
// test/invariant/VaultInvariant.t.sol
|
|
236
|
+
function invariant_totalSharesMatchesDeposits() public {
|
|
237
|
+
uint256 totalAssets = vault.totalAssets();
|
|
238
|
+
uint256 totalShares = vault.totalSupply();
|
|
239
|
+
|
|
240
|
+
// Total shares should always be <= total assets (no inflation)
|
|
241
|
+
assert(totalShares <= totalAssets + 1); // +1 for rounding
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function invariant_noFreeShares() public {
|
|
245
|
+
// Can't get shares without depositing
|
|
246
|
+
assert(vault.balanceOf(attacker) == 0 || deposits[attacker] > 0);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 3. Fuzz Testing
|
|
251
|
+
|
|
252
|
+
```solidity
|
|
253
|
+
function testFuzz_deposit(uint256 amount) public {
|
|
254
|
+
amount = bound(amount, 1, type(uint96).max); // Reasonable bounds
|
|
255
|
+
|
|
256
|
+
token.mint(user, amount);
|
|
257
|
+
vm.startPrank(user);
|
|
258
|
+
token.approve(address(vault), amount);
|
|
259
|
+
|
|
260
|
+
uint256 sharesBefore = vault.balanceOf(user);
|
|
261
|
+
vault.deposit(amount, user);
|
|
262
|
+
uint256 sharesAfter = vault.balanceOf(user);
|
|
263
|
+
|
|
264
|
+
assertGt(sharesAfter, sharesBefore);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 4. Mainnet Fork Testing
|
|
269
|
+
|
|
270
|
+
```solidity
|
|
271
|
+
function testFork_integrationWithUniswap() public {
|
|
272
|
+
// Fork mainnet at specific block
|
|
273
|
+
vm.createSelectFork(vm.envString("ETH_RPC_URL"), 18_000_000);
|
|
274
|
+
|
|
275
|
+
// Test against real Uniswap contracts
|
|
276
|
+
IUniswapV3Router router = IUniswapV3Router(UNISWAP_ROUTER);
|
|
277
|
+
// ... test integration
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Audit Preparation
|
|
282
|
+
|
|
283
|
+
### Documentation Required
|
|
284
|
+
|
|
285
|
+
1. **System Overview**: Architecture diagram, trust assumptions
|
|
286
|
+
2. **Contract Descriptions**: Purpose of each contract
|
|
287
|
+
3. **External Interactions**: All protocols you integrate with
|
|
288
|
+
4. **Access Control Matrix**: Who can call what
|
|
289
|
+
5. **Known Issues**: Things you've accepted as tradeoffs
|
|
290
|
+
6. **Test Coverage Report**: What's tested, what's not
|
|
291
|
+
|
|
292
|
+
### Code Organization
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
docs/
|
|
296
|
+
├── architecture.md # System design
|
|
297
|
+
├── threat-model.md # Security assumptions
|
|
298
|
+
├── access-control.md # Roles and permissions
|
|
299
|
+
└── deployment.md # Deploy process
|
|
300
|
+
|
|
301
|
+
contracts/
|
|
302
|
+
├── src/ # Clean, well-documented code
|
|
303
|
+
├── test/ # Comprehensive tests
|
|
304
|
+
└── script/ # Deployment scripts
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Common Audit Findings to Pre-Check
|
|
308
|
+
|
|
309
|
+
1. Missing zero-address checks
|
|
310
|
+
2. Missing input validation
|
|
311
|
+
3. Centralization risks (admin keys)
|
|
312
|
+
4. Lack of events for state changes
|
|
313
|
+
5. Inconsistent error handling
|
|
314
|
+
6. Missing NatSpec documentation
|
|
315
|
+
7. Unused code/imports
|
|
316
|
+
8. Hardcoded addresses
|
|
317
|
+
9. Missing reentrancy protection
|
|
318
|
+
10. Unsafe external calls
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Smart Contract Development
|
|
2
|
+
|
|
3
|
+
Solidity patterns and best practices for secure, maintainable smart contracts.
|
|
4
|
+
|
|
5
|
+
## Solidity Style Guide
|
|
6
|
+
|
|
7
|
+
### File Structure
|
|
8
|
+
|
|
9
|
+
```solidity
|
|
10
|
+
// SPDX-License-Identifier: MIT
|
|
11
|
+
pragma solidity ^0.8.20;
|
|
12
|
+
|
|
13
|
+
// 1. Imports (interfaces first, then libraries, then contracts)
|
|
14
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
15
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
16
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
17
|
+
|
|
18
|
+
// 2. Interfaces
|
|
19
|
+
|
|
20
|
+
// 3. Libraries
|
|
21
|
+
|
|
22
|
+
// 4. Contracts
|
|
23
|
+
contract MyContract is Ownable {
|
|
24
|
+
// a. Type declarations (using, struct, enum)
|
|
25
|
+
using SafeERC20 for IERC20;
|
|
26
|
+
|
|
27
|
+
// b. State variables
|
|
28
|
+
uint256 public constant MAX_SUPPLY = 1_000_000e18;
|
|
29
|
+
uint256 private s_totalDeposits;
|
|
30
|
+
mapping(address => uint256) private s_balances;
|
|
31
|
+
|
|
32
|
+
// c. Events
|
|
33
|
+
event Deposited(address indexed user, uint256 amount);
|
|
34
|
+
|
|
35
|
+
// d. Errors
|
|
36
|
+
error InsufficientBalance(uint256 requested, uint256 available);
|
|
37
|
+
|
|
38
|
+
// e. Modifiers
|
|
39
|
+
|
|
40
|
+
// f. Constructor
|
|
41
|
+
|
|
42
|
+
// g. External functions
|
|
43
|
+
|
|
44
|
+
// h. Public functions
|
|
45
|
+
|
|
46
|
+
// i. Internal functions
|
|
47
|
+
|
|
48
|
+
// j. Private functions
|
|
49
|
+
|
|
50
|
+
// k. View/Pure functions
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Naming Conventions
|
|
55
|
+
|
|
56
|
+
```solidity
|
|
57
|
+
// Constants: SCREAMING_SNAKE_CASE
|
|
58
|
+
uint256 public constant MAX_FEE = 1000;
|
|
59
|
+
|
|
60
|
+
// Immutables: i_ prefix
|
|
61
|
+
address private immutable i_owner;
|
|
62
|
+
|
|
63
|
+
// Storage variables: s_ prefix
|
|
64
|
+
uint256 private s_totalSupply;
|
|
65
|
+
mapping(address => uint256) private s_balances;
|
|
66
|
+
|
|
67
|
+
// Function parameters: no prefix
|
|
68
|
+
function deposit(uint256 amount, address recipient) external;
|
|
69
|
+
|
|
70
|
+
// Local variables: no prefix
|
|
71
|
+
uint256 balance = s_balances[msg.sender];
|
|
72
|
+
|
|
73
|
+
// Events: PastTense
|
|
74
|
+
event Deposited(address indexed user, uint256 amount);
|
|
75
|
+
event FeeUpdated(uint256 oldFee, uint256 newFee);
|
|
76
|
+
|
|
77
|
+
// Errors: DescriptiveError
|
|
78
|
+
error InsufficientBalance(uint256 requested, uint256 available);
|
|
79
|
+
error Unauthorized(address caller);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Security Patterns
|
|
83
|
+
|
|
84
|
+
### Checks-Effects-Interactions
|
|
85
|
+
|
|
86
|
+
Always follow this order to prevent reentrancy:
|
|
87
|
+
|
|
88
|
+
```solidity
|
|
89
|
+
// Good: CEI pattern
|
|
90
|
+
function withdraw(uint256 amount) external {
|
|
91
|
+
// 1. CHECKS - Validate inputs and state
|
|
92
|
+
if (s_balances[msg.sender] < amount) {
|
|
93
|
+
revert InsufficientBalance(amount, s_balances[msg.sender]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. EFFECTS - Update state
|
|
97
|
+
s_balances[msg.sender] -= amount;
|
|
98
|
+
|
|
99
|
+
// 3. INTERACTIONS - External calls
|
|
100
|
+
(bool success,) = msg.sender.call{value: amount}("");
|
|
101
|
+
if (!success) revert TransferFailed();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Bad: Reentrancy vulnerable
|
|
105
|
+
function withdrawBad(uint256 amount) external {
|
|
106
|
+
require(s_balances[msg.sender] >= amount);
|
|
107
|
+
(bool success,) = msg.sender.call{value: amount}(""); // External call first!
|
|
108
|
+
require(success);
|
|
109
|
+
s_balances[msg.sender] -= amount; // State update after!
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Reentrancy Guards
|
|
114
|
+
|
|
115
|
+
Use for complex functions even with CEI:
|
|
116
|
+
|
|
117
|
+
```solidity
|
|
118
|
+
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
119
|
+
|
|
120
|
+
contract Vault is ReentrancyGuard {
|
|
121
|
+
function complexWithdraw(uint256 amount) external nonReentrant {
|
|
122
|
+
// Safe from reentrancy even with multiple external calls
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Access Control
|
|
128
|
+
|
|
129
|
+
```solidity
|
|
130
|
+
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
|
131
|
+
|
|
132
|
+
contract Protocol is AccessControl {
|
|
133
|
+
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
|
|
134
|
+
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
|
|
135
|
+
|
|
136
|
+
constructor() {
|
|
137
|
+
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
|
138
|
+
_grantRole(ADMIN_ROLE, msg.sender);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function emergencyPause() external onlyRole(ADMIN_ROLE) {
|
|
142
|
+
// Only admins can pause
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function processQueue() external onlyRole(OPERATOR_ROLE) {
|
|
146
|
+
// Operators can process
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Input Validation
|
|
152
|
+
|
|
153
|
+
```solidity
|
|
154
|
+
function deposit(address token, uint256 amount) external {
|
|
155
|
+
// Validate address
|
|
156
|
+
if (token == address(0)) revert ZeroAddress();
|
|
157
|
+
|
|
158
|
+
// Validate amount
|
|
159
|
+
if (amount == 0) revert ZeroAmount();
|
|
160
|
+
|
|
161
|
+
// Validate token is allowed
|
|
162
|
+
if (!s_allowedTokens[token]) revert TokenNotAllowed(token);
|
|
163
|
+
|
|
164
|
+
// Validate limits
|
|
165
|
+
if (amount > MAX_DEPOSIT) revert ExceedsMaxDeposit(amount, MAX_DEPOSIT);
|
|
166
|
+
|
|
167
|
+
// Safe to proceed
|
|
168
|
+
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Common Patterns
|
|
173
|
+
|
|
174
|
+
### Pull Over Push
|
|
175
|
+
|
|
176
|
+
Prefer pull payments over push to avoid DoS:
|
|
177
|
+
|
|
178
|
+
```solidity
|
|
179
|
+
// Good: Pull pattern
|
|
180
|
+
mapping(address => uint256) private s_pendingWithdrawals;
|
|
181
|
+
|
|
182
|
+
function claimRewards() external {
|
|
183
|
+
uint256 amount = s_pendingWithdrawals[msg.sender];
|
|
184
|
+
if (amount == 0) revert NothingToClaim();
|
|
185
|
+
|
|
186
|
+
s_pendingWithdrawals[msg.sender] = 0;
|
|
187
|
+
IERC20(rewardToken).safeTransfer(msg.sender, amount);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Bad: Push pattern (can be DoS'd)
|
|
191
|
+
function distributeRewards(address[] calldata recipients) external {
|
|
192
|
+
for (uint256 i = 0; i < recipients.length; i++) {
|
|
193
|
+
// If one transfer fails, all fail
|
|
194
|
+
IERC20(rewardToken).safeTransfer(recipients[i], rewards[i]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Emergency Stops
|
|
200
|
+
|
|
201
|
+
```solidity
|
|
202
|
+
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
|
|
203
|
+
|
|
204
|
+
contract Protocol is Pausable, Ownable {
|
|
205
|
+
function deposit(uint256 amount) external whenNotPaused {
|
|
206
|
+
// Normal operation
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function withdraw(uint256 amount) external {
|
|
210
|
+
// Withdrawals always work (no whenNotPaused)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function pause() external onlyOwner {
|
|
214
|
+
_pause();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function unpause() external onlyOwner {
|
|
218
|
+
_unpause();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Timelock for Sensitive Operations
|
|
224
|
+
|
|
225
|
+
```solidity
|
|
226
|
+
uint256 public constant TIMELOCK_DURATION = 2 days;
|
|
227
|
+
|
|
228
|
+
struct PendingChange {
|
|
229
|
+
uint256 value;
|
|
230
|
+
uint256 executeAfter;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
mapping(bytes32 => PendingChange) public pendingChanges;
|
|
234
|
+
|
|
235
|
+
function proposeFeeChange(uint256 newFee) external onlyOwner {
|
|
236
|
+
bytes32 id = keccak256(abi.encode("FEE", newFee));
|
|
237
|
+
pendingChanges[id] = PendingChange({
|
|
238
|
+
value: newFee,
|
|
239
|
+
executeAfter: block.timestamp + TIMELOCK_DURATION
|
|
240
|
+
});
|
|
241
|
+
emit FeeChangeProposed(newFee, block.timestamp + TIMELOCK_DURATION);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function executeFeeChange(uint256 newFee) external onlyOwner {
|
|
245
|
+
bytes32 id = keccak256(abi.encode("FEE", newFee));
|
|
246
|
+
PendingChange memory change = pendingChanges[id];
|
|
247
|
+
|
|
248
|
+
if (change.executeAfter == 0) revert ChangeNotProposed();
|
|
249
|
+
if (block.timestamp < change.executeAfter) revert TimelockNotExpired();
|
|
250
|
+
|
|
251
|
+
delete pendingChanges[id];
|
|
252
|
+
s_fee = newFee;
|
|
253
|
+
emit FeeChanged(newFee);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## OpenZeppelin Contracts
|
|
258
|
+
|
|
259
|
+
### Recommended Imports
|
|
260
|
+
|
|
261
|
+
```solidity
|
|
262
|
+
// Tokens
|
|
263
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
264
|
+
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
265
|
+
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
|
|
266
|
+
|
|
267
|
+
// Security
|
|
268
|
+
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
269
|
+
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
|
|
270
|
+
|
|
271
|
+
// Access
|
|
272
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
273
|
+
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
|
274
|
+
|
|
275
|
+
// Utils
|
|
276
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
277
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
278
|
+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
279
|
+
|
|
280
|
+
// Upgradeable (use with caution)
|
|
281
|
+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
282
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## NatSpec Documentation
|
|
286
|
+
|
|
287
|
+
```solidity
|
|
288
|
+
/// @title Vault - A yield-bearing token vault
|
|
289
|
+
/// @author Protocol Team
|
|
290
|
+
/// @notice Allows users to deposit tokens and earn yield
|
|
291
|
+
/// @dev Implements ERC-4626 tokenized vault standard
|
|
292
|
+
contract Vault is ERC4626 {
|
|
293
|
+
/// @notice Deposits assets and mints shares to receiver
|
|
294
|
+
/// @dev Emits Deposit event
|
|
295
|
+
/// @param assets Amount of underlying tokens to deposit
|
|
296
|
+
/// @param receiver Address to receive the minted shares
|
|
297
|
+
/// @return shares Amount of shares minted
|
|
298
|
+
function deposit(uint256 assets, address receiver)
|
|
299
|
+
public
|
|
300
|
+
override
|
|
301
|
+
returns (uint256 shares)
|
|
302
|
+
{
|
|
303
|
+
// Implementation
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Anti-Patterns to Avoid
|
|
309
|
+
|
|
310
|
+
### Unchecked External Calls
|
|
311
|
+
|
|
312
|
+
```solidity
|
|
313
|
+
// Bad: Ignoring return value
|
|
314
|
+
token.transfer(recipient, amount);
|
|
315
|
+
|
|
316
|
+
// Good: Using SafeERC20
|
|
317
|
+
IERC20(token).safeTransfer(recipient, amount);
|
|
318
|
+
|
|
319
|
+
// Good: Checking return value explicitly
|
|
320
|
+
bool success = token.transfer(recipient, amount);
|
|
321
|
+
if (!success) revert TransferFailed();
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Unbounded Loops
|
|
325
|
+
|
|
326
|
+
```solidity
|
|
327
|
+
// Bad: Can run out of gas
|
|
328
|
+
function processAll() external {
|
|
329
|
+
for (uint256 i = 0; i < users.length; i++) {
|
|
330
|
+
processUser(users[i]);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Good: Paginated processing
|
|
335
|
+
function processBatch(uint256 start, uint256 count) external {
|
|
336
|
+
uint256 end = Math.min(start + count, users.length);
|
|
337
|
+
for (uint256 i = start; i < end; i++) {
|
|
338
|
+
processUser(users[i]);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Floating Pragma
|
|
344
|
+
|
|
345
|
+
```solidity
|
|
346
|
+
// Bad: Any 0.8.x version
|
|
347
|
+
pragma solidity ^0.8.0;
|
|
348
|
+
|
|
349
|
+
// Good: Specific version
|
|
350
|
+
pragma solidity 0.8.20;
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Magic Numbers
|
|
354
|
+
|
|
355
|
+
```solidity
|
|
356
|
+
// Bad: Magic numbers
|
|
357
|
+
if (fee > 10000) revert();
|
|
358
|
+
|
|
359
|
+
// Good: Named constants
|
|
360
|
+
uint256 public constant MAX_FEE_BPS = 10_000; // 100%
|
|
361
|
+
uint256 public constant FEE_DENOMINATOR = 10_000;
|
|
362
|
+
|
|
363
|
+
if (fee > MAX_FEE_BPS) revert FeeExceedsMax(fee, MAX_FEE_BPS);
|
|
364
|
+
```
|