Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions KNOWN_BUGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,47 @@ Submitting multiple transactions in parallel led to inconsistent contract state

---

### 14. **Event Listener Memory Leaks** in Multiple Components **(FIXED)**
```11:16:src/components/roulette/MostRecentSpinResults.js
rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
```
```17:23:src/components/roulette/SpinResult.js
rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
```
Event listeners were registered on every render without cleanup, causing memory leaks and duplicate event handling. Fixed by moving listeners to useEffect with proper cleanup functions.

---

### 15. **Infinite Re-render Loop** in NumbersHitTracker **(FIXED)**
```11:16:src/components/roulette/NumbersHitTracker.js
}, [props.playerAddress, currentSet]);
```
Component had `currentSet` in dependency array while also updating it, causing infinite loops. Fixed by removing from dependencies and adding proper event-based updates with debouncing.

---

### 16. **Race Condition** Between Frontend and Blockchain **(FIXED)**
```107:136:src/components/roulette/Roulette.js
getRandomWheelNumber(`${Date.now()}${playerAddress}`)
```
Frontend generated random numbers locally before blockchain confirmation, creating inconsistent state. Fixed by waiting for blockchain events and using actual blockchain results.

---

### 17. **Missing Transaction Error Handling** **(FIXED)**
```90:137:src/components/roulette/Roulette.js
executeWager(playerAddress).then((response) => {
```
No error handling for failed blockchain transactions. Added try-catch blocks and user-friendly error messages.

---

### 18. **Precision Issues** with ETH Balance Comparisons **(FIXED)**
```78:82:src/components/roulette/Roulette.js
if (currentChipAmountSelected > availableBalance) {
```
Using parseFloat for ETH values caused precision errors. Fixed by using ethers.js BigNumber for all balance calculations.

---

*(End of file – please update as additional bugs are discovered)*
115 changes: 115 additions & 0 deletions ROULETTE_BUG_FIXES_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Roulette Game Bug Fixes Summary

## Overview
This document summarizes the bugs found and fixed in the Roulette game's interaction between the UI, service layer, and blockchain.

## Bugs Fixed

### 1. Event Listener Memory Leaks
**Problem:** Multiple components (`MostRecentSpinResults`, `SpinResult`) were registering event listeners on every render without cleanup, causing memory leaks and duplicate event handling.

**Solution:**
- Moved event listener registration to `useEffect` hooks
- Added cleanup functions to remove listeners on unmount
- Used functional state updates to avoid stale closures

**Files Modified:**
- `src/components/roulette/MostRecentSpinResults.js`
- `src/components/roulette/SpinResult.js`

### 2. Infinite Re-render Loop in NumbersHitTracker
**Problem:** Component had `currentSet` in its dependency array while also updating it, causing infinite re-renders.

**Solution:**
- Removed `currentSet` from dependency array
- Added event-based updates with debouncing
- Used `useRef` for timer management

**File Modified:**
- `src/components/roulette/NumbersHitTracker.js`

### 3. Race Condition Between Frontend and Blockchain
**Problem:** Frontend was generating random numbers locally before blockchain confirmation, creating inconsistent state where UI showed different results than what was recorded on blockchain.

**Solution:**
- Removed local random number generation
- Wait for blockchain event (`ExecutedWager`) to get actual result
- Calculate results based on blockchain data, not predicted values

**File Modified:**
- `src/components/roulette/Roulette.js`

### 4. Missing Transaction Error Handling
**Problem:** No error handling for failed blockchain transactions, leaving users confused when transactions failed.

**Solution:**
- Added try-catch blocks around blockchain calls
- Added user-friendly error messages
- Properly reset spinning state on errors
- Clean up event listeners on error

**File Modified:**
- `src/components/roulette/Roulette.js`

### 5. Precision Issues with ETH Balance Comparisons
**Problem:** Using `parseFloat` for ETH values caused precision errors when comparing balances.

**Solution:**
- Switched to using `ethers.BigNumber` for all balance calculations
- Used proper Wei conversions with `parseEther`
- Ensured all comparisons use BigNumber methods

**File Modified:**
- `src/components/roulette/Roulette.js`

