|
| 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 | + |
0 commit comments