Some checks are pending
Deploy to VPS / deploy (push) Waiting to run
- Update RPSArena.sol: move bet payment to commit() instead of createMatch() - Fix Hardhat tests to match new contract API (7/7 passing) - Deploy updated contract to local Hardhat node (0xe7f1725...) - Add blockchain service (server) listening to contract events - Create matches on-chain from server matchmaking handler - Frontend calls contract directly for commit/reveal via MetaMask - Add Prisma DB persistence for match results and player stats - Copy ABI to shared package for frontend/server consumption - Update deployment docs and architecture docs Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
115 lines
4.3 KiB
TypeScript
115 lines
4.3 KiB
TypeScript
import { expect } from 'chai';
|
|
import '@nomicfoundation/hardhat-chai-matchers';
|
|
import hre from 'hardhat';
|
|
import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers';
|
|
import { keccak256, solidityPacked } from 'ethers';
|
|
|
|
describe('RPSArena', () => {
|
|
async function deployFixture() {
|
|
const [owner, p1, p2] = await hre.ethers.getSigners();
|
|
const RPSArena = await hre.ethers.getContractFactory('RPSArena');
|
|
const rps = await RPSArena.deploy();
|
|
return { rps, owner, p1, p2 };
|
|
}
|
|
|
|
function hashChoice(choice: number, nonce: bigint) {
|
|
return keccak256(solidityPacked(['uint256', 'uint256'], [choice, nonce]));
|
|
}
|
|
|
|
describe('Create Match', () => {
|
|
it('should create a match with a bet', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
const bet = hre.ethers.parseEther('0.01');
|
|
await expect(rps.connect(p1).createMatch(p2.address, bet))
|
|
.to.emit(rps, 'MatchCreated')
|
|
.withArgs(1, p1.address, p2.address, bet);
|
|
const m = await rps.matches(1);
|
|
expect(m.player1).to.eq(p1.address);
|
|
expect(m.bet).to.eq(bet);
|
|
});
|
|
|
|
it('should revert with zero bet', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
await expect(rps.connect(p1).createMatch(p2.address, 0)).to.be.revertedWith('Bet must be > 0');
|
|
});
|
|
});
|
|
|
|
describe('Commit & Reveal', () => {
|
|
it('should allow commit from both players', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
const bet = hre.ethers.parseEther('0.01');
|
|
await rps.connect(p1).createMatch(p2.address, bet);
|
|
|
|
const nonce1 = 123n;
|
|
const nonce2 = 456n;
|
|
const hash1 = hashChoice(1, nonce1); // Rock
|
|
const hash2 = hashChoice(2, nonce2); // Paper
|
|
|
|
await rps.connect(p1).commit(1, hash1, { value: bet });
|
|
await rps.connect(p2).commit(1, hash2, { value: bet });
|
|
|
|
const m = await rps.matches(1);
|
|
expect(m.status).to.eq(1); // Committed
|
|
});
|
|
|
|
it('should finalize with correct winner', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
const bet = hre.ethers.parseEther('0.01');
|
|
await rps.connect(p1).createMatch(p2.address, bet);
|
|
|
|
const nonce1 = 123n;
|
|
const nonce2 = 456n;
|
|
await rps.connect(p1).commit(1, hashChoice(1, nonce1), { value: bet }); // Rock
|
|
await rps.connect(p2).commit(1, hashChoice(3, nonce2), { value: bet }); // Scissors
|
|
|
|
await expect(rps.connect(p1).reveal(1, 1, nonce1))
|
|
.to.emit(rps, 'Revealed')
|
|
.withArgs(1, p1.address, 1);
|
|
|
|
await expect(rps.connect(p2).reveal(1, 3, nonce2))
|
|
.to.emit(rps, 'MatchFinished')
|
|
.withArgs(1, p1.address, hre.ethers.parseEther('0.0194')); // 97% of 0.02
|
|
});
|
|
|
|
it('should reject invalid reveal', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
const bet = hre.ethers.parseEther('0.01');
|
|
await rps.connect(p1).createMatch(p2.address, bet);
|
|
|
|
const nonce1 = 123n;
|
|
await rps.connect(p1).commit(1, hashChoice(1, nonce1), { value: bet });
|
|
await rps.connect(p2).commit(1, hashChoice(2, 456n), { value: bet });
|
|
|
|
await expect(rps.connect(p1).reveal(1, 2, nonce1)).to.be.revertedWith('Invalid reveal');
|
|
});
|
|
|
|
it('should handle draw', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
const bet = hre.ethers.parseEther('0.01');
|
|
await rps.connect(p1).createMatch(p2.address, bet);
|
|
|
|
const nonce = 111n;
|
|
await rps.connect(p1).commit(1, hashChoice(1, nonce), { value: bet });
|
|
await rps.connect(p2).commit(1, hashChoice(1, nonce), { value: bet });
|
|
|
|
await rps.connect(p1).reveal(1, 1, nonce);
|
|
await expect(rps.connect(p2).reveal(1, 1, nonce))
|
|
.to.emit(rps, 'MatchDraw')
|
|
.withArgs(1, bet);
|
|
});
|
|
});
|
|
|
|
describe('Timeout', () => {
|
|
it('should allow timeout claim if no commit', async () => {
|
|
const { rps, p1, p2 } = await loadFixture(deployFixture);
|
|
const bet = hre.ethers.parseEther('0.01');
|
|
await rps.connect(p1).createMatch(p2.address, bet);
|
|
|
|
await hre.network.provider.send('evm_increaseTime', [301]);
|
|
await hre.network.provider.send('evm_mine');
|
|
|
|
await expect(rps.connect(p1).claimTimeout(1))
|
|
.to.emit(rps, 'MatchTimeout');
|
|
});
|
|
});
|
|
});
|