### 6. Transaction Confirmation Not Awaited
**Problem:** The code wasn't waiting for transaction confirmation, potentially showing success before the transaction was mined.

**Solution:**
- Added `await tx.wait()` to wait for transaction confirmation
- Only update UI after confirmation is received

**File Modified:**
- `src/components/roulette/Roulette.js`

## Tests Created

### 1. Integration Tests (`Roulette.integration.test.js`)
Comprehensive tests covering:
- Complete betting workflow
- Balance validation
- Blockchain interaction
- Error handling
- Event listener cleanup
- Precision handling with BigNumbers

### 2. Event Listener Tests (`EventListenerComponents.test.js`)
Tests for components using blockchain events:
- Event registration and cleanup
- Proper state updates from events
- Debouncing behavior
- Error handling

### 3. Bet Results Tests (`BetResultsCalculation.test.js`)
Tests for betting logic:
- Winning calculations for all bet types
- Multiple bets handling
- Display of results
- Special cases (0 and 00)

## Key Improvements

1. **Reliability:** Eliminated race conditions and memory leaks
2. **Consistency:** UI now always reflects actual blockchain state
3. **User Experience:** Added proper error handling and feedback
4. **Precision:** Fixed decimal precision issues with ETH values
5. **Maintainability:** Added comprehensive test coverage

## Verification Steps

To verify the fixes:
1. Place bets and spin - results should match blockchain events
2. Check browser console - no memory leak warnings
3. Try invalid operations - proper error messages should appear
4. Check balances - should be precise even with many decimals
5. Run tests - all should pass
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
"@nomiclabs/hardhat-ethers": "^2.2.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"hardhat": "^2.13.0",
Expand Down
2 changes: 1 addition & 1 deletion src/components/roulette/ClickableBet.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function ClickableBet(props) {
</div>
<Chip
auxiliaryClassName="betting-square-chip"
key={props.chipAmount}
key={props.betAmount}
onClick={props.onClick}
chipAmount={props.betAmount}
/>
Expand Down
26 changes: 16 additions & 10 deletions src/components/roulette/MostRecentSpinResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@ const CLASS_NAME = "MostRecentSpinResults-component";
export function MostRecentSpinResults(props) {
const [spinResults, setSpinResults] = useState([]);

rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
const copySpinResults = [...spinResults];
copySpinResults.push(parseInt(wheelNumber, 10));
setSpinResults(copySpinResults.slice(-20)); // Only keep the last 20 results
}
});

useEffect(() => {
// render
}, [spinResults, props.playerAddress]);
const handleExecutedWager = (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
setSpinResults(prevResults => {
const newResults = [...prevResults, parseInt(wheelNumber, 10)];
return newResults.slice(-20); // Only keep the last 20 results
});
}
};

rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Cleanup function to remove event listener
return () => {
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
};
}, [props.playerAddress]);

return (
<div
Expand Down
45 changes: 38 additions & 7 deletions src/components/roulette/NumbersHitTracker.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import { useEffect, useState } from 'react';
import { useEffect, useState, useRef } from 'react';

import { WHEEL_NUMBERS } from "../../common/wheelNumbers";

import { getPlayerNumberCompletionSetCurrent } from "../../common/blockchainWrapper";
import { getPlayerNumberCompletionSetCurrent, rouletteContractEvents } from "../../common/blockchainWrapper";

const CLASS_NAME = "NumbersHitTracker-component";
export function NumbersHitTracker(props) {
const [currentSet, setCurrentSet] = useState(new Set());
const timerRef = useRef(null);

useEffect(() => {
setTimeout(async () => {
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
}, 1000);
}, [props.playerAddress, currentSet]);
const fetchCurrentSet = async () => {
try {
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
} catch (error) {
console.error("Error fetching current number set:", error);
}
};

// Initial fetch
fetchCurrentSet();

// Listen for new spins
const handleExecutedWager = (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
// Debounce the fetch to avoid rapid updates
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
fetchCurrentSet();
}, 500);
}
};

rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Cleanup
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
};
}, [props.playerAddress]);

return (
<div
Expand Down
Loading
Loading