Page cover image

📜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

  1. 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.

  2. 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

  1. Game Start: When two players are matched, the game starts. The smart contract records the starting block number of the game.

  2. 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

  1. Triggering Finalization: Once the future block (determined by the start block and BLOCK_DELAY) has passed, anyone can call close_game to finalize it.

  2. 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

  1. 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.

  2. 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 a bytes32 hash and a number of bits to rotate, returning a new bytes32 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

  1. current_game_id: Stores the ID of the current game.

  2. games: A hash map that tracks all games using their ID.

  3. checker: The address of a contract used to evaluate poker hands.

  4. fee_recipient: Address where fees are sent.

  5. fee_rate & fee_rates: Represents the fee rate; fee_rates is mapped by Denomination.

  6. base_amount: A constant representing the base amount of ETH for stakes.

  7. BLOCK_DELAY: The constant number of blocks to wait for game finalization.

  8. pending_players: Tracks players waiting for a game, mapped by Denomination.

  9. enabled: Boolean to enable or disable the game.

  10. denomination_enabled: Tracks which denominations are enabled.

  11. PERMANENT_SHUTDOWN: Boolean for irreversible shutdown of the game.

  12. manager & pending_manager: Addresses for contract management.

  13. super_admin: A constant address with high-level control.

  14. 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

  1. __init__: Constructor to initialize the contract.

  2. _rotate_bits: Internal function for bit manipulation.

  3. 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.

  4. _get_denomination_amount: Returns the amount of ETH for a given denomination.

  5. compare_hands: Compares two poker hands using the PokerChecker.

  6. _handle_win: Handles the winning process, transfers funds, and logs the game end.

  7. get_best_hand: Determines the best hand out of possible combinations.

  8. back_out: Allows a player to back out of a pending game.

  9. start_game: Begins a new game or pairs with a pending player.

  10. _get_cards_for_block_hash, _get_cards_for_blocknumber: Generate cards based on block hashes.

  11. close_game: Finalizes and determines the outcome of a game.

  12. check_pending_games: Checks for games pending for a specific player or in general.

  13. 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 and get_best_hand functions use the external PokerChecker 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

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

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