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,415 @@
|
|
|
1
|
+
# Smart Contract Testing
|
|
2
|
+
|
|
3
|
+
Comprehensive testing strategies using Foundry and best practices for contract verification.
|
|
4
|
+
|
|
5
|
+
## Testing Philosophy
|
|
6
|
+
|
|
7
|
+
1. **Test behavior, not implementation** - Focus on what the contract does
|
|
8
|
+
2. **100% branch coverage minimum** - Every code path tested
|
|
9
|
+
3. **Fuzz everything** - Random inputs catch edge cases humans miss
|
|
10
|
+
4. **Invariants are mandatory** - Properties that must always hold
|
|
11
|
+
5. **Fork tests for integrations** - Test against real protocols
|
|
12
|
+
|
|
13
|
+
## Foundry Test Structure
|
|
14
|
+
|
|
15
|
+
### Directory Organization
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
test/
|
|
19
|
+
├── unit/ # Isolated function tests
|
|
20
|
+
│ ├── Vault.deposit.t.sol
|
|
21
|
+
│ ├── Vault.withdraw.t.sol
|
|
22
|
+
│ └── Vault.fees.t.sol
|
|
23
|
+
├── integration/ # Multi-contract tests
|
|
24
|
+
│ └── VaultWithOracle.t.sol
|
|
25
|
+
├── invariant/ # Invariant/stateful fuzz tests
|
|
26
|
+
│ ├── VaultInvariant.t.sol
|
|
27
|
+
│ └── handlers/
|
|
28
|
+
│ └── VaultHandler.sol
|
|
29
|
+
├── fork/ # Mainnet fork tests
|
|
30
|
+
│ └── UniswapIntegration.t.sol
|
|
31
|
+
└── mocks/ # Test doubles
|
|
32
|
+
└── MockOracle.sol
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Basic Test Structure
|
|
36
|
+
|
|
37
|
+
```solidity
|
|
38
|
+
// SPDX-License-Identifier: MIT
|
|
39
|
+
pragma solidity ^0.8.20;
|
|
40
|
+
|
|
41
|
+
import {Test, console} from "forge-std/Test.sol";
|
|
42
|
+
import {Vault} from "src/Vault.sol";
|
|
43
|
+
import {MockERC20} from "test/mocks/MockERC20.sol";
|
|
44
|
+
|
|
45
|
+
contract VaultDepositTest is Test {
|
|
46
|
+
// Contracts
|
|
47
|
+
Vault public vault;
|
|
48
|
+
MockERC20 public token;
|
|
49
|
+
|
|
50
|
+
// Actors
|
|
51
|
+
address public owner = makeAddr("owner");
|
|
52
|
+
address public user = makeAddr("user");
|
|
53
|
+
address public attacker = makeAddr("attacker");
|
|
54
|
+
|
|
55
|
+
// Constants
|
|
56
|
+
uint256 public constant INITIAL_BALANCE = 1000e18;
|
|
57
|
+
|
|
58
|
+
function setUp() public {
|
|
59
|
+
// Deploy contracts
|
|
60
|
+
token = new MockERC20("Token", "TKN", 18);
|
|
61
|
+
vault = new Vault(address(token));
|
|
62
|
+
|
|
63
|
+
// Setup actors
|
|
64
|
+
token.mint(user, INITIAL_BALANCE);
|
|
65
|
+
vm.prank(user);
|
|
66
|
+
token.approve(address(vault), type(uint256).max);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function test_deposit_success() public {
|
|
70
|
+
uint256 amount = 100e18;
|
|
71
|
+
|
|
72
|
+
vm.prank(user);
|
|
73
|
+
vault.deposit(amount, user);
|
|
74
|
+
|
|
75
|
+
assertEq(vault.balanceOf(user), amount);
|
|
76
|
+
assertEq(token.balanceOf(address(vault)), amount);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function test_deposit_emitsEvent() public {
|
|
80
|
+
uint256 amount = 100e18;
|
|
81
|
+
|
|
82
|
+
vm.expectEmit(true, true, false, true);
|
|
83
|
+
emit Vault.Deposited(user, amount);
|
|
84
|
+
|
|
85
|
+
vm.prank(user);
|
|
86
|
+
vault.deposit(amount, user);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function test_deposit_revertsOnZeroAmount() public {
|
|
90
|
+
vm.expectRevert(Vault.ZeroAmount.selector);
|
|
91
|
+
|
|
92
|
+
vm.prank(user);
|
|
93
|
+
vault.deposit(0, user);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function test_deposit_revertsOnZeroAddress() public {
|
|
97
|
+
vm.expectRevert(Vault.ZeroAddress.selector);
|
|
98
|
+
|
|
99
|
+
vm.prank(user);
|
|
100
|
+
vault.deposit(100e18, address(0));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Fuzz Testing
|
|
106
|
+
|
|
107
|
+
### Basic Fuzz Tests
|
|
108
|
+
|
|
109
|
+
```solidity
|
|
110
|
+
function testFuzz_deposit(uint256 amount) public {
|
|
111
|
+
// Bound inputs to reasonable ranges
|
|
112
|
+
amount = bound(amount, 1, INITIAL_BALANCE);
|
|
113
|
+
|
|
114
|
+
vm.prank(user);
|
|
115
|
+
vault.deposit(amount, user);
|
|
116
|
+
|
|
117
|
+
assertEq(vault.balanceOf(user), amount);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function testFuzz_withdraw(uint256 depositAmount, uint256 withdrawAmount) public {
|
|
121
|
+
depositAmount = bound(depositAmount, 1, INITIAL_BALANCE);
|
|
122
|
+
withdrawAmount = bound(withdrawAmount, 1, depositAmount);
|
|
123
|
+
|
|
124
|
+
// Setup
|
|
125
|
+
vm.startPrank(user);
|
|
126
|
+
vault.deposit(depositAmount, user);
|
|
127
|
+
|
|
128
|
+
// Action
|
|
129
|
+
vault.withdraw(withdrawAmount, user, user);
|
|
130
|
+
vm.stopPrank();
|
|
131
|
+
|
|
132
|
+
// Assert
|
|
133
|
+
assertEq(vault.balanceOf(user), depositAmount - withdrawAmount);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Fuzz Test with Assumptions
|
|
138
|
+
|
|
139
|
+
```solidity
|
|
140
|
+
function testFuzz_transfer(address to, uint256 amount) public {
|
|
141
|
+
// Skip invalid addresses
|
|
142
|
+
vm.assume(to != address(0));
|
|
143
|
+
vm.assume(to != address(vault));
|
|
144
|
+
vm.assume(to != user);
|
|
145
|
+
|
|
146
|
+
amount = bound(amount, 1, INITIAL_BALANCE);
|
|
147
|
+
|
|
148
|
+
// Deposit first
|
|
149
|
+
vm.prank(user);
|
|
150
|
+
vault.deposit(amount, user);
|
|
151
|
+
|
|
152
|
+
// Transfer
|
|
153
|
+
vm.prank(user);
|
|
154
|
+
vault.transfer(to, amount);
|
|
155
|
+
|
|
156
|
+
assertEq(vault.balanceOf(to), amount);
|
|
157
|
+
assertEq(vault.balanceOf(user), 0);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Invariant Testing
|
|
162
|
+
|
|
163
|
+
### Handler Contract
|
|
164
|
+
|
|
165
|
+
```solidity
|
|
166
|
+
// test/invariant/handlers/VaultHandler.sol
|
|
167
|
+
contract VaultHandler is Test {
|
|
168
|
+
Vault public vault;
|
|
169
|
+
MockERC20 public token;
|
|
170
|
+
|
|
171
|
+
address[] public actors;
|
|
172
|
+
address internal currentActor;
|
|
173
|
+
|
|
174
|
+
// Ghost variables for tracking
|
|
175
|
+
uint256 public ghost_totalDeposited;
|
|
176
|
+
uint256 public ghost_totalWithdrawn;
|
|
177
|
+
|
|
178
|
+
modifier useActor(uint256 actorSeed) {
|
|
179
|
+
currentActor = actors[bound(actorSeed, 0, actors.length - 1)];
|
|
180
|
+
vm.startPrank(currentActor);
|
|
181
|
+
_;
|
|
182
|
+
vm.stopPrank();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
constructor(Vault _vault, MockERC20 _token) {
|
|
186
|
+
vault = _vault;
|
|
187
|
+
token = _token;
|
|
188
|
+
|
|
189
|
+
// Create actors
|
|
190
|
+
for (uint256 i = 0; i < 5; i++) {
|
|
191
|
+
address actor = makeAddr(string(abi.encodePacked("actor", i)));
|
|
192
|
+
actors.push(actor);
|
|
193
|
+
token.mint(actor, 1000e18);
|
|
194
|
+
vm.prank(actor);
|
|
195
|
+
token.approve(address(vault), type(uint256).max);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function deposit(uint256 actorSeed, uint256 amount) public useActor(actorSeed) {
|
|
200
|
+
amount = bound(amount, 0, token.balanceOf(currentActor));
|
|
201
|
+
if (amount == 0) return;
|
|
202
|
+
|
|
203
|
+
vault.deposit(amount, currentActor);
|
|
204
|
+
ghost_totalDeposited += amount;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function withdraw(uint256 actorSeed, uint256 amount) public useActor(actorSeed) {
|
|
208
|
+
amount = bound(amount, 0, vault.balanceOf(currentActor));
|
|
209
|
+
if (amount == 0) return;
|
|
210
|
+
|
|
211
|
+
vault.withdraw(amount, currentActor, currentActor);
|
|
212
|
+
ghost_totalWithdrawn += amount;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Invariant Test Contract
|
|
218
|
+
|
|
219
|
+
```solidity
|
|
220
|
+
// test/invariant/VaultInvariant.t.sol
|
|
221
|
+
contract VaultInvariantTest is Test {
|
|
222
|
+
Vault public vault;
|
|
223
|
+
MockERC20 public token;
|
|
224
|
+
VaultHandler public handler;
|
|
225
|
+
|
|
226
|
+
function setUp() public {
|
|
227
|
+
token = new MockERC20("Token", "TKN", 18);
|
|
228
|
+
vault = new Vault(address(token));
|
|
229
|
+
handler = new VaultHandler(vault, token);
|
|
230
|
+
|
|
231
|
+
// Target only the handler
|
|
232
|
+
targetContract(address(handler));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Invariant: Total assets >= total shares (no inflation)
|
|
236
|
+
function invariant_noShareInflation() public view {
|
|
237
|
+
assertGe(
|
|
238
|
+
token.balanceOf(address(vault)),
|
|
239
|
+
vault.totalSupply()
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Invariant: Sum of balances == total supply
|
|
244
|
+
function invariant_balancesMatchTotalSupply() public view {
|
|
245
|
+
uint256 sum;
|
|
246
|
+
address[] memory actors = handler.getActors();
|
|
247
|
+
|
|
248
|
+
for (uint256 i = 0; i < actors.length; i++) {
|
|
249
|
+
sum += vault.balanceOf(actors[i]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
assertEq(sum, vault.totalSupply());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Invariant: Deposits - withdrawals == vault balance
|
|
256
|
+
function invariant_depositWithdrawAccounting() public view {
|
|
257
|
+
assertEq(
|
|
258
|
+
handler.ghost_totalDeposited() - handler.ghost_totalWithdrawn(),
|
|
259
|
+
token.balanceOf(address(vault))
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Fork Testing
|
|
266
|
+
|
|
267
|
+
### Setup Fork
|
|
268
|
+
|
|
269
|
+
```solidity
|
|
270
|
+
contract UniswapForkTest is Test {
|
|
271
|
+
// Mainnet addresses
|
|
272
|
+
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
|
273
|
+
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
|
274
|
+
address constant UNISWAP_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
|
|
275
|
+
|
|
276
|
+
uint256 mainnetFork;
|
|
277
|
+
|
|
278
|
+
function setUp() public {
|
|
279
|
+
// Create fork at specific block for reproducibility
|
|
280
|
+
mainnetFork = vm.createFork(vm.envString("ETH_RPC_URL"), 18_500_000);
|
|
281
|
+
vm.selectFork(mainnetFork);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function test_swapOnUniswap() public {
|
|
285
|
+
// Get some WETH
|
|
286
|
+
deal(WETH, address(this), 10 ether);
|
|
287
|
+
|
|
288
|
+
// Approve router
|
|
289
|
+
IERC20(WETH).approve(UNISWAP_ROUTER, 10 ether);
|
|
290
|
+
|
|
291
|
+
// Execute swap
|
|
292
|
+
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
293
|
+
tokenIn: WETH,
|
|
294
|
+
tokenOut: USDC,
|
|
295
|
+
fee: 3000,
|
|
296
|
+
recipient: address(this),
|
|
297
|
+
deadline: block.timestamp,
|
|
298
|
+
amountIn: 1 ether,
|
|
299
|
+
amountOutMinimum: 0,
|
|
300
|
+
sqrtPriceLimitX96: 0
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
uint256 amountOut = ISwapRouter(UNISWAP_ROUTER).exactInputSingle(params);
|
|
304
|
+
|
|
305
|
+
assertGt(amountOut, 0);
|
|
306
|
+
assertGt(IERC20(USDC).balanceOf(address(this)), 0);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Testing Utilities
|
|
312
|
+
|
|
313
|
+
### Useful Cheatcodes
|
|
314
|
+
|
|
315
|
+
```solidity
|
|
316
|
+
// Time manipulation
|
|
317
|
+
vm.warp(block.timestamp + 1 days); // Set timestamp
|
|
318
|
+
skip(1 hours); // Advance time
|
|
319
|
+
|
|
320
|
+
// Block manipulation
|
|
321
|
+
vm.roll(block.number + 100); // Set block number
|
|
322
|
+
|
|
323
|
+
// Account manipulation
|
|
324
|
+
vm.prank(user); // Next call from user
|
|
325
|
+
vm.startPrank(user); // All calls from user
|
|
326
|
+
vm.stopPrank(); // Reset caller
|
|
327
|
+
|
|
328
|
+
// Balance manipulation
|
|
329
|
+
deal(address(token), user, 1000e18); // Set ERC20 balance
|
|
330
|
+
deal(user, 100 ether); // Set ETH balance
|
|
331
|
+
|
|
332
|
+
// Expect revert
|
|
333
|
+
vm.expectRevert(); // Any revert
|
|
334
|
+
vm.expectRevert("Error message"); // Specific message
|
|
335
|
+
vm.expectRevert(Contract.CustomError.selector); // Custom error
|
|
336
|
+
vm.expectRevert(abi.encodeWithSelector(
|
|
337
|
+
Contract.CustomError.selector, arg1, arg2
|
|
338
|
+
)); // Custom error with args
|
|
339
|
+
|
|
340
|
+
// Expect emit
|
|
341
|
+
vm.expectEmit(true, true, false, true); // Check topics 1, 2, 4
|
|
342
|
+
emit ExpectedEvent(indexed1, indexed2, data);
|
|
343
|
+
|
|
344
|
+
// Snapshot/revert state
|
|
345
|
+
uint256 snapshot = vm.snapshot();
|
|
346
|
+
// ... make changes ...
|
|
347
|
+
vm.revertTo(snapshot); // Restore state
|
|
348
|
+
|
|
349
|
+
// Labels for traces
|
|
350
|
+
vm.label(address(vault), "Vault");
|
|
351
|
+
vm.label(user, "User");
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Custom Assertions
|
|
355
|
+
|
|
356
|
+
```solidity
|
|
357
|
+
function assertApproxEq(
|
|
358
|
+
uint256 a,
|
|
359
|
+
uint256 b,
|
|
360
|
+
uint256 maxDelta,
|
|
361
|
+
string memory err
|
|
362
|
+
) internal {
|
|
363
|
+
uint256 delta = a > b ? a - b : b - a;
|
|
364
|
+
if (delta > maxDelta) {
|
|
365
|
+
emit log_named_string("Error", err);
|
|
366
|
+
emit log_named_uint(" Expected", b);
|
|
367
|
+
emit log_named_uint(" Actual", a);
|
|
368
|
+
emit log_named_uint(" MaxDelta", maxDelta);
|
|
369
|
+
emit log_named_uint(" Delta", delta);
|
|
370
|
+
fail();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Usage
|
|
375
|
+
assertApproxEq(actualShares, expectedShares, 1, "Shares mismatch");
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Running Tests
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
# Run all tests
|
|
382
|
+
forge test
|
|
383
|
+
|
|
384
|
+
# Run specific test file
|
|
385
|
+
forge test --match-path test/unit/Vault.deposit.t.sol
|
|
386
|
+
|
|
387
|
+
# Run specific test
|
|
388
|
+
forge test --match-test test_deposit_success
|
|
389
|
+
|
|
390
|
+
# Run with verbosity
|
|
391
|
+
forge test -vvvv # Show traces
|
|
392
|
+
|
|
393
|
+
# Run with gas report
|
|
394
|
+
forge test --gas-report
|
|
395
|
+
|
|
396
|
+
# Run fork tests (requires RPC URL)
|
|
397
|
+
ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/KEY forge test --match-path test/fork/
|
|
398
|
+
|
|
399
|
+
# Run invariant tests
|
|
400
|
+
forge test --match-path test/invariant/
|
|
401
|
+
|
|
402
|
+
# Coverage
|
|
403
|
+
forge coverage
|
|
404
|
+
forge coverage --report lcov
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Test Coverage Requirements
|
|
408
|
+
|
|
409
|
+
| Test Type | Coverage Target | Purpose |
|
|
410
|
+
|-----------|-----------------|---------|
|
|
411
|
+
| Unit | 100% branches | Verify individual functions |
|
|
412
|
+
| Fuzz | 10,000+ runs | Find edge cases |
|
|
413
|
+
| Invariant | 10,000+ calls | Verify system properties |
|
|
414
|
+
| Fork | Critical paths | Verify integrations |
|
|
415
|
+
| E2E | Happy paths | Verify user flows |
|