📜On-Chain Implementation
Game link: https://app.ethxy.com
Introduction
Each game on EthXY is conducted on-chain. The future blockhash on the Base chain is utilized to minimize vectors for exploitation randomness for determining player cards and the board.
Poker Smart Contract: https://basescan.org/address/0x46EbAa6dF85900200B6b6CF15248F3F1adcA13b2
Game Initiation and Player Commitment
Committing to a Game: A user commits to playing a game by calling the
start_game
function and specifying a denomination and character ID. This action sets the player as a pending player for the chosen denomination.Matching with Another Player: If there's already a pending player for the selected denomination, the new player is matched with this pending player. If not, the new player becomes the pending player until another player joins.
Game Start and Block Hash Dependence
Game Start: When two players are matched, the game starts. The smart contract records the starting block number of the game.
Block Hash Dependence: The outcome of the game is determined based on the hash of a future block (specifically, the block number at the start of the game plus a constant block delay, defined by
BLOCK_DELAY
).
Finalization and Card Generation
Triggering Finalization: Once the future block (determined by the start block and
BLOCK_DELAY
) has passed, anyone can callclose_game
to finalize it.Generating Cards Using Block Hash: The block hash is used to generate random cards for each player. The card generation process involves a bit rotation technique to maximize randomness and utility of the block hash. Bit rotation allows us to generate up to 256 valid card games from a single block hash.
Determining the Winner and Handling Ties
Evaluating Hands: Using the generated cards, the contract evaluates the hands of both players to determine the winner. This is done through the
PokerChecker
interface to an external contract, specialized only in judging a winner between two poker hands.Handling Ties: In case of a tie, the contract has specific logic to resolve it, ensuring a fair outcome. Through bit rotation, a new set of cards is drawn based on the same (bit-shifted) block hash. This allows the game to continue without delaying for a further block.
Bit Rotation for Enchanced Randomness
Purpose: Bit rotation is used to enhance the randomness of card generation from a single block hash and to increase the number of games that can be generated from a single block hash.
Mechanism: By rotating the bits of the block hash, the contract can effectively create multiple distinct pseudo-random outcomes from a single hash. A block hash is a random sequence of 256 bits, and by shifting by 1, we can deterministically generate another sequence of random 256 bits. Because cards are generated from 4 bits at a time, this results in a commpletely different set of cards each time we shift the block hash by 1.
Usage in Contract: The
_rotate_bits
function performs this operation. It takes abytes32
hash and a number of bits to rotate, returning a newbytes32
hash. This is done for every new game we generate based off a block hash, or in the case of a tie between two generated hands.Implication for Game Limit: Because a block hash is 256 bits long, we can shift up to 256 times before we end up "cycling" through and repeating cards. This technique allows up to 256 random games per block, significantly increasing the capacity of the contract to handle multiple games within the same block. Without bit rotation, the number of games per block would be limited, potentially causing delays in game creation and finalization.
Technical Breakdown
Key Variables and Constants
current_game_id
: Stores the ID of the current game.games
: A hash map that tracks all games using their ID.checker
: The address of a contract used to evaluate poker hands.fee_recipient
: Address where fees are sent.fee_rate
&fee_rates
: Represents the fee rate;fee_rates
is mapped byDenomination
.base_amount
: A constant representing the base amount of ETH for stakes.BLOCK_DELAY
: The constant number of blocks to wait for game finalization.pending_players
: Tracks players waiting for a game, mapped byDenomination
.enabled
: Boolean to enable or disable the game.denomination_enabled
: Tracks which denominations are enabled.PERMANENT_SHUTDOWN
: Boolean for irreversible shutdown of the game.manager
&pending_manager
: Addresses for contract management.super_admin
: A constant address with high-level control.block_hashes
,games_per_address
,games_per_char_id
,block_draw_counter
: Various mappings for game management.
Structs and Enums
Character
: Represents a player with address and ID.Game
: Defines a game with two players, amount, start block, and denomination.Denomination
: Enum for various ETH denominations.
Events
GameStarted
,PendingPlayer
,GameEnded
,Cards
,Outcome
,Tie
: Used for logging different stages and outcomes of games.
Interfaces
PokerChecker
: An external contract to evaluate poker hands.
Functions
__init__
: Constructor to initialize the contract._rotate_bits
: Internal function for bit manipulation.set_fee_recipient
,set_fee_rate
,set_enabled
,set_denomination_enabled
,set_checker
,set_manager
,accept_manager
: Functions to change contract settings, restricted to the manager or super admin._get_denomination_amount
: Returns the amount of ETH for a given denomination.compare_hands
: Compares two poker hands using thePokerChecker
._handle_win
: Handles the winning process, transfers funds, and logs the game end.get_best_hand
: Determines the best hand out of possible combinations.back_out
: Allows a player to back out of a pending game.start_game
: Begins a new game or pairs with a pending player._get_cards_for_block_hash
,_get_cards_for_blocknumber
: Generate cards based on block hashes.close_game
: Finalizes and determines the outcome of a game.check_pending_games
: Checks for games pending for a specific player or in general.admin_withdraw
: Allows the super admin to withdraw funds from the contract.
Security and Game Integrity
The contract employs various assertions and checks to ensure only authorized users can modify critical settings.
The
compare_hands
andget_best_hand
functions use the externalPokerChecker
contract to evaluate poker hands. This allows the PokerChecker contract and the Poker contract to maintain "separation of concerns", making it easier to verify each is correct for its purpose.Game outcomes are determined using block hashes, providing a decentralized and tamper-proof mechanism for randomization.
Fee Management
The contract allows setting different fee rates for different game denominations.
Fees are collected each game and sent to the designated
fee_recipient
.
Game Logic
Players can start new games, join pending games, or back out of games they've joined.
Games are tracked with unique IDs, and the outcome is determined after a predefined block delay.
Looking at start_game
start_game
Each game is started by a player calling the start_game
function. In the parameters, we have the character id that's playing and the denomination.
Depending on whether this is the first player to be playing at this denomination or the second, we have a difference in behavior. There can only be one player in queue for a denomination.
As soon as the second player comes, the smart contract emits a GameStarted
event with the player addresses and the gameId
. This gameId
is immutable and is forever linked to this game between the players listed.
Looking at close_game
close_game
The second player invokes the close_game
function that resolves the game. Inside the transaction are two key logs: Cards
and GameEnded
Cards
displays the raw cards for each player as well as the "flop" (which constitutes the full board containing flop, turn, and river).
GameEnded
provides a summary of the outcome of the game. The poker hand strength interpreter runs fully on-chain and can determine the best 5-card hand out of the 7 cards each player has available. It also records the exact blockhash used to derive the cards allowing for an audit of the gameplay.
Inspecting your EthXY wallet
We recommend using Basescan for examining your EthXY wallet for on-chain activities. Simply looking at "Transactions" may be misleading as you'll see an assortment of start_game
and close_game
transactions. Remember that only the second player calls close_game
! This means you'll naturally see an imbalance of start_game
and close_game
transactions. This is not a bug.
To see your wins flowing back into your wallet, click on "Internal Txns"
It is here that you'll be able to witness your winnings flying in. The reason why 0.0195 ETH is coming in (instead of 0.02 ETH) is due to the 2.5% protocol fee levied on the 0.01 ETH denomination games. 0.02 * (1 - 2.5%) = 0.0195 ETH
Conclusion
This contract represents a complex and secure system for playing poker on the Ethereum blockchain. Its reliance on block hashes for randomness enhances its trustworthiness and fairness. Additionally, the contract's fee and management features provide flexibility and control to the contract administrators.
Last updated