Skip to content

Commit e30e49f

Browse files
committed
forge tests ready
1 parent 9855d1f commit e30e49f

File tree

12 files changed

+2842
-0
lines changed

12 files changed

+2842
-0
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PRIVATE_KEY=your_private_key_here
2+
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/your_key
3+
ETHERSCAN_API_KEY=your_etherscan_key

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main, develop ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Install Foundry
17+
uses: foundry-rs/foundry-toolchain@v1
18+
with:
19+
version: nightly
20+
21+
- name: Build contracts
22+
run: forge build
23+
24+
- name: Run tests
25+
run: forge test
26+

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/forge-std"]
2+
path = lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std

README.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Rock Paper Scissors dApp
2+
3+
A confidential Rock Paper Scissors decentralized application using a commit-reveal scheme for move confidentiality. Players commit their moves as hashes, then reveal within a 2-minute window after both players have committed.
4+
5+
## Features
6+
7+
- **Confidential Moves**: Uses commit-reveal scheme - players can see commitments but not actual moves until reveal
8+
- **Stake-based Gameplay**: Each game requires 0.001 ETH stake from both players
9+
- **Automatic Resolution**: Winner receives full stake (0.002 ETH total)
10+
- **Tie Resolution**: Players can choose rematch or split funds
11+
- **Game Discovery**: Games discoverable via on-chain events
12+
- **Time Limits**: 2-minute reveal phase, 2-minute tie resolution phase
13+
14+
## Prerequisites
15+
16+
- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed
17+
- Git installed
18+
- For testnet deployment: Sepolia ETH in your wallet
19+
20+
## Installation
21+
22+
1. Clone the repository:
23+
```bash
24+
git clone https://github.com/LyonSsS/rps_tests.git
25+
cd rps_tests
26+
```
27+
28+
2. Install test library dependency (forge-std) and set remapping:
29+
```bash
30+
# Add forge-std test utils
31+
forge install foundry-rs/forge-std@v1.9.5
32+
33+
# Ensure remapping exists (once)
34+
printf '\nremappings = ["forge-std/=lib/forge-std/src/"]\n' >> foundry.toml
35+
```
36+
37+
3. (Optional) Clean and build to verify setup:
38+
```bash
39+
forge clean
40+
forge build
41+
```
42+
43+
4. Create `.env` file (copy from `.env.example`):
44+
```bash
45+
cp .env.example .env
46+
```
47+
48+
5. Fill in your `.env` file:
49+
```
50+
PRIVATE_KEY=your_private_key_here
51+
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/your_key
52+
ETHERSCAN_API_KEY=your_etherscan_key
53+
```
54+
55+
Note on PRIVATE_KEY format:
56+
- Use a 0x-prefixed hex private key (example: `0x0123abcd...`).
57+
- If your key is missing the prefix in your current shell session, you can export it as:
58+
```bash
59+
export PRIVATE_KEY=0x$PRIVATE_KEY
60+
```
61+
Then re-run the deploy command.
62+
63+
## Quick Start
64+
65+
### Run All Tests
66+
67+
```bash
68+
forge test
69+
```
70+
71+
This single command runs all tests (unit, integration, and E2E).
72+
73+
### Test Layers (what each suite covers)
74+
75+
- Unit (`test/RockPaperScissors.t.sol`): function-level logic, revert reasons, edge cases, all winner/tie branches, double-reveal, commitment mismatches, fixed-stake enforcement, timeout-claim unit cases.
76+
- Integration (`test/RockPaperScissorsIntegration.t.sol`): full contract flows and state transitions (create → join → reveal → resolve), tie flows (SPLIT/REMATCH), multiple concurrent games, and timeout-claim flows.
77+
- E2E (`test/RockPaperScissorsE2E.t.sol`): client-perspective journeys using events and balance assertions (complete happy path, discovery, cancel, rematch, and timeout-claim where only one player reveals or none reveal).
78+
79+
Rationale: follow a test-pyramid – many fast unit tests, fewer integration, few E2E – for speed, debuggability, and realistic coverage.
80+
81+
### Run Specific Test Suites
82+
83+
```bash
84+
# Unit tests only
85+
forge test --match-path test/RockPaperScissors.t.sol
86+
87+
# Integration tests only
88+
forge test --match-path test/RockPaperScissorsIntegration.t.sol
89+
90+
# E2E tests only
91+
forge test --match-path test/RockPaperScissorsE2E.t.sol
92+
```
93+
94+
### Run Tests with Verbose Output
95+
96+
```bash
97+
forge test -vvv
98+
```
99+
100+
### Build Contracts
101+
102+
```bash
103+
forge build
104+
```
105+
106+
## How It Works
107+
108+
### Game Flow
109+
110+
1. **Game Creation**: Player 1 creates a game with a hashed move commitment and stakes 0.001 ETH
111+
- Game emits `GameCreated` event (discoverable by other players)
112+
- Game stays open indefinitely until someone joins
113+
114+
2. **Game Join**: Player 2 joins with their own move commitment and stakes 0.001 ETH
115+
- Emits `GameJoined` event
116+
- Starts 2-minute reveal phase
117+
118+
3. **Reveal Phase**: Both players reveal their moves within 2 minutes
119+
- Contract verifies commitments match hashes
120+
- Emits `GameRevealed` event
121+
122+
4. **Resolution**:
123+
- If winner: Winner receives full stake (0.002 ETH)
124+
- If tie: Enters 2-minute tie resolution phase
125+
126+
5. **Tie Resolution** (if applicable):
127+
- Both players choose REMATCH or SPLIT
128+
- Both REMATCH: Game restarts (same game ID)
129+
- Otherwise: Funds split (0.001 ETH each)
130+
- Timeout: Auto-split funds
131+
132+
### Example Usage
133+
134+
```solidity
135+
// Generate commitment off-chain
136+
bytes32 salt = keccak256("my_secret_salt");
137+
bytes32 nonce = keccak256("my_nonce");
138+
bytes32 moveHash = keccak256(abi.encodePacked(Move.ROCK, salt, nonce));
139+
140+
// Player 1 creates game
141+
uint256 gameId = rps.createGame{value: 0.001 ether}(moveHash);
142+
143+
// Player 2 joins (after discovering via event)
144+
rps.joinGame{value: 0.001 ether}(gameId, moveHash2);
145+
146+
// Both reveal
147+
rps.reveal(gameId, Move.ROCK, salt, nonce);
148+
rps.reveal(gameId, Move.PAPER, salt2, nonce2);
149+
150+
// Winner automatically receives funds
151+
```
152+
153+
## Contract Functions
154+
155+
### Core Functions
156+
157+
- `createGame(bytes32 moveHash)`: Create a new game with stake
158+
- `joinGame(uint256 gameId, bytes32 moveHash)`: Join an existing game
159+
- `reveal(uint256 gameId, Move move, bytes32 salt, bytes32 nonce)`: Reveal your move
160+
- `handleTie(uint256 gameId, TieChoice choice)`: Handle tie resolution (REMATCH or SPLIT)
161+
- `cancelGame(uint256 gameId)`: Cancel a game if no one joined
162+
163+
### View Functions
164+
165+
- `getGame(uint256 gameId)`: Get game details
166+
- `isRevealDeadlinePassed(uint256 gameId)`: Check if reveal deadline passed
167+
168+
### Events
169+
170+
- `GameCreated(uint256 indexed gameId, address indexed player1, uint256 stake)`
171+
- `GameJoined(uint256 indexed gameId, address indexed player2)`
172+
- `GameRevealed(uint256 indexed gameId, Move player1Move, Move player2Move)`
173+
- `GameResolved(uint256 indexed gameId, address indexed winner, Move move1, Move move2)`
174+
- `TieHandled(uint256 indexed gameId, TieChoice player1Choice, TieChoice player2Choice, bool isRematch)`
175+
- `GameCancelled(uint256 indexed gameId)`
176+
177+
## Testing
178+
179+
### Test Structure
180+
181+
- **Unit Tests** (`test/RockPaperScissors.t.sol`): Test individual functions
182+
- **Integration Tests** (`test/RockPaperScissorsIntegration.t.sol`): Test full game flows
183+
- **E2E Tests** (`test/RockPaperScissorsE2E.t.sol`): Test complete user journeys
184+
185+
See [TEST_STRATEGY.md](./docs/TEST_STRATEGY.md) for detailed testing strategy.
186+
187+
### Test Coverage
188+
189+
Tests cover:
190+
- ✅ All core functions (create, join, reveal, handleTie, cancel)
191+
- ✅ Win/loss logic (all combinations)
192+
- ✅ Edge cases (timeouts, invalid commitments, double operations)
193+
- ✅ State transitions
194+
- ✅ Fund transfers
195+
- ✅ Event emissions
196+
197+
## Deployment
198+
199+
### Local (Anvil)
200+
201+
```bash
202+
# Start Anvil
203+
anvil
204+
205+
# Deploy (in another terminal)
206+
forge script script/Deploy.s.sol:DeployScript --rpc-url http://localhost:8545 --broadcast
207+
```
208+
209+
### Sepolia Testnet
210+
211+
```bash
212+
forge script script/Deploy.s.sol:DeployScript --rpc-url $SEPOLIA_RPC_URL --broadcast --verify
213+
```
214+
215+
## Project Structure
216+
217+
```
218+
.
219+
├── src/
220+
│ └── RockPaperScissors.sol # Main contract
221+
├── test/
222+
│ ├── RockPaperScissors.t.sol # Unit tests
223+
│ ├── RockPaperScissorsIntegration.t.sol # Integration tests
224+
│ └── RockPaperScissorsE2E.t.sol # E2E tests
225+
├── script/
226+
│ └── Deploy.s.sol # Deployment script
227+
├── docs/
228+
│ └── TEST_STRATEGY.md # Testing strategy document
229+
├── .github/workflows/
230+
│ └── ci.yml # CI/CD pipeline
231+
├── foundry.toml # Foundry configuration
232+
├── .env.example # Environment variables template
233+
└── README.md # This file
234+
```
235+
236+
## Security Considerations
237+
238+
- **Commit-Reveal**: Prevents front-running by hiding moves until both committed
239+
- **Deadline Enforcement**: Prevents indefinite games
240+
- **Automatic Split**: Tie resolution timeout prevents stuck games
241+
- **Access Control**: Only authorized players can interact with their games
242+
243+
## License
244+
245+
MIT
246+
247+
## Author
248+
249+
Homework submission for confidential dApp with comprehensive testing strategy.
250+

