Skip to content

Commit 2fabb5c

Browse files
committed
add slithermoney
1 parent b48bc74 commit 2fabb5c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+18415
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PRIVATE_KEY=
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Node modules
2+
/node_modules
3+
4+
# Compilation output
5+
/dist
6+
7+
# pnpm deploy output
8+
/bundle
9+
10+
# Hardhat Build Artifacts
11+
/artifacts
12+
13+
# Hardhat compilation (v2) support directory
14+
/cache
15+
16+
# Typechain output
17+
/types
18+
19+
# Hardhat coverage reports
20+
/coverage
21+
22+
.env
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
config:
2+
3+
cp .env.example .env
4+
5+
6+
npx hardhat run scripts/deploy.ts --build-profile production --network base-sepolia
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const config = {
2+
84532: { // basesepolia
3+
entropyV2Address: "0x41c9e39574f40ad34c79f1c99b66a45efb830d4c",
4+
usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
5+
wagerAmount: "0.01", // 1 cent
6+
},
7+
114: { // flare-coston2
8+
entropyV2Address: "0x41c9e39574f40ad34c79f1c99b66a45efb830d4c",
9+
usdcAddress: "0x573d34311044b51246d022c326b12cd2dffc82a1",
10+
wagerAmount: "10", // 1 cent
11+
},
12+
};
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
import { IEntropyConsumer } from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
5+
import { IEntropyV2 } from "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol";
6+
import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
7+
import {RandomNumberV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/RandomNumberV2Interface.sol";
8+
9+
interface IERC20 {
10+
function transferFrom(address from, address to, uint256 amount) external returns (bool);
11+
function transfer(address to, uint256 amount) external returns (bool);
12+
function balanceOf(address account) external view returns (uint256);
13+
}
14+
15+
// @param entropyAddress The address of the entropy contract.
16+
// @param wagerAmount The wager amount in wei.
17+
// @param usdcAddress The address of the USDC token contract.
18+
contract Snake is IEntropyConsumer {
19+
IEntropyV2 public entropy;
20+
bytes32 public number;
21+
uint256 public numberAsUint;
22+
uint256 public wagerAmount;
23+
address public usdcAddress;
24+
bool public isFlare;
25+
// Player balances and ready states
26+
uint256 public player1Balance;
27+
uint256 public player2Balance;
28+
bool public player1Ready;
29+
bool public player2Ready;
30+
31+
// Flare Random Number V2
32+
RandomNumberV2Interface public randomV2;
33+
34+
constructor(address entropyAddress, uint256 _wagerAmount, address _usdcAddress, bool _isFlare) {
35+
entropy = IEntropyV2(entropyAddress);
36+
wagerAmount = _wagerAmount;
37+
usdcAddress = _usdcAddress;
38+
randomV2 = ContractRegistry.getRandomNumberV2();
39+
isFlare = _isFlare;
40+
}
41+
42+
function requestRandomNumber() external payable {
43+
if (isFlare) {
44+
(uint256 _randomNumber, bool _isSecure, uint256 _timestamp) = randomV2
45+
.getRandomNumber();
46+
47+
number = bytes32(_randomNumber);
48+
numberAsUint = uint256(_randomNumber);
49+
} else {
50+
uint256 fee = entropy.getFeeV2();
51+
uint64 sequenceNumber = entropy.requestV2{ value: fee }();
52+
}
53+
}
54+
55+
function wagerPlayer1() external payable {
56+
require(!player1Ready, "Player 1 already ready");
57+
58+
// Transfer USDC from user to contract
59+
IERC20 usdc = IERC20(usdcAddress);
60+
require(usdc.transferFrom(msg.sender, address(this), wagerAmount), "USDC transfer failed");
61+
62+
// Set player 1 ready state
63+
player1Ready = true;
64+
65+
if (isFlare) {
66+
// Flare: use RandomNumberV2 (no fee needed)
67+
(uint256 _randomNumber, bool _isSecure, uint256 _timestamp) = randomV2.getRandomNumber();
68+
number = bytes32(_randomNumber);
69+
numberAsUint = uint256(_randomNumber);
70+
71+
// Refund any ETH sent (should be 0, but just in case)
72+
if (msg.value > 0) {
73+
payable(msg.sender).transfer(msg.value);
74+
}
75+
} else {
76+
// Pyth: require ETH fee and request random number
77+
uint256 fee = entropy.getFeeV2();
78+
require(msg.value >= fee, "Insufficient ETH for Pyth fee");
79+
entropy.requestV2{ value: fee }();
80+
81+
// Refund excess ETH if any
82+
if (msg.value > fee) {
83+
payable(msg.sender).transfer(msg.value - fee);
84+
}
85+
}
86+
}
87+
88+
function wagerPlayer2() external payable {
89+
require(!player2Ready, "Player 2 already ready");
90+
91+
// Transfer USDC from user to contract
92+
IERC20 usdc = IERC20(usdcAddress);
93+
require(usdc.transferFrom(msg.sender, address(this), wagerAmount), "USDC transfer failed");
94+
95+
// Set player 2 ready state
96+
player2Ready = true;
97+
98+
if (isFlare) {
99+
// Flare: use RandomNumberV2 (no fee needed)
100+
(uint256 _randomNumber, bool _isSecure, uint256 _timestamp) = randomV2.getRandomNumber();
101+
number = bytes32(_randomNumber);
102+
numberAsUint = uint256(_randomNumber);
103+
104+
// Refund any ETH sent (should be 0, but just in case)
105+
if (msg.value > 0) {
106+
payable(msg.sender).transfer(msg.value);
107+
}
108+
} else {
109+
// Pyth: require ETH fee and request random number
110+
uint256 fee = entropy.getFeeV2();
111+
require(msg.value >= fee, "Insufficient ETH for Pyth fee");
112+
entropy.requestV2{ value: fee }();
113+
114+
// Refund excess ETH if any
115+
if (msg.value > fee) {
116+
payable(msg.sender).transfer(msg.value - fee);
117+
}
118+
}
119+
}
120+
121+
122+
function entropyCallback(
123+
uint64 sequenceNumber,
124+
address provider,
125+
bytes32 randomNumber
126+
) internal override {
127+
number = randomNumber;
128+
numberAsUint = uint256(randomNumber);
129+
}
130+
131+
// This method is required by the IEntropyConsumer interface.
132+
// It returns the address of the entropy contract which will call the callback.
133+
function getEntropy() internal view override returns (address) {
134+
return address(entropy);
135+
}
136+
137+
/**
138+
* @notice Consensus-based winner selection with hash verification
139+
* @dev NOTE: For hackathon purposes, this is a single function combining what would normally be
140+
* three separate functions (one for each party: player1, player2, server) in a distributed
141+
* consensus system. In production, each party would submit their vote and hash separately
142+
* to prevent collusion and ensure verifiability.
143+
*
144+
* @param p1hash Game state hash from player 1
145+
* @param p2hash Game state hash from player 2
146+
* @param serverhash Game state hash from server
147+
* @param p1winner True if player 1 votes for player 1, false if player 1 votes for player 2
148+
* @param p2winner True if player 2 votes for player 1, false if player 2 votes for player 2
149+
* @param serverwinner True if server votes for player 1, false if server votes for player 2
150+
*/
151+
function chooseWinner(
152+
bytes32 p1hash,
153+
bytes32 p2hash,
154+
bytes32 serverhash,
155+
bool p1winner,
156+
bool p2winner,
157+
bool serverwinner
158+
) external {
159+
require(player1Ready && player2Ready, "Both players must be ready");
160+
161+
uint256 totalWager = wagerAmount * 2; // Total wager in contract (2 * wagerAmount)
162+
163+
// Count votes for player 1 (true votes)
164+
uint256 votesForP1 = 0;
165+
if (p1winner) votesForP1++;
166+
if (p2winner) votesForP1++;
167+
if (serverwinner) votesForP1++;
168+
169+
// Determine majority winner (2/3 or more)
170+
bool p1Wins = votesForP1 >= 2;
171+
bool p2Wins = votesForP1 <= 1;
172+
173+
if (p1Wins) {
174+
// Player 1 wins by majority - verify hashes match among voters
175+
bytes32[] memory winnerHashes = new bytes32[](votesForP1);
176+
uint256 index = 0;
177+
178+
if (p1winner) {
179+
winnerHashes[index] = p1hash;
180+
index++;
181+
}
182+
if (p2winner) {
183+
winnerHashes[index] = p2hash;
184+
index++;
185+
}
186+
if (serverwinner) {
187+
winnerHashes[index] = serverhash;
188+
index++;
189+
}
190+
191+
// Check if all hashes match
192+
bool hashesMatch = true;
193+
for (uint256 i = 1; i < winnerHashes.length; i++) {
194+
if (winnerHashes[0] != winnerHashes[i]) {
195+
hashesMatch = false;
196+
break;
197+
}
198+
}
199+
200+
if (hashesMatch) {
201+
// Hashes match - pay winner the full pot (wager * 2)
202+
player1Balance += totalWager;
203+
} else {
204+
// Hashes don't match - refund both players (split 50/50)
205+
player1Balance += wagerAmount;
206+
player2Balance += wagerAmount;
207+
}
208+
} else if (p2Wins) {
209+
// Player 2 wins by majority - verify hashes match among voters
210+
bytes32[] memory winnerHashes = new bytes32[](3 - votesForP1);
211+
uint256 index = 0;
212+
213+
if (!p1winner) {
214+
winnerHashes[index] = p1hash;
215+
index++;
216+
}
217+
if (!p2winner) {
218+
winnerHashes[index] = p2hash;
219+
index++;
220+
}
221+
if (!serverwinner) {
222+
winnerHashes[index] = serverhash;
223+
index++;
224+
}
225+
226+
// Check if all hashes match
227+
bool hashesMatch = true;
228+
for (uint256 i = 1; i < winnerHashes.length; i++) {
229+
if (winnerHashes[0] != winnerHashes[i]) {
230+
hashesMatch = false;
231+
break;
232+
}
233+
}
234+
235+
if (hashesMatch) {
236+
// Hashes match - pay winner the full pot (wager * 2)
237+
player2Balance += totalWager;
238+
} else {
239+
// Hashes don't match - refund both players (split 50/50)
240+
player1Balance += wagerAmount;
241+
player2Balance += wagerAmount;
242+
}
243+
}
244+
245+
// Reset ready states for next game
246+
player1Ready = false;
247+
player2Ready = false;
248+
}
249+
}
250+
251+
252+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
import {Snake} from "./Snake.sol";
5+
import {Test} from "forge-std/Test.sol";
6+
7+
contract SnakeTest is Test {
8+
Snake snake;
9+
address constant ENTROPY_V2_ADDRESS = 0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c;
10+
address constant USDC_ADDRESS = 0x036CbD53842c5426634e7929541eC2318f3dCF7e;
11+
uint256 constant WAGER_AMOUNT = 10000; // 1 cent in USDC (6 decimals: 0.01 * 10^6 = 10000)
12+
13+
function setUp() public {
14+
snake = new Snake(ENTROPY_V2_ADDRESS, WAGER_AMOUNT, USDC_ADDRESS, false);
15+
}
16+
17+
function test_EntropyAddress() public view {
18+
require(address(snake.entropy()) == ENTROPY_V2_ADDRESS, "Entropy address should match");
19+
}
20+
21+
function test_UsdcAddress() public view {
22+
require(snake.usdcAddress() == USDC_ADDRESS, "USDC address should match");
23+
}
24+
25+
function test_WagerAmount() public view {
26+
require(snake.wagerAmount() == WAGER_AMOUNT, "Wager amount should match");
27+
}
28+
}

0 commit comments

Comments
 (0)