foundry.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"lib\\forge-std": {
3+
"tag": {
4+
"name": "v1.9.5",
5+
"rev": "b93cf4bc34ff214c099dc970b153f85ade8c9f66"
6+
}
7+
}
8+
}

foundry.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
solc = "0.8.23"
6+
optimizer = true
7+
optimizer_runs = 200
8+
via_ir = false
9+
10+
[profile.ci]
11+
fuzz = { runs = 256 }
12+
13+
[rpc_endpoints]
14+
anvil = "http://localhost:8545"
15+
sepolia = "${SEPOLIA_RPC_URL}"
16+
17+
[etherscan]
18+
sepolia = { key = "${ETHERSCAN_API_KEY}" }
19+

lib/forge-std

Submodule forge-std added at b93cf4b

script/Deploy.s.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {RockPaperScissors} from "../src/RockPaperScissors.sol";
6+
7+
contract DeployScript is Script {
8+
function run() external returns (RockPaperScissors) {
9+
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
10+
vm.startBroadcast(deployerPrivateKey);
11+
12+
console.log("Deploying RockPaperScissors...");
13+
RockPaperScissors rps = new RockPaperScissors();
14+
15+
console.log("RockPaperScissors deployed at:", address(rps));
16+
17+
vm.stopBroadcast();
18+
return rps;
19+
}
20+
}
21+

0 commit comments

Comments
 (0)