Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
Latest 5 from a total of 5 transactions
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
TexasHoldemRoom
Compiler Version
v0.8.29+commit.ab55807c
Optimization Enabled:
No with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import "./BigNumbers/BigNumbers.sol";
import "./CryptoUtils.sol";
import "./DeckHandler.sol";
contract TexasHoldemRoom {
using BigNumbers for BigNumber;
enum GameStage {
Idle, // 0
Shuffle, // 1
RevealDeal, // 2
Preflop, // 3
RevealFlop, // 4
Flop, // 5
RevealTurn, // 6
Turn, // 7
RevealRiver, // 8
River, // 9
Showdown, // 10
Break, // 11
Ended // 12
}
enum Action {
None, // 0
Call, // 1
Raise, // 2
Check, // 3
Fold // 4
}
struct Player {
address addr;
uint256 chips;
uint256 currentStageBet;
uint256 totalRoundBet;
bool hasFolded;
bool isAllIn;
bool hasChecked;
string[2] cards;
uint8 seatPosition;
uint256 handScore;
bool joinedAndWaitingForNextRound;
bool leavingAfterRoundEnds;
}
uint256 public roundNumber;
GameStage public stage;
uint256 public pot;
uint256 public currentStageBet; // per player (to stay in the round)
uint256 public smallBlind;
uint256 public bigBlind;
uint8 public dealerPosition;
/**
* @dev The current player that should take an action (background or bet action)
* This is the index of the player in the players array. Not the seat position.
*/
uint8 public currentPlayerIndex;
uint8 public lastRaiseIndex;
uint256 public lastActionTimestamp;
uint8 public constant MAX_PLAYERS = 10;
uint8 public constant MIN_PLAYERS = 2;
uint8 public constant EMPTY_SEAT = 255;
uint256 public constant STARTING_CHIPS = 1000;
CryptoUtils public cryptoUtils;
DeckHandler public deckHandler;
Player[MAX_PLAYERS] public players;
uint8[MAX_PLAYERS] public seatPositionToPlayerIndex;
uint8 public numPlayers;
bool public isPrivate;
event GameStarted(uint256 dealerPosition);
event NewStage(GameStage stage);
event PlayerMoved(address indexed player, Action indexed action, uint256 amount);
event PotWon(address[] winners, uint8[] winnerPlayerIndexes, uint256 amount);
event InvalidCardsReported(address indexed player);
event PlayerJoined(
address indexed player, uint8 indexed playerIndex, uint8 indexed seatPosition
);
event PlayerLeft(address indexed player, uint8 indexed playerIndex, uint8 indexed seatPosition);
// a non-player could report an idle player
event IdlePlayerKicked(
address indexed addressReporting, address indexed playerReported, uint256 timeElapsed
);
// event THP_Log(string message);
constructor(address _cryptoUtils, uint256 _smallBlind, bool _isPrivate) {
cryptoUtils = CryptoUtils(_cryptoUtils);
smallBlind = _smallBlind;
bigBlind = _smallBlind * 2;
stage = GameStage.Idle;
roundNumber = 0;
isPrivate = _isPrivate;
dealerPosition = 0;
currentPlayerIndex = 0;
numPlayers = 0;
// possibly move this to setDeckHandler() to reduce initcode size
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
seatPositionToPlayerIndex[i] = EMPTY_SEAT;
players[i] = Player({
addr: address(0),
chips: 0,
currentStageBet: 0,
totalRoundBet: 0,
hasFolded: false,
hasChecked: false,
isAllIn: false,
cards: ["", ""],
seatPosition: i,
handScore: 0,
joinedAndWaitingForNextRound: false,
leavingAfterRoundEnds: false
});
}
}
/**
* @dev Should only be set by the deployer contract. Can only be called once.
*/
function setDeckHandler(address _deckHandler) external {
require(address(deckHandler) == address(0), "DeckHandler already set");
deckHandler = DeckHandler(_deckHandler);
}
/**
* @dev Returns the index of the player in the players array for a given address
* @dev Reverts if the player is not found in the players array
*/
function getPlayerIndexFromAddr(address addr) external view returns (uint8) {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr == addr) {
return i;
}
}
revert("Player not found for given address");
}
/**
* @dev Finds the next active player index clockwise of the current player using seat position
* @dev Skips players that have folded or are all-in
* @dev Returns the current player index if no active players are found
*/
function getNextActivePlayer(bool requireActive) public view returns (uint8) {
// get the current player's seat position
uint8 currentSeatPosition = players[currentPlayerIndex].seatPosition; // 1, 1
// loop over the players in the ascending order of their seat positions
// until we find an active player
// TODO: create a number of players that played in the current round and use that for the modulo
// and for the next seat index. (don't use players that joined the game after the round started)
// todo : % numPlayers or % MAX_PLAYERS?
uint8 nextSeatIndex = (currentSeatPosition + 1) % MAX_PLAYERS; // 2 % 2 = 0
while (nextSeatIndex != currentSeatPosition) {
// TODO: add a status for a player that has joined, but did not start the round
uint8 playerIndex = seatPositionToPlayerIndex[nextSeatIndex];
// skip empty seats and players that joined after the round started
if (playerIndex != EMPTY_SEAT) {
Player memory checkPlayer = players[playerIndex];
if (!checkPlayer.joinedAndWaitingForNextRound) {
// TODO: check if the player has cards (joined before the round started)
if (!requireActive) {
return playerIndex;
}
if (!checkPlayer.hasFolded && !checkPlayer.isAllIn) {
return playerIndex;
}
}
}
nextSeatIndex = (nextSeatIndex + 1) % MAX_PLAYERS;
}
// if no active players are found, return the current player
return currentPlayerIndex;
}
function removePlayer(uint8 playerIndex) internal {
// Reset player states
players[playerIndex].joinedAndWaitingForNextRound = false;
players[playerIndex].leavingAfterRoundEnds = false;
// saving contract size and not setting to 0. These are set to 0 when a new player joins.
// players[playerIndex].currentStageBet = 0;
// players[playerIndex].totalRoundBet = 0;
// players[playerIndex].hasFolded = false;
// players[playerIndex].hasChecked = false;
// players[playerIndex].isAllIn = false;
// players[playerIndex].cards = ["", ""];
players[playerIndex].handScore = 0;
// players[playerIndex].chips = 0;
seatPositionToPlayerIndex[players[playerIndex].seatPosition] = EMPTY_SEAT;
numPlayers--;
emit PlayerLeft(players[playerIndex].addr, playerIndex, players[playerIndex].seatPosition);
players[playerIndex].addr = address(0);
// players[playerIndex].seatPosition = EMPTY_SEAT; // intialized as playerIndex in the constructor
}
/**
* @dev This function is callable by players in the room. If the player is currently waiting
* for the next round to start, they will be removed from the room immediately.
*
* @dev If the player is currently in the middle of a round (folded or active),
* they will be removed from the room at the end of the round.
*/
function leaveGame() external {
uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
require(playerIndex != EMPTY_SEAT, "Player not in game");
if (players[playerIndex].joinedAndWaitingForNextRound) {
// set their seat as empty and remove their player from the players array
removePlayer(playerIndex);
} else {
// will be removed from the game at the end of the round
players[playerIndex].leavingAfterRoundEnds = true;
}
}
// fully new function
function joinGame() external {
require(numPlayers < MAX_PLAYERS, "Room is full");
// Check if player is already in the game
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr == msg.sender) {
revert("Already in game");
}
}
// Find the first empty seat
uint8 seatPosition = 0;
while (seatPositionToPlayerIndex[seatPosition] != EMPTY_SEAT && seatPosition < MAX_PLAYERS)
{
seatPosition++;
}
// This should never happen if seats are set empty correctly as players leave the game
require(seatPosition < MAX_PLAYERS, "No empty seats");
// find the first player in the players array which is a null player (addr == 0)
uint8 nullPlayerIndex = 0;
while (players[nullPlayerIndex].addr != address(0) && nullPlayerIndex < MAX_PLAYERS) {
nullPlayerIndex++;
}
require(nullPlayerIndex < MAX_PLAYERS, "No empty players");
require(players[nullPlayerIndex].addr == address(0), "Null player index not found");
bool isRoundPastIdleStage = stage >= GameStage.Idle;
players[nullPlayerIndex] = Player({
addr: msg.sender,
chips: STARTING_CHIPS,
currentStageBet: 0,
totalRoundBet: 0,
hasFolded: false,
hasChecked: false,
isAllIn: false,
cards: ["", ""],
seatPosition: seatPosition,
handScore: 0,
joinedAndWaitingForNextRound: isRoundPastIdleStage,
leavingAfterRoundEnds: false
});
seatPositionToPlayerIndex[seatPosition] = nullPlayerIndex;
numPlayers++;
emit PlayerJoined(msg.sender, nullPlayerIndex, seatPosition);
if (numPlayers >= MIN_PLAYERS && !isPrivate) {
_progressGame();
}
}
function resetRound() external {
require(
msg.sender == address(0x2a99EC82d658F7a77DdEbFd83D0f8F591769cB64)
|| msg.sender == address(0x101a25d0FDC4E9ACa9fA65584A28781046f1BeEe)
|| msg.sender == address(0x7D20fd2BD3D13B03571A36568cfCc2A4EB3c749e)
|| msg.sender == address(0x3797A1F60C46D2D6F02c3568366712D8A8A69a73),
"Only Johns can call this"
);
// return chips to players
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
function _startNewHand() internal {
// do this check later after processing players waiting to join? or move game stage to idle.
// require(numPlayers >= MIN_PLAYERS, "Not enough players");
// todo: ? stage might be any stage if players fold or showdown?
// require(stage == GameStage.Idle, "Game in progress");
// Reset game state
roundNumber++;
stage = GameStage.Idle;
pot = 0;
currentStageBet = 0;
// todo: blinds
// currentStageBet = bigBlind;
// These are all indexes into the players array, but the dealer position is based
// on the seat position of the players.
uint8 previousDealerSeatPosition = players[dealerPosition].seatPosition;
// Process players that are leaving/have left the game here
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].leavingAfterRoundEnds) {
removePlayer(i);
}
}
// process players that joined the game after the round started and reset their states
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].joinedAndWaitingForNextRound) {
players[i].joinedAndWaitingForNextRound = false;
}
// Reset player states
players[i].currentStageBet = 0;
players[i].totalRoundBet = 0;
players[i].hasFolded = false;
players[i].hasChecked = false;
players[i].isAllIn = false;
players[i].cards = ["", ""];
players[i].handScore = 0;
}
// reset the deck
deckHandler.resetDeck();
if (numPlayers < MIN_PLAYERS) {
// not enough players to start the round
// already in idle stage with pot = 0
lastActionTimestamp = 0; // turns the clock "off"
dealerPosition = 0;
currentPlayerIndex = 0;
return;
}
// todo: what to do if there are no players, or only 1 player left?
// Now that all player join/leaves have been processed, update the dealer position
// and the current player index
// The next dealer position is the next player clockwise of the previous dealer
// So loop through all the seats until we find the next dealer, starting from the previous dealer
// and wrap around if necessary
uint8 nextDealerSeatPosition = (previousDealerSeatPosition + 1) % MAX_PLAYERS;
while (
seatPositionToPlayerIndex[nextDealerSeatPosition] == EMPTY_SEAT
&& nextDealerSeatPosition != previousDealerSeatPosition
) {
nextDealerSeatPosition = (nextDealerSeatPosition + 1) % MAX_PLAYERS;
}
require(
seatPositionToPlayerIndex[nextDealerSeatPosition] != EMPTY_SEAT,
"Next dealer must not be an empty seat"
);
require(
nextDealerSeatPosition != previousDealerSeatPosition,
"Next dealer must not be the previous dealer"
);
dealerPosition = seatPositionToPlayerIndex[nextDealerSeatPosition];
currentPlayerIndex = dealerPosition; // dealer always starts shuffling
lastRaiseIndex = currentPlayerIndex; // todo: check if this is correct
// todo: blinds
// uint256 sbPosition = (dealerPosition + 1) % numPlayers;
// uint256 bbPosition = (dealerPosition + 2) % numPlayers;
// _placeBet(sbPosition, smallBlind);
// _placeBet(bbPosition, bigBlind);
_progressGame();
}
// mostly fully tested function
function submitAction(Action action, uint256 raiseAmount) external {
require(
stage == GameStage.Preflop || stage == GameStage.Flop || stage == GameStage.Turn
|| stage == GameStage.River,
"Game not in a betting stage"
);
uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
require(playerIndex == currentPlayerIndex, "Not your turn");
require(!players[playerIndex].hasFolded, "Player has folded");
require(!players[playerIndex].isAllIn, "Player is all-in");
if (action == Action.Fold) {
players[playerIndex].hasFolded = true;
} else if (action == Action.Call) {
uint256 callAmount = currentStageBet - players[playerIndex].currentStageBet;
require(players[playerIndex].chips >= callAmount, "Not enough chips");
_placeBet(playerIndex, callAmount);
} else if (action == Action.Raise) {
require(raiseAmount > currentStageBet, "Raise must be higher than current bet");
uint256 totalAmount = raiseAmount - players[playerIndex].currentStageBet;
require(players[playerIndex].chips >= totalAmount, "Not enough chips");
_placeBet(playerIndex, totalAmount);
currentStageBet = raiseAmount;
lastRaiseIndex = playerIndex;
} else if (action == Action.Check) {
require(players[playerIndex].currentStageBet == currentStageBet, "Must call or raise");
players[playerIndex].hasChecked = true;
}
emit PlayerMoved(msg.sender, action, raiseAmount);
// Move to next player or stage
_progressGame();
}
function determineWinners() internal {
require(
stage == GameStage.Showdown || countActivePlayers() == 1,
"Not showdown stage or more than 1 active player"
);
// Evaluate hands and find winners
// Can be tie if best 5 cards are the same (eg. community cards)
uint256 highestScore = 0;
uint8 maxWinnerCount = countOfHandsRevealed() > 0 ? countOfHandsRevealed() : 1;
uint8[] memory winnerPlayerIndexes = new uint8[](maxWinnerCount);
uint8 winnerCount = 0;
if (countActivePlayers() == 1) {
// emit THP_Log("_progressGame() dw in_countActivePlayers() == 1");
// only 1 active player, so they win the pot
uint8 lastActivePlayerIndex;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].joinedAndWaitingForNextRound
&& !players[i].hasFolded
) {
lastActivePlayerIndex = i;
break;
}
}
// emit THP_Log(
// "_progressGame() dw in _countActivePlayers() == 1 after lastActivePlayerIndex"
// );
winnerPlayerIndexes[0] = lastActivePlayerIndex;
winnerCount = 1;
} else {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
uint256 handScore = players[i].handScore;
if (handScore == 0) {
// player was not active or "alive" at the end of the round
continue;
}
if (handScore > highestScore) {
// New highest hand
highestScore = handScore;
winnerPlayerIndexes[0] = i;
winnerCount = 1;
} else if (handScore == highestScore) {
// Tie
winnerPlayerIndexes[winnerCount] = i;
winnerCount++;
}
}
}
// Distribute pot
// todo: what to do with the remainder fractional chips?
// use 6th or 7th card to decide who gets the "odd chip" (remainder)
uint256 winAmount = pot / winnerCount;
uint8[] memory justWinnerIndicies = new uint8[](winnerCount);
for (uint8 i = 0; i < winnerCount; i++) {
uint8 winnerPlayerIndex = winnerPlayerIndexes[i];
justWinnerIndicies[i] = winnerPlayerIndex;
players[winnerPlayerIndex].chips += winAmount;
}
// emit THP_Log("_progressGame() dw after chips split");
// address[] memory winnerAddrs = new address[](winnerPlayerIndexes.length);
// ^ previous left 0x00 addresses
address[] memory winnerAddrs = new address[](winnerCount);
for (uint8 i = 0; i < winnerCount; i++) {
winnerAddrs[i] = players[justWinnerIndicies[i]].addr;
}
// emit THP_Log("_progressGame() dw after winnerAddrs");
emit PotWon(winnerAddrs, justWinnerIndicies, winAmount);
}
function _placeBet(uint8 playerIndex, uint256 amount) internal {
require(players[playerIndex].chips >= amount, "Not enough chips");
players[playerIndex].chips -= amount;
players[playerIndex].currentStageBet += amount;
players[playerIndex].totalRoundBet += amount;
pot += amount;
if (players[playerIndex].chips == 0) {
players[playerIndex].isAllIn = true;
}
}
// TODO: require all the players to submit their keys onchain, so later offchainwe can see which player
// submitted incorrect card encryption/decryption values.
function reportInvalidCards() external {
require(stage >= GameStage.Preflop, "Cannot report invalid cards before preflop");
// require player to be non-null, in the game, and not waiting to join
uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
// getPlayerIndexFromAddr will revert if the player is not in the room
require(!players[playerIndex].joinedAndWaitingForNextRound, "Player is joining next round");
emit InvalidCardsReported(msg.sender);
// same logic as: this.resetRound();
// returns chips to players and starts a new round
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
function progressGame() external {
require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
_progressGame();
}
function reportIdlePlayer() external {
// require(!isPrivate, "Cannot report idle player in private game");
uint256 timeElapsed = block.timestamp - lastActionTimestamp;
require(timeElapsed > 30 seconds, "Player has 30 seconds to act");
// check if it is the reported player's turn to act or if the player has already revealed their cards
// todo: FIX! the currentPlayerIndex is not changed in showdown. kick the first active player that hasn't
// revealed their cards?
uint8 playerIndexToKick = EMPTY_SEAT;
if (stage == GameStage.Showdown) {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].joinedAndWaitingForNextRound
&& !players[i].hasFolded && players[i].handScore == 0
) {
playerIndexToKick = i;
break;
}
}
require(playerIndexToKick != EMPTY_SEAT, "All players have revealed their cards");
} else {
playerIndexToKick = currentPlayerIndex;
}
emit IdlePlayerKicked(msg.sender, players[playerIndexToKick].addr, timeElapsed);
// todo: split the kicked player's chips between the other active players
players[playerIndexToKick].leavingAfterRoundEnds = true;
// same logic as: this.resetRound();
// returns chips to players and starts a new round
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
/**
* @dev This function should be called after EVERY valid player action. It contains
* @dev logic to update common state like lastActionTimestamp, currentPlayerIndex, and stage.
* @dev If in a reveal stage, all players need to submit their decryption values
* @dev If in a betting stage, only the players who are not all-in
* @dev and not folded need to submit their actions
* @notice Emits a NewStage event and new current player index event
*/
function _progressGame() internal {
// if showdown and countRevealed < countActive, do NOT update timestamp
if (stage == GameStage.Showdown && countOfHandsRevealed() < countActivePlayers()) {
// do not update lastActionTimestamp
// all players have 30 seconds to reveal their cards in showdown
} else {
lastActionTimestamp = block.timestamp;
}
if (stage == GameStage.Idle) {
// mark all players waiting for the next round as not joined
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].joinedAndWaitingForNextRound) {
players[i].joinedAndWaitingForNextRound = false;
}
}
return _moveToNextStage();
}
// shuffle or reveal stage
if (
stage == GameStage.Shuffle || stage == GameStage.RevealDeal
|| stage == GameStage.RevealFlop || stage == GameStage.RevealTurn
|| stage == GameStage.RevealRiver
) {
// emit THP_Log("_progressGame() if Reveal stage true");
// if the next reveal player is back at the dealer, move to the next stage
// since the dealer starts all reveal stages
bool requireActive = false;
uint8 nextRevealPlayer = getNextActivePlayer(requireActive);
if (nextRevealPlayer == dealerPosition) {
// emit THP_Log("_progressGame() if nextRevealPlayer == dealerPosition");
// after a reveal stage, we enter a betting stage
// always the first active player LEFT of the dealer starts all betting stages
// After shuffle, we are still in a reveal stage
if (stage == GameStage.Shuffle) {
currentPlayerIndex = dealerPosition;
} else {
bool requireActiveForBetting = true;
currentPlayerIndex = dealerPosition;
uint8 nextActivePlayer = getNextActivePlayer(requireActiveForBetting);
currentPlayerIndex = nextActivePlayer;
// at the start of a betting stage, the last raise index is the first active player by default
lastRaiseIndex = nextActivePlayer;
}
return _moveToNextStage();
} else {
// otherwise, the next player should submit their decryption values
currentPlayerIndex = nextRevealPlayer;
}
} else if (stage == GameStage.Showdown) {
if (countOfHandsRevealed() == countActivePlayers()) {
// find the winners and split the pot
determineWinners();
// Should start a new round
_startNewHand(); // moves game to idle/shuffling stage
}
// do nothing while the rest of the players reveal their cards
return;
} else {
// current in a betting stage
// if there are no more active players, the round ends and the last
// active player wins the pot
// emit THP_Log("_progressGame() running if _countActivePlayers() == 1");
if (countActivePlayers() == 1) {
// todo: split the pot
// emit THP_Log("_progressGame() in if _countActivePlayers() == 1");
determineWinners();
// emit THP_Log("_progressGame() in if _countActivePlayers() == 1 after det win");
_startNewHand();
// emit THP_Log(
// "_progressGame() in if _countActivePlayers() == 1 after start new hand"
// );
return;
}
// if the last raise index is the same as the next active player index,
// the betting stage is complete.
// Reset the round bet amounts and move to the next stage
bool requireActive = true;
uint8 nextPlayer = getNextActivePlayer(requireActive);
// Check if betting round is complete
if (nextPlayer == lastRaiseIndex) {
// emit THP_Log("_progressGame() if nextPlayer == lastRaiseIndex");
// Reset betting for a new betting stage
// do not reset players' total round bets here
currentStageBet = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].currentStageBet = 0;
players[i].hasChecked = false;
}
// if a betting stage ends, the next player should be the dealer
// to prepare for the next reveal stage
currentPlayerIndex = dealerPosition;
// For a reveal stage, this isn't used, however, it should be reset at the start
// of the next betting stage
// lastRaiseIndex = currentPlayerIndex;
return _moveToNextStage();
} else {
// in the middle of a betting stage with an active player left to act
currentPlayerIndex = nextPlayer;
}
// TODO: handle a new round of poker after showdown or only 1 active players
}
}
function _moveToNextStage() internal {
stage = GameStage(uint8(stage) + 1);
emit NewStage(stage);
}
// TODO: do we include all-in players in the count?
// TODO: don't count players who joined the game after the round started
function countActivePlayers() public view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].hasFolded
&& !players[i].joinedAndWaitingForNextRound
) {
count++;
}
}
return count;
}
function setPlayerHandScore(uint8 playerIndex, uint256 handScore) external {
require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
players[playerIndex].handScore = handScore;
}
function getPlayers() external view returns (Player[] memory) {
Player[] memory playersArray = new Player[](MAX_PLAYERS);
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
playersArray[i] = players[i];
}
return playersArray;
}
function getPlayer(uint8 playerIndex) external view returns (Player memory) {
return players[playerIndex];
}
function countPlayersAtRoundStart() external view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr != address(0) && players[i].joinedAndWaitingForNextRound == false) {
count++;
}
}
return count;
}
function getPlayersCardIndexes(uint8 playerIndex)
external
view
returns (uint8[2] memory playerCardIndexes)
{
// emit THP_Log("_progressGame() in getPlayersCardIndexes()");
uint8 countOfPlayersCounterClockwiseToDealer = 0;
uint8 playerSeatPosition = players[playerIndex].seatPosition; // 0
uint8 dealerSeatPosition = players[dealerPosition].seatPosition; // 1
while (playerSeatPosition != dealerSeatPosition) {
// 0 != 1
playerSeatPosition = (playerSeatPosition + (MAX_PLAYERS - 1)) % MAX_PLAYERS; // = (0 - 1 + 10) % 10 = 9
if (
seatPositionToPlayerIndex[playerSeatPosition] != EMPTY_SEAT
&& !players[seatPositionToPlayerIndex[playerSeatPosition]].joinedAndWaitingForNextRound
) {
countOfPlayersCounterClockwiseToDealer++;
}
// emit THP_Log("_progressGame() in getPlayersCardIndexes() in while loop");
}
// emit THP_Log("_progressGame() in getPlayersCardIndexes() after while loop");
uint8 playersAtRoundStart = this.countPlayersAtRoundStart();
playerCardIndexes[0] = countOfPlayersCounterClockwiseToDealer;
playerCardIndexes[1] = countOfPlayersCounterClockwiseToDealer + playersAtRoundStart;
return playerCardIndexes;
}
/**
* @dev Returns the number of hands revealed by the players this round by
* checking if the player's hand score is greater than 0
* @return The number of hands revealed
*/
function countOfHandsRevealed() public view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
// This is set to 0 after each round,
// so players with a score have just revealed their cards
// This should by default exclude null players and players who joined the game after the round started
if (players[i].handScore > 0) {
// emit THP_Log(
// "_progressGame() in countOfHandsRevealed() if players[i].handScore > 0"
// );
count++;
}
}
return count;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
// Definition here allows both the lib and inheriting contracts to use BigNumber directly.
struct BigNumber {
bytes val;
bool neg;
uint256 bitlen;
}
/**
* @notice BigNumbers library for Solidity.
*/
library BigNumbers {
/// @notice the value for number 0 of a BigNumber instance.
bytes constant ZERO = hex"0000000000000000000000000000000000000000000000000000000000000000";
/// @notice the value for number 1 of a BigNumber instance.
bytes constant ONE = hex"0000000000000000000000000000000000000000000000000000000000000001";
/// @notice the value for number 2 of a BigNumber instance.
bytes constant TWO = hex"0000000000000000000000000000000000000000000000000000000000000002";
// ***************** BEGIN EXPOSED MANAGEMENT FUNCTIONS ******************
/**
* @notice verify a BN instance
* @dev checks if the BN is in the correct format. operations should only be carried out on
* verified BNs, so it is necessary to call this if your function takes an arbitrary BN
* as input.
*
* @param bn BigNumber instance
*/
function verify(BigNumber memory bn) public pure {
uint256 msword;
bytes memory val = bn.val;
assembly {
msword := mload(add(val, 0x20))
} //get msword of result
if (msword == 0) require(isZero(bn));
else require((bn.val.length % 32 == 0) && (msword >> ((bn.bitlen - 1) % 256) == 1));
}
/**
* @notice initialize a BN instance
* @dev wrapper function for _init. initializes from bytes value.
* Allows passing bitLength of value. This is NOT verified in the public function. Only use where bitlen is
* explicitly known; otherwise use the other init function.
*
* @param val BN value. may be of any size.
* @param neg neg whether the BN is +/-
* @param bitlen bit length of output.
* @return BigNumber instance
*/
function init(bytes memory val, bool neg, uint256 bitlen)
public
view
returns (BigNumber memory)
{
return _init(val, neg, bitlen);
}
/**
* @notice initialize a BN instance
* @dev wrapper function for _init. initializes from bytes value.
*
* @param val BN value. may be of any size.
* @param neg neg whether the BN is +/-
* @return BigNumber instance
*/
function init(bytes memory val, bool neg) public view returns (BigNumber memory) {
return _init(val, neg, 0);
}
/**
* @notice initialize a BN instance
* @dev wrapper function for _init. initializes from uint value (converts to bytes);
* tf. resulting BN is in the range -2^256-1 ... 2^256-1.
*
* @param val uint value.
* @param neg neg whether the BN is +/-
* @return BigNumber instance
*/
function init(uint256 val, bool neg) public view returns (BigNumber memory) {
return _init(abi.encodePacked(val), neg, 0);
}
// ***************** END EXPOSED MANAGEMENT FUNCTIONS ******************
// ***************** BEGIN EXPOSED CORE CALCULATION FUNCTIONS ******************
/**
* @notice BigNumber addition: a + b.
* @dev add: Initially prepare BigNumbers for addition operation; internally calls actual addition/subtraction,
* depending on inputs.
* In order to do correct addition or subtraction we have to handle the sign.
* This function discovers the sign of the result based on the inputs, and calls the correct operation.
*
* @param a first BN
* @param b second BN
* @return r result - addition of a and b.
*/
function add(BigNumber memory a, BigNumber memory b) public pure returns (BigNumber memory r) {
if (a.bitlen == 0 && b.bitlen == 0) return zero();
if (a.bitlen == 0) return b;
if (b.bitlen == 0) return a;
bytes memory val;
uint256 bitlen;
int256 compare = cmp(a, b, false);
if (a.neg || b.neg) {
if (a.neg && b.neg) {
if (compare >= 0) (val, bitlen) = _add(a.val, b.val, a.bitlen);
else (val, bitlen) = _add(b.val, a.val, b.bitlen);
r.neg = true;
} else {
if (compare == 1) {
(val, bitlen) = privSub(a.val, b.val);
r.neg = a.neg;
} else if (compare == -1) {
(val, bitlen) = privSub(b.val, a.val);
r.neg = !a.neg;
} else {
return zero();
} //one pos and one neg, and same value.
}
} else {
if (compare >= 0) {
// a>=b
(val, bitlen) = _add(a.val, b.val, a.bitlen);
} else {
(val, bitlen) = _add(b.val, a.val, b.bitlen);
}
r.neg = false;
}
r.val = val;
r.bitlen = (bitlen);
}
/**
* @notice BigNumber subtraction: a - b.
* @dev sub: Initially prepare BigNumbers for subtraction operation; internally calls actual addition/subtraction,
* depending on inputs.
* In order to do correct addition or subtraction we have to handle the sign.
* This function discovers the sign of the result based on the inputs, and calls the correct operation.
*
* @param a first BN
* @param b second BN
* @return r result - subtraction of a and b.
*/
function sub(BigNumber memory a, BigNumber memory b) public pure returns (BigNumber memory r) {
if (a.bitlen == 0 && b.bitlen == 0) return zero();
bytes memory val;
int256 compare;
uint256 bitlen;
compare = cmp(a, b, false);
if (a.neg || b.neg) {
if (a.neg && b.neg) {
if (compare == 1) {
(val, bitlen) = privSub(a.val, b.val);
r.neg = true;
} else if (compare == -1) {
(val, bitlen) = privSub(b.val, a.val);
r.neg = false;
} else {
return zero();
}
} else {
if (compare >= 0) (val, bitlen) = _add(a.val, b.val, a.bitlen);
else (val, bitlen) = _add(b.val, a.val, b.bitlen);
r.neg = (a.neg) ? true : false;
}
} else {
if (compare == 1) {
(val, bitlen) = privSub(a.val, b.val);
r.neg = false;
} else if (compare == -1) {
(val, bitlen) = privSub(b.val, a.val);
r.neg = true;
} else {
return zero();
}
}
r.val = val;
r.bitlen = (bitlen);
}
/**
* @notice BigNumber multiplication: a * b.
* @dev mul: takes two BigNumbers and multiplys them. Order is irrelevant.
* multiplication achieved using modexp precompile:
* (a * b) = ((a + b)**2 - (a - b)**2) / 4
*
* @param a first BN
* @param b second BN
* @return r result - multiplication of a and b.
*/
function mul(BigNumber memory a, BigNumber memory b) public view returns (BigNumber memory r) {
BigNumber memory lhs = add(a, b);
BigNumber memory fst = modexp(lhs, two(), _powModulus(lhs, 2)); // (a+b)^2
// no need to do subtraction part of the equation if a == b; if so, it has no effect on final result.
if (!eq(a, b)) {
BigNumber memory rhs = sub(a, b);
BigNumber memory snd = modexp(rhs, two(), _powModulus(rhs, 2)); // (a-b)^2
r = _shr(sub(fst, snd), 2); // (a * b) = (((a + b)**2 - (a - b)**2) / 4
} else {
r = _shr(fst, 2); // a==b ? (((a + b)**2 / 4
}
}
/**
* @notice This function is not optimized for large (2048 bit) numbers and will break gas limits.
* @notice BigNumber division: a / b.
* @dev div: takes two BigNumbers and divides them.
* This is an expensive operation and should be used with caution.
* For verification of division results, use divVerify instead.
*
* @param a dividend BigNumber
* @param b divisor BigNumber
* @return r result BigNumber
*/
function div(BigNumber memory a, BigNumber memory b) public view returns (BigNumber memory r) {
// Check for division by zero
require(!isZero(b.val), "Division by zero");
// If a < b, result is 0
if (cmp(a, b, false) == -1) {
return zero();
}
// Determine sign of result
bool resultNegative = (a.neg && !b.neg) || (!a.neg && b.neg);
// Work with positive values for the algorithm
BigNumber memory dividend = BigNumber(a.val, false, a.bitlen);
BigNumber memory divisor = BigNumber(b.val, false, b.bitlen);
// Initialize result
BigNumber memory quotient = zero();
BigNumber memory one_bn = one();
// Binary search approach for division
BigNumber memory low = one_bn;
BigNumber memory high = dividend;
while (cmp(low, high, false) <= 0) {
BigNumber memory mid = _shr(add(low, high), 1);
BigNumber memory product = mul(divisor, mid);
int256 compareResult = cmp(product, dividend, false);
if (compareResult == 0) {
// Exact match
quotient = mid;
break;
} else if (compareResult < 0) {
// product < dividend, try higher
quotient = mid;
low = add(mid, one_bn);
} else {
// product > dividend, try lower
high = sub(mid, one_bn);
}
}
// Set the sign
quotient.neg = resultNegative;
return quotient;
}
/**
* @notice This function is not optimized for large (2048 bit) numbers and will break gas limits.
* @notice Modular inverse: finds x such that (a * x) % n = 1
* @dev modInverse: Computes the modular multiplicative inverse of a modulo n.
* Uses the Extended Euclidean Algorithm to find the inverse.
* Returns zero if the inverse doesn't exist (when a and n are not coprime).
*
* @param a BigNumber to find inverse for
* @param n modulus BigNumber
* @return r result BigNumber - the modular inverse
*/
function modInverse(BigNumber memory a, BigNumber memory n)
public
view
returns (BigNumber memory)
{
// Check inputs
require(!n.neg && !isZero(n.val), "Modulus must be positive");
require(!a.neg, "Base must be positive");
// Ensure a is within the modulus range
BigNumber memory base = mod(a, n);
// Special case: if base is 0, no inverse exists
if (isZero(base.val)) {
return zero();
}
// Special case: if base is 1, the inverse is 1
if (eq(base, one())) {
return one();
}
// Initialize values for Extended Euclidean Algorithm
BigNumber memory r1 = BigNumber(n.val, false, n.bitlen);
BigNumber memory r2 = base;
BigNumber memory t1 = zero();
BigNumber memory t2 = one();
// Extended Euclidean Algorithm
while (!isZero(r2.val)) {
BigNumber memory quotient = div(r1, r2);
// r1, r2 = r2, r1 - quotient * r2
BigNumber memory temp_r = r2;
r2 = sub(r1, mul(quotient, r2));
r1 = temp_r;
// t1, t2 = t2, t1 - quotient * t2
BigNumber memory temp_t = t2;
t2 = sub(t1, mul(quotient, t2));
t1 = temp_t;
}
// Check if gcd is 1 (r1 should be 1 if inverse exists)
if (!eq(r1, one())) {
return zero(); // No inverse exists
}
// Make sure result is positive
if (t1.neg) {
t1 = add(t1, n);
}
return t1;
}
/**
* @notice BigNumber division verification: a * b.
* @dev div: takes three BigNumbers (a,b and result), and verifies that a/b == result.
* Performing BigNumber division on-chain is a significantly expensive operation. As a result,
* we expose the ability to verify the result of a division operation, which is a constant time operation.
* (a/b = result) == (a = b * result)
* Integer division only; therefore:
* verify ((b*result) + (a % (b*result))) == a.
* eg. 17/7 == 2:
* verify (7*2) + (17 % (7*2)) == 17.
* The function returns a bool on successful verification. The require statements will ensure that false can never
* be returned, however inheriting contracts may also want to put this function inside a require statement.
*
* @param a first BigNumber
* @param b second BigNumber
* @param r result BigNumber
* @return bool whether or not the operation was verified
*/
function divVerify(BigNumber memory a, BigNumber memory b, BigNumber memory r)
public
view
returns (bool)
{
// first do zero check.
// if a<b (always zero) and r==zero (input check), return true.
if (cmp(a, b, false) == -1) {
require(cmp(zero(), r, false) == 0);
return true;
}
// Following zero check:
//if both negative: result positive
//if one negative: result negative
//if neither negative: result positive
bool positiveResult = (a.neg && b.neg) || (!a.neg && !b.neg);
require(positiveResult ? !r.neg : r.neg);
// require denominator to not be zero.
require(!(cmp(b, zero(), true) == 0));
// division result check assumes inputs are positive.
// we have already checked for result sign so this is safe.
bool[3] memory negs = [a.neg, b.neg, r.neg];
a.neg = false;
b.neg = false;
r.neg = false;
// do multiplication (b * r)
BigNumber memory fst = mul(b, r);
// check if we already have 'a' (ie. no remainder after division). if so, no mod necessary, and return true.
if (cmp(fst, a, true) == 0) return true;
//a mod (b*r)
BigNumber memory snd = modexp(a, one(), fst);
// ((b*r) + a % (b*r)) == a
require(cmp(add(fst, snd), a, true) == 0);
a.neg = negs[0];
b.neg = negs[1];
r.neg = negs[2];
return true;
}
/**
* @notice BigNumber exponentiation: a ^ b.
* @dev pow: takes a BigNumber and a uint (a,e), and calculates a^e.
* modexp precompile is used to achieve a^e; for this is work, we need to work out the minimum modulus value
* such that the modulus passed to modexp is not used. the result of a^e can never be more than size bitlen(a) * e.
*
* @param a BigNumber
* @param e exponent
* @return r result BigNumber
*/
function pow(BigNumber memory a, uint256 e) public view returns (BigNumber memory) {
return modexp(a, init(e, false), _powModulus(a, e));
}
/**
* @notice BigNumber modulus: a % n.
* @dev mod: takes a BigNumber and modulus BigNumber (a,n), and calculates a % n.
* modexp precompile is used to achieve a % n; an exponent of value '1' is passed.
* @param a BigNumber
* @param n modulus BigNumber
* @return r result BigNumber
*/
function mod(BigNumber memory a, BigNumber memory n) public view returns (BigNumber memory) {
return modexp(a, one(), n);
}
/**
* @notice BigNumber modular exponentiation: a^e mod n.
* @dev modexp: takes base, exponent, and modulus, internally computes base^exponent % modulus using the precompile at address 0x5, and creates new BigNumber.
* this function is overloaded: it assumes the exponent is positive. if not, the other method is used, whereby the inverse of the base is also passed.
*
* @param a base BigNumber
* @param e exponent BigNumber
* @param n modulus BigNumber
* @return result BigNumber
*/
function modexp(BigNumber memory a, BigNumber memory e, BigNumber memory n)
public
view
returns (BigNumber memory)
{
//if exponent is negative, other method with this same name should be used.
//if modulus is negative or zero, we cannot perform the operation.
require(e.neg == false && n.neg == false && !isZero(n.val));
bytes memory _result = _modexp(a.val, e.val, n.val);
//get bitlen of result (TODO: optimise. we know bitlen is in the same byte as the modulus bitlen byte)
uint256 bitlen = bitLength(_result);
// if result is 0, immediately return.
if (bitlen == 0) return zero();
// if base is negative AND exponent is odd, base^exp is negative, and tf. result is negative;
// in that case we make the result positive by adding the modulus.
if (a.neg && isOdd(e)) return add(BigNumber(_result, true, bitlen), n);
// in any other case we return the positive result.
return BigNumber(_result, false, bitlen);
}
/**
* @notice BigNumber modular exponentiation with negative base: inv(a)==a_inv && a_inv^e mod n.
* /** @dev modexp: takes base, base inverse, exponent, and modulus, asserts inverse(base)==base inverse,
* internally computes base_inverse^exponent % modulus and creates new BigNumber.
* this function is overloaded: it assumes the exponent is negative.
* if not, the other method is used, where the inverse of the base is not passed.
*
* @param a base BigNumber
* @param ai base inverse BigNumber
* @param e exponent BigNumber
* @param a modulus
* @return BigNumber memory result.
*/
function modexp(BigNumber memory a, BigNumber memory ai, BigNumber memory e, BigNumber memory n)
public
view
returns (BigNumber memory)
{
// base^-exp = (base^-1)^exp
require(!a.neg && e.neg);
//if modulus is negative or zero, we cannot perform the operation.
require(!n.neg && !isZero(n.val));
//base_inverse == inverse(base, modulus)
require(modinvVerify(a, n, ai));
bytes memory _result = _modexp(ai.val, e.val, n.val);
//get bitlen of result (TODO: optimise. we know bitlen is in the same byte as the modulus bitlen byte)
uint256 bitlen = bitLength(_result);
// if result is 0, immediately return.
if (bitlen == 0) return zero();
// if base_inverse is negative AND exponent is odd, base_inverse^exp is negative, and tf. result is negative;
// in that case we make the result positive by adding the modulus.
if (ai.neg && isOdd(e)) return add(BigNumber(_result, true, bitlen), n);
// in any other case we return the positive result.
return BigNumber(_result, false, bitlen);
}
/**
* @notice modular multiplication: (a*b) % n.
* @dev modmul: Takes BigNumbers for a, b, and modulus, and computes (a*b) % modulus
* We call mul for the two input values, before calling modexp, passing exponent as 1.
* Sign is taken care of in sub-functions.
*
* @param a BigNumber
* @param b BigNumber
* @param n Modulus BigNumber
* @return result BigNumber
*/
function modmul(BigNumber memory a, BigNumber memory b, BigNumber memory n)
public
view
returns (BigNumber memory)
{
return mod(mul(a, b), n);
}
/**
* @notice modular inverse verification: Verifies that (a*r) % n == 1.
* @dev modinvVerify: Takes BigNumbers for base, modulus, and result, verifies (base*result)%modulus==1, and returns result.
* Similar to division, it's far cheaper to verify an inverse operation on-chain than it is to calculate it, so we allow the user to pass their own result.
*
* @param a base BigNumber
* @param n modulus BigNumber
* @param r result BigNumber
* @return boolean result
*/
function modinvVerify(BigNumber memory a, BigNumber memory n, BigNumber memory r)
public
view
returns (bool)
{
require(!a.neg && !n.neg); //assert positivity of inputs.
/*
* the following proves:
* - user result passed is correct for values base and modulus
* - modular inverse exists for values base and modulus.
* otherwise it fails.
*/
require(
cmp(modmul(a, r, n), one(), true) == 0,
"BigNumbers.modinvVerify(): Invalid modular inverse"
);
return true;
}
// ***************** END EXPOSED CORE CALCULATION FUNCTIONS ******************
// ***************** START EXPOSED HELPER FUNCTIONS ******************
/**
* @notice BigNumber odd number check
* @dev isOdd: returns 1 if BigNumber value is an odd number and 0 otherwise.
*
* @param a BigNumber
* @return r Boolean result
*/
function isOdd(BigNumber memory a) public pure returns (bool r) {
assembly {
let a_ptr := add(mload(a), mload(mload(a))) // go to least significant word
r := mod(mload(a_ptr), 2) // mod it with 2 (returns 0 or 1)
}
}
/**
* @notice BigNumber comparison
* @dev cmp: Compares BigNumbers a and b. 'signed' parameter indiciates whether to consider the sign of the inputs.
* 'trigger' is used to decide this -
* if both negative, invert the result;
* if both positive (or signed==false), trigger has no effect;
* if differing signs, we return immediately based on input.
* returns -1 on a<b, 0 on a==b, 1 on a>b.
*
* @param a BigNumber
* @param b BigNumber
* @param signed whether to consider sign of inputs
* @return int result
*/
function cmp(BigNumber memory a, BigNumber memory b, bool signed)
public
pure
returns (int256)
{
int256 trigger = 1;
if (signed) {
if (a.neg && b.neg) trigger = -1;
else if (a.neg == false && b.neg == true) return 1;
else if (a.neg == true && b.neg == false) return -1;
}
if (a.bitlen > b.bitlen) return trigger; // 1*trigger
if (b.bitlen > a.bitlen) return -1 * trigger;
uint256 a_ptr;
uint256 b_ptr;
uint256 a_word;
uint256 b_word;
uint256 len = a.val.length; //bitlen is same so no need to check length.
assembly {
a_ptr := add(mload(a), 0x20)
b_ptr := add(mload(b), 0x20)
}
for (uint256 i = 0; i < len; i += 32) {
assembly {
a_word := mload(add(a_ptr, i))
b_word := mload(add(b_ptr, i))
}
if (a_word > b_word) return trigger; // 1*trigger
if (b_word > a_word) return -1 * trigger;
}
return 0; //same value.
}
/**
* @notice BigNumber equality
* @dev eq: returns true if a==b. sign always considered.
*
* @param a BigNumber
* @param b BigNumber
* @return boolean result
*/
function eq(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
int256 result = cmp(a, b, true);
return (result == 0) ? true : false;
}
/**
* @notice BigNumber greater than
* @dev eq: returns true if a>b. sign always considered.
*
* @param a BigNumber
* @param b BigNumber
* @return boolean result
*/
function gt(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
int256 result = cmp(a, b, true);
return (result == 1) ? true : false;
}
/**
* @notice BigNumber greater than or equal to
* @dev eq: returns true if a>=b. sign always considered.
*
* @param a BigNumber
* @param b BigNumber
* @return boolean result
*/
function gte(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
int256 result = cmp(a, b, true);
return (result == 1 || result == 0) ? true : false;
}
/**
* @notice BigNumber less than
* @dev eq: returns true if a<b. sign always considered.
*
* @param a BigNumber
* @param b BigNumber
* @return boolean result
*/
function lt(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
int256 result = cmp(a, b, true);
return (result == -1) ? true : false;
}
/**
* @notice BigNumber less than or equal o
* @dev eq: returns true if a<=b. sign always considered.
*
* @param a BigNumber
* @param b BigNumber
* @return boolean result
*/
function lte(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
int256 result = cmp(a, b, true);
return (result == -1 || result == 0) ? true : false;
}
/**
* @notice right shift BigNumber value
* @dev shr: right shift BigNumber a by 'bits' bits.
* copies input value to new memory location before shift and calls _shr function after.
* @param a BigNumber value to shift
* @param bits amount of bits to shift by
* @return result BigNumber
*/
function pubShr(BigNumber memory a, uint256 bits) public view returns (BigNumber memory) {
require(!a.neg);
return _shr(a, bits);
}
/**
* @notice right shift BigNumber memory 'dividend' by 'bits' bits.
* @dev _shr: Shifts input value in-place, ie. does not create new memory. shr function does this.
* right shift does not necessarily have to copy into a new memory location. where the user wishes the modify
* the existing value they have in place, they can use this.
* @param bn value to shift
* @param bits amount of bits to shift by
* @return r result
*/
function _shr(BigNumber memory bn, uint256 bits) public view returns (BigNumber memory) {
uint256 length;
assembly {
length := mload(mload(bn))
}
// if bits is >= the bitlength of the value the result is always 0
if (bits >= bn.bitlen) return BigNumber(ZERO, false, 0);
// set bitlen initially as we will be potentially modifying 'bits'
bn.bitlen = bn.bitlen - (bits);
// handle shifts greater than 256:
// if bits is greater than 256 we can simply remove any trailing words, by altering the BN length.
// we also update 'bits' so that it is now in the range 0..256.
assembly {
if or(gt(bits, 0x100), eq(bits, 0x100)) {
length := sub(length, mul(div(bits, 0x100), 0x20))
mstore(mload(bn), length)
bits := mod(bits, 0x100)
}
// if bits is multiple of 8 (byte size), we can simply use identity precompile for cheap memcopy.
// otherwise we shift each word, starting at the least signifcant word, one-by-one using the mask technique.
// TODO it is possible to do this without the last two operations, see SHL identity copy.
let bn_val_ptr := mload(bn)
switch eq(mod(bits, 8), 0)
case 1 {
let bytes_shift := div(bits, 8)
let in := mload(bn)
let inlength := mload(in)
let insize := add(inlength, 0x20)
let out := add(in, bytes_shift)
let outsize := sub(insize, bytes_shift)
let success := staticcall(450, 0x4, in, insize, out, insize)
mstore8(add(out, 0x1f), 0) // maintain our BN layout following identity call:
mstore(in, inlength) // set current length byte to 0, and reset old length.
}
default {
let mask
let lsw
let mask_shift := sub(0x100, bits)
let lsw_ptr := add(bn_val_ptr, length)
for { let i := length } eq(eq(i, 0), 0) { i := sub(i, 0x20) } {
// for(int i=max_length; i!=0; i-=32)
switch eq(i, 0x20)
// if i==32:
case 1 { mask := 0 }
// - handles lsword: no mask needed.
default { mask := mload(sub(lsw_ptr, 0x20)) } // - else get mask (previous word)
lsw := shr(bits, mload(lsw_ptr)) // right shift current by bits
mask := shl(mask_shift, mask) // left shift next significant word by mask_shift
mstore(lsw_ptr, or(lsw, mask)) // store OR'd mask and shifted bits in-place
lsw_ptr := sub(lsw_ptr, 0x20) // point to next bits.
}
}
// The following removes the leading word containing all zeroes in the result should it exist,
// as well as updating lengths and pointers as necessary.
let msw_ptr := add(bn_val_ptr, 0x20)
switch eq(mload(msw_ptr), 0)
case 1 {
mstore(msw_ptr, sub(mload(bn_val_ptr), 0x20)) // store new length in new position
mstore(bn, msw_ptr) // update pointer from bn
}
default { }
}
return bn;
}
/**
* @notice left shift BigNumber value
* @dev shr: left shift BigNumber a by 'bits' bits.
* ensures the value is not negative before calling the private function.
* @param a BigNumber value to shift
* @param bits amount of bits to shift by
* @return result BigNumber
*/
function shl(BigNumber memory a, uint256 bits) public view returns (BigNumber memory) {
require(!a.neg);
return _shl(a, bits);
}
/**
* @notice sha3 hash a BigNumber.
* @dev hash: takes a BigNumber and performs sha3 hash on it.
* we hash each BigNumber WITHOUT it's first word - first word is a pointer to the start of the bytes value,
* and so is different for each struct.
*
* @param a BigNumber
* @return h bytes32 hash.
*/
function hash(BigNumber memory a) public pure returns (bytes32 h) {
//amount of words to hash = all words of the value and three extra words: neg, bitlen & value length.
assembly {
h := keccak256(add(a, 0x20), add(mload(mload(a)), 0x60))
}
}
/**
* @notice BigNumber full zero check
* @dev isZero: checks if the BigNumber is in the default zero format for BNs (ie. the result from zero()).
*
* @param a BigNumber
* @return boolean result.
*/
function isZero(BigNumber memory a) public pure returns (bool) {
return isZero(a.val) && a.val.length == 0x20 && !a.neg && a.bitlen == 0;
}
/**
* @notice bytes zero check
* @dev isZero: checks if input bytes value resolves to zero.
*
* @param a bytes value
* @return boolean result.
*/
function isZero(bytes memory a) public pure returns (bool) {
uint256 msword;
uint256 msword_ptr;
assembly {
msword_ptr := add(a, 0x20)
}
for (uint256 i = 0; i < a.length; i += 32) {
assembly {
msword := mload(msword_ptr)
} // get msword of input
if (msword > 0) return false;
assembly {
msword_ptr := add(msword_ptr, 0x20)
}
}
return true;
}
/**
* @notice BigNumber value bit length
* @dev bitLength: returns BigNumber value bit length- ie. log2 (most significant bit of value)
*
* @param a BigNumber
* @return uint bit length result.
*/
function bitLength(BigNumber memory a) public pure returns (uint256) {
return bitLength(a.val);
}
/**
* @notice bytes bit length
* @dev bitLength: returns bytes bit length- ie. log2 (most significant bit of value)
*
* @param a bytes value
* @return r uint bit length result.
*/
function bitLength(bytes memory a) public pure returns (uint256 r) {
if (isZero(a)) return 0;
uint256 msword;
assembly {
msword := mload(add(a, 0x20)) // get msword of input
}
r = bitLength(msword); // get bitlen of msword, add to size of remaining words.
assembly {
r := add(r, mul(sub(mload(a), 0x20), 8)) // res += (val.length-32)*8;
}
}
/**
* @notice uint bit length
* @dev bitLength: get the bit length of a uint input - ie. log2 (most significant bit of 256 bit value (one EVM word))
* credit: Tjaden Hess @ ethereum.stackexchange
* @param a uint value
* @return r uint bit length result.
*/
function bitLength(uint256 a) public pure returns (uint256 r) {
assembly {
switch eq(a, 0)
case 1 { r := 0 }
default {
let arg := a
a := sub(a, 1)
a := or(a, div(a, 0x02))
a := or(a, div(a, 0x04))
a := or(a, div(a, 0x10))
a := or(a, div(a, 0x100))
a := or(a, div(a, 0x10000))
a := or(a, div(a, 0x100000000))
a := or(a, div(a, 0x10000000000000000))
a := or(a, div(a, 0x100000000000000000000000000000000))
a := add(a, 1)
let m := mload(0x40)
mstore(m, 0xf8f9cbfae6cc78fbefe7cdc3a1793dfcf4f0e8bbd8cec470b6a28a7a5a3e1efd)
mstore(
add(m, 0x20), 0xf5ecf1b3e9debc68e1d9cfabc5997135bfb7a7a3938b7b606b5b4b3f2f1f0ffe
)
mstore(
add(m, 0x40), 0xf6e4ed9ff2d6b458eadcdf97bd91692de2d4da8fd2d0ac50c6ae9a8272523616
)
mstore(
add(m, 0x60), 0xc8c0b887b0a8a4489c948c7f847c6125746c645c544c444038302820181008ff
)
mstore(
add(m, 0x80), 0xf7cae577eec2a03cf3bad76fb589591debb2dd67e0aa9834bea6925f6a4a2e0e
)
mstore(
add(m, 0xa0), 0xe39ed557db96902cd38ed14fad815115c786af479b7e83247363534337271707
)
mstore(
add(m, 0xc0), 0xc976c13bb96e881cb166a933a55e490d9d56952b8d4e801485467d2362422606
)
mstore(
add(m, 0xe0), 0x753a6d1b65325d0c552a4d1345224105391a310b29122104190a110309020100
)
mstore(0x40, add(m, 0x100))
let magic := 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff
let shift := 0x100000000000000000000000000000000000000000000000000000000000000
let _a := div(mul(a, magic), shift)
r := div(mload(add(m, sub(255, _a))), shift)
r :=
add(
r,
mul(
256,
gt(arg, 0x8000000000000000000000000000000000000000000000000000000000000000)
)
)
// where a is a power of two, result needs to be incremented. we use the power of two trick here: if(arg & arg-1 == 0) ++r;
if eq(and(arg, sub(arg, 1)), 0) { r := add(r, 1) }
}
}
}
/**
* @notice BigNumber zero value
* @dev zero: returns zero encoded as a BigNumber
* @return zero encoded as BigNumber
*/
function zero() public pure returns (BigNumber memory) {
return BigNumber(ZERO, false, 0);
}
/**
* @notice BigNumber one value
* @dev one: returns one encoded as a BigNumber
* @return one encoded as BigNumber
*/
function one() public pure returns (BigNumber memory) {
return BigNumber(ONE, false, 1);
}
/**
* @notice BigNumber two value
* @dev two: returns two encoded as a BigNumber
* @return two encoded as BigNumber
*/
function two() public pure returns (BigNumber memory) {
return BigNumber(TWO, false, 2);
}
// ***************** END EXPOSED HELPER FUNCTIONS ******************
// ***************** START PRIVATE MANAGEMENT FUNCTIONS ******************
/**
* @notice Create a new BigNumber.
* @dev init: overloading allows caller to obtionally pass bitlen where it is known - as it is cheaper to do off-chain and verify on-chain.
* we assert input is in data structure as defined above, and that bitlen, if passed, is correct.
* 'copy' parameter indicates whether or not to copy the contents of val to a new location in memory (for example where you pass
* the contents of another variable's value in)
* @param val bytes - bignum value.
* @param neg bool - sign of value
* @param bitlen uint - bit length of value
* @return r BigNumber initialized value.
*/
function _init(bytes memory val, bool neg, uint256 bitlen)
private
view
returns (BigNumber memory r)
{
// use identity at location 0x4 for cheap memcpy.
// grab contents of val, load starting from memory end, update memory end pointer.
assembly {
let data := add(val, 0x20)
let length := mload(val)
let out
let freemem := msize()
switch eq(mod(length, 0x20), 0)
// if(val.length % 32 == 0)
case 1 {
out := add(freemem, 0x20) // freememory location + length word
mstore(freemem, length) // set new length
}
default {
let offset := sub(0x20, mod(length, 0x20)) // offset: 32 - (length % 32)
out := add(add(freemem, offset), 0x20) // freememory location + offset + length word
mstore(freemem, add(length, offset)) // set new length
}
pop(staticcall(450, 0x4, data, length, out, length)) // copy into 'out' memory location
mstore(0x40, add(freemem, add(mload(freemem), 0x20))) // update the free memory pointer
// handle leading zero words. assume freemem is pointer to bytes value
let bn_length := mload(freemem)
for { } eq(eq(bn_length, 0x20), 0) { } {
// for(; length!=32; length-=32)
switch eq(mload(add(freemem, 0x20)), 0)
// if(msword==0):
case 1 { freemem := add(freemem, 0x20) }
// update length pointer
default { break } // else: loop termination. non-zero word found
bn_length := sub(bn_length, 0x20)
}
mstore(freemem, bn_length)
mstore(r, freemem) // store new bytes value in r
mstore(add(r, 0x20), neg) // store neg value in r
}
r.bitlen = bitlen == 0 ? bitLength(r.val) : bitlen;
}
// ***************** END PRIVATE MANAGEMENT FUNCTIONS ******************
// ***************** START PRIVATE CORE CALCULATION FUNCTIONS ******************
/**
* @notice takes two BigNumber memory values and the bitlen of the max value, and adds them.
* @dev _add: This function is private and only callable from add: therefore the values may be of different sizes,
* in any order of size, and of different signs (handled in add).
* As values may be of different sizes, inputs are considered starting from the least significant
* words, working back.
* The function calculates the new bitlen (basically if bitlens are the same for max and min,
* max_bitlen++) and returns a new BigNumber memory value.
*
* @param max bytes - biggest value (determined from add)
* @param min bytes - smallest value (determined from add)
* @param max_bitlen uint - bit length of max value.
* @return bytes result - max + min.
* @return uint - bit length of result.
*/
function _add(bytes memory max, bytes memory min, uint256 max_bitlen)
private
pure
returns (bytes memory, uint256)
{
bytes memory result;
assembly {
let result_start := msize() // Get the highest available block of memory
let carry := 0
let uint_max := sub(0, 1)
let max_ptr := add(max, mload(max))
let min_ptr := add(min, mload(min)) // point to last word of each byte array.
let result_ptr := add(add(result_start, 0x20), mload(max)) // set result_ptr end.
for { let i := mload(max) } eq(eq(i, 0), 0) { i := sub(i, 0x20) } {
// for(int i=max_length; i!=0; i-=32)
let max_val := mload(max_ptr) // get next word for 'max'
switch gt(i, sub(mload(max), mload(min)))
// if(i>(max_length-min_length)). while
// 'min' words are still available.
case 1 {
let min_val := mload(min_ptr) // get next word for 'min'
mstore(result_ptr, add(add(max_val, min_val), carry)) // result_word = max_word+min_word+carry
switch gt(max_val, sub(uint_max, sub(min_val, carry)))
// this switch block finds whether or
// not to set the carry bit for the
// next iteration.
case 1 { carry := 1 }
default {
switch and(eq(max_val, uint_max), or(gt(carry, 0), gt(min_val, 0)))
case 1 { carry := 1 }
default { carry := 0 }
}
min_ptr := sub(min_ptr, 0x20) // point to next 'min' word
}
default {
// else: remainder after 'min' words are complete.
mstore(result_ptr, add(max_val, carry)) // result_word = max_word+carry
switch and(eq(uint_max, max_val), eq(carry, 1))
// this switch block finds whether or
// not to set the carry bit for the
// next iteration.
case 1 { carry := 1 }
default { carry := 0 }
}
result_ptr := sub(result_ptr, 0x20) // point to next 'result' word
max_ptr := sub(max_ptr, 0x20) // point to next 'max' word
}
switch eq(carry, 0)
case 1 { result_start := add(result_start, 0x20) }
// if carry is 0, increment result_start, ie.
// length word for result is now one word
// position ahead.
default { mstore(result_ptr, 1) } // else if carry is 1, store 1; overflow has
// occured, so length word remains in the
// same position.
result := result_start // point 'result' bytes value to the correct
// address in memory.
mstore(result, add(mload(max), mul(0x20, carry))) // store length of result. we are finished
// with the byte array.
mstore(0x40, add(result, add(mload(result), 0x20))) // Update freemem pointer to point to new
// end of memory.
// we now calculate the result's bit length.
// with addition, if we assume that some a is at least equal to some b, then the resulting bit length will
// be a's bit length or (a's bit length)+1, depending on carry bit.this is cheaper than calling bitLength.
let msword := mload(add(result, 0x20)) // get most significant word of result
// if(carry==1 || msword>>(max_bitlen % 256)==1):
if or(eq(carry, 1), eq(shr(mod(max_bitlen, 256), msword), 1)) {
max_bitlen := add(max_bitlen, 1)
} // if msword's bit length is 1 greater
// than max_bitlen, OR overflow occured,
// new bitlen is max_bitlen+1.
}
return (result, max_bitlen);
}
/**
* @notice takes two BigNumber memory values and subtracts them.
* @dev privSub: This function is private and only callable from add: therefore the values may be of different sizes,
* in any order of size, and of different signs (handled in add).
* As values may be of different sizes, inputs are considered starting from the least significant words,
* working back.
* The function calculates the new bitlen (basically if bitlens are the same for max and min,
* max_bitlen++) and returns a new BigNumber memory value.
*
* @param max bytes - biggest value (determined from add)
* @param min bytes - smallest value (determined from add)
* @return bytes result - max + min.
* @return uint - bit length of result.
*/
function privSub(bytes memory max, bytes memory min)
public
pure
returns (bytes memory, uint256)
{
bytes memory result;
uint256 carry = 0;
uint256 uint_max = type(uint256).max;
assembly {
let result_start := msize() // Get the highest available block of
// memory
let max_len := mload(max)
let min_len := mload(min) // load lengths of inputs
let len_diff := sub(max_len, min_len) // get differences in lengths.
let max_ptr := add(max, max_len)
let min_ptr := add(min, min_len) // go to end of arrays
let result_ptr := add(result_start, max_len) // point to least significant result
// word.
let memory_end := add(result_ptr, 0x20) // save memory_end to update free memory
// pointer at the end.
for { let i := max_len } eq(eq(i, 0), 0) { i := sub(i, 0x20) } {
// for(int i=max_length; i!=0; i-=32)
let max_val := mload(max_ptr) // get next word for 'max'
switch gt(i, len_diff)
// if(i>(max_length-min_length)). while
// 'min' words are still available.
case 1 {
let min_val := mload(min_ptr) // get next word for 'min'
mstore(result_ptr, sub(sub(max_val, min_val), carry)) // result_word = (max_word-min_word)-carry
switch or(
lt(max_val, add(min_val, carry)), and(eq(min_val, uint_max), eq(carry, 1))
)
// this switch block finds whether or
// not to set the carry bit for the next iteration.
case 1 { carry := 1 }
default { carry := 0 }
min_ptr := sub(min_ptr, 0x20) // point to next 'result' word
}
default {
// else: remainder after 'min' words are complete.
mstore(result_ptr, sub(max_val, carry)) // result_word = max_word-carry
switch and(eq(max_val, 0), eq(carry, 1))
// this switch block finds whether or
// not to set the carry bit for the
// next iteration.
case 1 { carry := 1 }
default { carry := 0 }
}
result_ptr := sub(result_ptr, 0x20) // point to next 'result' word
max_ptr := sub(max_ptr, 0x20) // point to next 'max' word
}
//the following code removes any leading words containing all zeroes in the result.
result_ptr := add(result_ptr, 0x20)
// for(result_ptr+=32;; result==0; result_ptr+=32)
for { } eq(mload(result_ptr), 0) { result_ptr := add(result_ptr, 0x20) } {
result_start := add(result_start, 0x20) // push up the start pointer for the result
max_len := sub(max_len, 0x20) // subtract a word (32 bytes) from the
// result length.
}
result := result_start // point 'result' bytes value to
// the correct address in memory
mstore(result, max_len) // store length of result. we
// are finished with the byte array.
mstore(0x40, memory_end) // Update freemem pointer.
}
uint256 new_bitlen = bitLength(result); // calculate the result's
// bit length.
return (result, new_bitlen);
}
/**
* @notice gets the modulus value necessary for calculating exponetiation.
* @dev _powModulus: we must pass the minimum modulus value which would return JUST the a^b part of the calculation
* in modexp. the rationale here is:
* if 'a' has n bits, then a^e has at most n*e bits.
* using this modulus in exponetiation will result in simply a^e.
* therefore the value may be many words long.
* This is done by:
* - storing total modulus byte length
* - storing first word of modulus with correct bit set
* - updating the free memory pointer to come after total length.
*
* @param a BigNumber base
* @param e uint exponent
* @return BigNumber modulus result
*/
function _powModulus(BigNumber memory a, uint256 e) private pure returns (BigNumber memory) {
bytes memory _modulus = ZERO;
uint256 mod_index;
assembly {
mod_index := mul(mload(add(a, 0x40)), e) // a.bitlen * e is the max bitlength of result
let first_word_modulus := shl(mod(mod_index, 256), 1) // set bit in first modulus word.
mstore(_modulus, mul(add(div(mod_index, 256), 1), 0x20)) // store length of modulus
mstore(add(_modulus, 0x20), first_word_modulus) // set first modulus word
mstore(0x40, add(_modulus, add(mload(_modulus), 0x20))) // update freemem pointer to be modulus index
// + length
}
//create modulus BigNumber memory for modexp function
return BigNumber(_modulus, false, mod_index);
}
/**
* @notice Modular Exponentiation: Takes bytes values for base, exp, mod and calls precompile for (base^exp)%^mod
* @dev modexp: Wrapper for built-in modexp (contract 0x5) as described here:
* https://github.com/ethereum/EIPs/pull/198
*
* @param _b bytes base
* @param _e bytes base_inverse
* @param _m bytes exponent
* @param r bytes result.
*/
function _modexp(bytes memory _b, bytes memory _e, bytes memory _m)
private
view
returns (bytes memory r)
{
assembly {
let bl := mload(_b)
let el := mload(_e)
let ml := mload(_m)
let freemem := mload(0x40) // Free memory pointer is always stored at 0x40
mstore(freemem, bl) // arg[0] = base.length @ +0
mstore(add(freemem, 32), el) // arg[1] = exp.length @ +32
mstore(add(freemem, 64), ml) // arg[2] = mod.length @ +64
// arg[3] = base.bits @ + 96
// Use identity built-in (contract 0x4) as a cheap memcpy
let success := staticcall(450, 0x4, add(_b, 32), bl, add(freemem, 96), bl)
// arg[4] = exp.bits @ +96+base.length
let size := add(96, bl)
success := staticcall(450, 0x4, add(_e, 32), el, add(freemem, size), el)
// arg[5] = mod.bits @ +96+base.length+exp.length
size := add(size, el)
success := staticcall(450, 0x4, add(_m, 32), ml, add(freemem, size), ml)
switch success
case 0 { invalid() } //fail where we haven't enough gas to make the call
// Total size of input = 96+base.length+exp.length+mod.length
size := add(size, ml)
// Invoke contract 0x5, put return value right after mod.length, @ +96
success := staticcall(sub(gas(), 1350), 0x5, freemem, size, add(freemem, 0x60), ml)
switch success
case 0 { invalid() } //fail where we haven't enough gas to make the call
let length := ml
let msword_ptr := add(freemem, 0x60)
///the following code removes any leading words containing all zeroes in the result.
for { } eq(eq(length, 0x20), 0) { } {
// for(; length!=32; length-=32)
switch eq(mload(msword_ptr), 0)
// if(msword==0):
case 1 { msword_ptr := add(msword_ptr, 0x20) }
// update length pointer
default { break } // else: loop termination. non-zero word found
length := sub(length, 0x20)
}
r := sub(msword_ptr, 0x20)
mstore(r, length)
// point to the location of the return value (length, bits)
//assuming mod length is multiple of 32, return value is already in the right format.
mstore(0x40, add(add(96, freemem), ml)) //deallocate freemem pointer
}
}
// ***************** END PRIVATE CORE CALCULATION FUNCTIONS ******************
// ***************** START PRIVATE HELPER FUNCTIONS ******************
/**
* @notice left shift BigNumber memory 'dividend' by 'value' bits.
* @param bn value to shift
* @param bits amount of bits to shift by
* @return r result
*/
function _shl(BigNumber memory bn, uint256 bits) private view returns (BigNumber memory r) {
if (bits == 0 || bn.bitlen == 0) return bn;
// we start by creating an empty bytes array of the size of the output, based on 'bits'.
// for that we must get the amount of extra words needed for the output.
uint256 length = bn.val.length;
// position of bitlen in most significnat word
uint256 bit_position = ((bn.bitlen - 1) % 256) + 1;
// total extra words. we check if the bits remainder will add one more word.
uint256 extra_words = (bits / 256) + ((bits % 256) >= (256 - bit_position) ? 1 : 0);
// length of output
uint256 total_length = length + (extra_words * 0x20);
r.bitlen = bn.bitlen + (bits);
r.neg = bn.neg;
bits %= 256;
bytes memory bn_shift;
uint256 bn_shift_ptr;
// the following efficiently creates an empty byte array of size 'total_length'
assembly {
let freemem_ptr := mload(0x40) // get pointer to free memory
mstore(freemem_ptr, total_length) // store bytes length
let mem_end := add(freemem_ptr, total_length) // end of memory
mstore(mem_end, 0) // store 0 at memory end
bn_shift := freemem_ptr // set pointer to bytes
bn_shift_ptr := add(bn_shift, 0x20) // get bn_shift pointer
mstore(0x40, add(mem_end, 0x20)) // update freemem pointer
}
// use identity for cheap copy if bits is multiple of 8.
if (bits % 8 == 0) {
// calculate the position of the first byte in the result.
uint256 bytes_pos = ((256 - (((bn.bitlen - 1) + bits) % 256)) - 1) / 8;
uint256 insize = (bn.bitlen / 8) + ((bn.bitlen % 8 != 0) ? 1 : 0);
assembly {
let in := add(add(mload(bn), 0x20), div(sub(256, bit_position), 8))
let out := add(bn_shift_ptr, bytes_pos)
let success := staticcall(450, 0x4, in, insize, out, length)
}
r.val = bn_shift;
return r;
}
uint256 mask;
uint256 mask_shift = 0x100 - bits;
uint256 msw;
uint256 msw_ptr;
assembly {
msw_ptr := add(mload(bn), 0x20)
}
// handle first word before loop if the shift adds any extra words.
// the loop would handle it if the bit shift doesn't wrap into the next word,
// so we check only for that condition.
if ((bit_position + bits) > 256) {
assembly {
msw := mload(msw_ptr)
mstore(bn_shift_ptr, shr(mask_shift, msw))
bn_shift_ptr := add(bn_shift_ptr, 0x20)
}
}
// as a result of creating the empty array we just have to operate on the words in the original bn.
for (uint256 i = bn.val.length; i != 0; i -= 0x20) {
// for each word:
assembly {
msw := mload(msw_ptr) // get most significant word
switch eq(i, 0x20)
// if i==32:
case 1 { mask := 0 }
// handles msword: no mask needed.
default { mask := mload(add(msw_ptr, 0x20)) } // else get mask (next word)
msw := shl(bits, msw) // left shift current msw by 'bits'
mask := shr(mask_shift, mask) // right shift next significant word by mask_shift
mstore(bn_shift_ptr, or(msw, mask)) // store OR'd mask and shifted bits in-place
msw_ptr := add(msw_ptr, 0x20)
bn_shift_ptr := add(bn_shift_ptr, 0x20)
}
}
r.val = bn_shift;
}
// ***************** END PRIVATE HELPER FUNCTIONS ******************
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import "./BigNumbers/BigNumbers.sol";
import "./TexasHoldemRoom.sol";
/**
* @title CryptoUtils
* @dev Implements cryptographic utilities for mental poker
*/
contract CryptoUtils {
using BigNumbers for BigNumber;
// 2048-bit prime number
BigNumber private P_2048;
uint256 constant G_2048 = 2;
uint256 public constant MAX_PLAYERS = 10;
uint8 public constant EMPTY_SEAT = 255;
event CULog(string message);
constructor() {
// Initialize P_2048
// bytes memory p2048Bytes =
// hex"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718399549CCEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
bytes memory p2048Bytes =
hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1";
P_2048 = BigNumbers.init(p2048Bytes, false, 256);
}
struct EncryptedCard {
BigNumber c1; // 2048-bit number
BigNumber c2; // 2048-bit number
}
// May no longer be accurate
// /**
// * @dev Decrypts a card using ElGamal decryption
// * @param encryptedCard The encrypted card (c1, c2)
// * @param privateKey The private key of the player decrypting the card
// * @return The decrypted card
// */
// function decryptCard(EncryptedCard memory encryptedCard, uint256 privateKey)
// public
// view
// returns (BigNumber memory)
// {
// BigNumber memory c1PowPrivateKey =
// BigNumbers.modexp(encryptedCard.c1, BigNumbers.init(privateKey, false), P_2048);
// BigNumber memory c1Inverse = modInverse(c1PowPrivateKey, P_2048);
// return BigNumbers.modmul(encryptedCard.c2, c1Inverse, P_2048);
// }
// TODO: modify this function to just verify the modular inverse and have the user supply the result (c1Inverse)
/**
* @dev Decrypts a card using ElGamal decryption
* @param encryptedCard The encrypted card (c1, c2)
* @param privateKey The private key of the player decrypting the card
* @return The decrypted card
*/
function verifyDecryptCard(
EncryptedCard memory encryptedCard,
BigNumber memory privateKey,
BigNumber memory c1InversePowPrivateKey
) public returns (BigNumber memory) {
BigNumber memory c1PowPrivateKey = BigNumbers.modexp(encryptedCard.c1, privateKey, P_2048);
emit CULog("c1PowPrivateKey");
bool verifyResult = BigNumbers.modinvVerify(c1PowPrivateKey, P_2048, c1InversePowPrivateKey);
emit CULog("verifyResult");
require(verifyResult, "Invalid modular inverse");
emit CULog("modmul");
return BigNumbers.modmul(encryptedCard.c2, c1InversePowPrivateKey, P_2048);
}
// This is on chain in case of a dispute and we want to verify that a user correctly encrypted each card
/**
* @dev Encrypts a message using ElGamal encryption
* @param message The message to encrypt (2048-bit)
* @param publicKey The public key to encrypt with
* @param r Optional random value (if not provided, will be generated)
* @return The encrypted message (c1, c2)
*/
function encryptMessageBigint(BigNumber memory message, uint256 publicKey, uint256 r)
public
view
returns (EncryptedCard memory)
{
BigNumber memory rToUse;
if (r == 0) {
// Generate random 2048-bit number
bytes32 entropy = keccak256(abi.encodePacked(block.timestamp, block.prevrandao));
bytes memory randomBytes = new bytes(32); // 2048 bits = 256 bytes, 256 bits = 32 bytes
for (uint256 i = 0; i < 32; i += 32) {
bytes32 randomWord = keccak256(abi.encodePacked(entropy, i));
assembly {
mstore(add(add(randomBytes, 0x20), i), randomWord)
}
}
rToUse = BigNumbers.init(randomBytes, false);
} else {
rToUse = BigNumbers.init(r, false);
}
BigNumber memory g = BigNumbers.init(G_2048, false);
BigNumber memory c1 = BigNumbers.modexp(g, rToUse, P_2048);
BigNumber memory pubKey = BigNumbers.init(publicKey, false);
BigNumber memory c2 =
BigNumbers.modmul(BigNumbers.modexp(pubKey, rToUse, P_2048), message, P_2048);
return EncryptedCard(c1, c2);
}
function decodeBigintMessage(BigNumber memory message) public pure returns (string memory) {
// possibly put this in decryptCard(), but don't want extra gas cost for all intermediate decryptions
// Extract the actual value from BigNumber, ignoring leading zeros
bytes memory decryptedBytes = message.val;
uint256 startIndex = 0;
// Find the first non-zero byte
for (uint256 i = 0; i < decryptedBytes.length; i++) {
if (decryptedBytes[i] != 0) {
startIndex = i;
break;
}
}
// Create a new bytes array with only the significant bytes
bytes memory trimmedBytes = new bytes(decryptedBytes.length - startIndex);
for (uint256 i = 0; i < trimmedBytes.length; i++) {
trimmedBytes[i] = decryptedBytes[i + startIndex];
}
string memory decryptedCardString = string(trimmedBytes);
return decryptedCardString;
}
// string equality check
function strEq(string memory a, string memory b) public pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import "./BigNumbers/BigNumbers.sol";
import "./CryptoUtils.sol";
import "./TexasHoldemRoom.sol";
import "./PokerHandEvaluatorv2.sol";
contract DeckHandler {
using BigNumbers for BigNumber;
BigNumber[] public encryptedDeck;
string[5] public communityCards;
CryptoUtils public cryptoUtils;
TexasHoldemRoom public texasHoldemRoom;
PokerHandEvaluatorv2 public handEvaluator;
event EncryptedShuffleSubmitted(address indexed player, bytes[] encryptedShuffle);
event DecryptionValuesSubmitted(
address indexed player, uint8[] cardIndexes, bytes[] decryptionValues
);
event FlopRevealed(string card1, string card2, string card3);
event TurnRevealed(string card1);
event RiverRevealed(string card1);
event PlayerRevealingCards(
address indexed player, bytes c1, bytes privateKey, bytes c1InversePowPrivateKey
);
event PlayerCardsRevealed(
address indexed player,
string card1,
string card2,
PokerHandEvaluatorv2.HandRank rank,
uint256 handScore
);
// event THP_Log(string message);
constructor(address _texasHoldemRoom, address _cryptoUtils, address _handEvaluator) {
texasHoldemRoom = TexasHoldemRoom(_texasHoldemRoom);
cryptoUtils = CryptoUtils(_cryptoUtils);
handEvaluator = PokerHandEvaluatorv2(_handEvaluator);
for (uint256 i = 0; i < 52; i++) {
encryptedDeck.push(BigNumber({ val: "0", neg: false, bitlen: 256 }));
}
}
// called when a new round starts, only callable by the room contract
function resetDeck() external {
require(msg.sender == address(texasHoldemRoom), "Only the room contract can reset the deck");
for (uint8 i = 0; i < 52; i++) {
encryptedDeck[i] = BigNumber({ val: "0", neg: false, bitlen: 256 });
}
communityCards = ["", "", "", "", ""];
}
function submitEncryptedShuffle(bytes[] memory encryptedShuffle) external {
require(encryptedShuffle.length == 52, "Must provide exactly 52 cards");
require(texasHoldemRoom.stage() == TexasHoldemRoom.GameStage.Shuffle, "Wrong stage");
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
require(currentPlayerIndex == playerIndex, "Not your turn to shuffle");
// Store shuffle as an action?
emit EncryptedShuffleSubmitted(msg.sender, encryptedShuffle);
// Copy each element individually since direct array assignment is not supported
for (uint8 i = 0; i < encryptedShuffle.length; i++) {
encryptedDeck[i] = BigNumbers.init(encryptedShuffle[i], false, 256);
}
texasHoldemRoom.progressGame();
}
// TODO(high): verify that the card indicies are valid for the number of players and stage
function submitDecryptionValues(uint8[] memory cardIndexes, bytes[] memory decryptionValues)
external
{
TexasHoldemRoom.GameStage stage = texasHoldemRoom.stage();
require(
stage == TexasHoldemRoom.GameStage.RevealDeal
|| stage == TexasHoldemRoom.GameStage.RevealFlop
|| stage == TexasHoldemRoom.GameStage.RevealTurn
|| stage == TexasHoldemRoom.GameStage.RevealRiver,
"Game is not in a reveal stage"
);
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
require(currentPlayerIndex == playerIndex, "Not your turn to decrypt");
require(
cardIndexes.length == decryptionValues.length,
"Mismatch in cardIndexes and decryptionValues lengths"
);
// TODO: verify decryption values?
// TODO: verify decryption indexes?
emit DecryptionValuesSubmitted(msg.sender, cardIndexes, decryptionValues);
for (uint8 i = 0; i < cardIndexes.length; i++) {
encryptedDeck[cardIndexes[i]] = BigNumbers.init(decryptionValues[i], false, 256);
}
// The dealer always starts decrypting, so when we are back at the dealer,
// we know the last player has decrypted community cards, so emit/set the community cards
uint8 nextRevealPlayer = texasHoldemRoom.getNextActivePlayer(true);
if (nextRevealPlayer == texasHoldemRoom.dealerPosition()) {
if (stage == TexasHoldemRoom.GameStage.RevealFlop) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
string memory card2 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[1]]);
string memory card3 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[2]]);
emit FlopRevealed(card1, card2, card3);
communityCards[0] = card1;
communityCards[1] = card2;
communityCards[2] = card3;
} else if (stage == TexasHoldemRoom.GameStage.RevealTurn) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
emit TurnRevealed(card1);
communityCards[3] = card1;
} else if (stage == TexasHoldemRoom.GameStage.RevealRiver) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
emit RiverRevealed(card1);
communityCards[4] = card1;
}
}
texasHoldemRoom.progressGame();
}
/**
* @dev Reveals the player's cards.
* @dev Many todos: validate the encrypted cards, the card indexes for the player are valid (getCardIndexForPlayer in js),
* @dev and that it is appropriate (showdown/all-in) to reveal cards.
* @param c1 The player's encryption key c1 (derived from the private key)
* @param privateKey The player's private key
* @param c1InversePowPrivateKey The inverse of the player's c1*privateKey modulo inverse for decryption
*/
function revealMyCards(
bytes memory c1,
bytes memory privateKey,
bytes memory c1InversePowPrivateKey
) external returns (string memory card1, string memory card2) {
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
TexasHoldemRoom.Player memory player = texasHoldemRoom.getPlayer(playerIndex);
// todo: uncomment this
require(!player.joinedAndWaitingForNextRound, "Player not joined after round started");
require(!player.hasFolded, "Player folded");
require(
cryptoUtils.strEq(player.cards[0], ""),
"Player already revealed cards (0) in this round"
);
require(
cryptoUtils.strEq(player.cards[1], ""),
"Player already revealed cards (1) in this round"
);
emit PlayerRevealingCards(msg.sender, c1, privateKey, c1InversePowPrivateKey);
// scope block to reduce number of variables in memory (evm stack depth limited to 16 variables)
{
uint8[2] memory playerCardIndexes = texasHoldemRoom.getPlayersCardIndexes(playerIndex);
// BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 2048);
// BigNumber memory privateKeyBN = BigNumber({ val: privateKey, neg: false, bitlen: 256 });
// BigNumber memory c1BN = BigNumber({ val: c1, neg: false, bitlen: 256 });
BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 256);
BigNumber memory c1BN = BigNumbers.init(c1, false, 256);
BigNumber memory encryptedCard1BN = encryptedDeck[playerCardIndexes[0]];
BigNumber memory encryptedCard2BN = encryptedDeck[playerCardIndexes[1]];
CryptoUtils.EncryptedCard memory encryptedCard1Struct =
CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard1BN });
CryptoUtils.EncryptedCard memory encryptedCard2Struct =
CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard2BN });
BigNumber memory c1InversePowPrivateKeyBN =
BigNumbers.init(c1InversePowPrivateKey, false, 256);
BigNumber memory decryptedCard1 = cryptoUtils.verifyDecryptCard(
encryptedCard1Struct, privateKeyBN, c1InversePowPrivateKeyBN
);
BigNumber memory decryptedCard2 = cryptoUtils.verifyDecryptCard(
encryptedCard2Struct, privateKeyBN, c1InversePowPrivateKeyBN
);
// convert the decrypted cards to a string
card1 = cryptoUtils.decodeBigintMessage(decryptedCard1);
card2 = cryptoUtils.decodeBigintMessage(decryptedCard2);
}
player.cards[0] = card1;
player.cards[1] = card2;
// Get the player's hand score (using player's cards and community cards) from HandEvaluator
// Combine player cards and community cards into a single array
string[7] memory allCards;
allCards[0] = card1;
allCards[1] = card2;
allCards[2] = communityCards[0];
allCards[3] = communityCards[1];
allCards[4] = communityCards[2];
allCards[5] = communityCards[3];
allCards[6] = communityCards[4];
PokerHandEvaluatorv2.Hand memory playerHand = handEvaluator.findBestHandExternal(allCards);
uint256 playerHandScore = playerHand.score;
texasHoldemRoom.setPlayerHandScore(playerIndex, playerHandScore);
emit PlayerCardsRevealed(
address(msg.sender), card1, card2, playerHand.rank, playerHandScore
);
// emit THP_Log("emit PlayerCardsRevealed() in revealMyCards()");
// return true if the encrypted cards match the decrypted cards from the deck?
texasHoldemRoom.progressGame();
// emit THP_Log("after texasHoldemRoom.progressGame()");
return (card1, card2);
}
function getEncryptedDeck() external view returns (bytes[] memory) {
bytes[] memory deckBytes = new bytes[](encryptedDeck.length);
for (uint8 i = 0; i < encryptedDeck.length; i++) {
deckBytes[i] = encryptedDeck[i].val;
}
return deckBytes;
}
function getEncrypedCard(uint256 cardIndex) external view returns (BigNumber memory) {
return encryptedDeck[cardIndex];
}
function getCommunityCards() external view returns (string[5] memory) {
return communityCards;
}
/**
* @dev Returns all simple public variables of the TexasHoldemRoom contract and the encrypted deck
* To reduce the size of the TexasHoldemRoom contract, this function is put here.
*/
struct BulkRoomData {
uint256 roundNumber;
TexasHoldemRoom.GameStage stage;
uint256 smallBlind;
uint256 bigBlind;
uint8 dealerPosition;
uint8 currentPlayerIndex;
uint8 lastRaiseIndex;
uint256 pot;
uint256 currentStageBet;
uint8 numPlayers;
bool isPrivate;
string[5] communityCards;
bytes[] encryptedDeck;
uint256 lastActionTimestamp;
}
function getBulkRoomData() external view returns (BulkRoomData memory) {
return BulkRoomData({
roundNumber: texasHoldemRoom.roundNumber(),
stage: texasHoldemRoom.stage(),
smallBlind: texasHoldemRoom.smallBlind(),
bigBlind: texasHoldemRoom.bigBlind(),
dealerPosition: texasHoldemRoom.dealerPosition(),
currentPlayerIndex: texasHoldemRoom.currentPlayerIndex(),
lastRaiseIndex: texasHoldemRoom.lastRaiseIndex(),
pot: texasHoldemRoom.pot(),
currentStageBet: texasHoldemRoom.currentStageBet(),
numPlayers: texasHoldemRoom.numPlayers(),
isPrivate: texasHoldemRoom.isPrivate(),
encryptedDeck: this.getEncryptedDeck(),
communityCards: communityCards,
lastActionTimestamp: texasHoldemRoom.lastActionTimestamp()
});
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
contract PokerHandEvaluatorv2 {
event PHE_Log(string message);
enum HandRank {
HighCard, // 0
Pair, // 1
TwoPair, // 2
ThreeOfAKind, // 3
Straight, // 4
Flush, // 5
FullHouse, // 6
FourOfAKind, // 7
StraightFlush, // 8
RoyalFlush // 9
}
struct Card {
uint8 rank; // 2-14 (14 = AceHigh)
uint8 suit; // 0-3 (Hearts, Diamonds, Clubs, Spades)
}
struct Hand {
HandRank rank;
uint256 score;
Card[5] bestHand;
}
function uintToString(uint8 value) public pure returns (string memory) {
// Special case for 0
if (value == 0) {
return "0";
}
// Find length of number by counting digits
uint8 length = 0;
uint8 temp = value;
while (temp != 0) {
length++;
temp /= 10;
}
// Create bytes array of the right length
bytes memory buffer = new bytes(length);
// Fill buffer from right to left
uint8 i = length;
while (value != 0) {
buffer[--i] = bytes1(uint8(48 + value % 10));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a card string representation (0-51) to a Card struct
* @param cardStr The string representation of the card (0-51)
* @return A Card struct with the appropriate rank and suit
*
* Card mapping:
* - Hearts: 0-12 (2-A)
* - Diamonds: 13-25 (2-A)
* - Clubs: 26-38 (2-A)
* - Spades: 39-51 (2-A)
*/
function stringToCard(string memory cardStr) public pure returns (Card memory) {
require(
bytes(cardStr).length == 1 || bytes(cardStr).length == 2, "Invalid card string length"
);
uint8 cardNum = uint8(parseInt(cardStr));
require(cardNum < 52, "Invalid card number");
uint8 suit = cardNum / 13;
uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
return Card({ rank: rank, suit: suit });
}
/**
* @dev Converts a card string representation (0-51) to a string
* @param cardStr The string representation of the card (0-51)
* @return A string with the appropriate rank and suit
*
* Card mapping:
* - Hearts: 0-12 (2-A)
* - Diamonds: 13-25 (2-A)
* - Clubs: 26-38 (2-A)
* - Spades: 39-51 (2-A)
*/
function stringToHumanReadable(string memory cardStr) public pure returns (string memory) {
uint8 cardNum = uint8(parseInt(cardStr));
require(cardNum < 52, "Invalid card number");
uint8 suit = cardNum / 13;
uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
string memory suitStr;
if (suit <= 0) {
suitStr = "H";
} else if (suit <= 1) {
suitStr = "D";
} else if (suit <= 2) {
suitStr = "C";
} else {
suitStr = "S";
}
string memory rankStr;
if (rank <= 10) {
rankStr = uintToString(rank);
} else if (rank == 11) {
rankStr = "J";
} else if (rank == 12) {
rankStr = "Q";
} else if (rank == 13) {
rankStr = "K";
} else {
rankStr = "A";
}
return string.concat(rankStr, suitStr);
}
/**
* @dev Converts a human readable card string to a Card struct
* @dev Example: "2H" -> Card(2, 0)
* @dev Example: "AH" -> Card(14, 0)
* @dev Example: "10D" -> Card(21, 1)
* @param cardStr The human readable card string
* @return A Card struct with the appropriate rank and suit
*/
function humanReadableToCard(string memory cardStr) public pure returns (Card memory) {
bytes memory cardBytes = bytes(cardStr);
string memory rankStr;
string memory suitStr = new string(1);
if (cardBytes.length > 2 && cardBytes[0] == "1" && cardBytes[1] == "0") {
// Handle "10" as a special case
rankStr = "10";
bytes(suitStr)[0] = cardBytes[2];
} else {
// For all other cards, just take the first character
bytes(suitStr)[0] = cardBytes[cardBytes.length - 1];
rankStr = new string(1);
bytes(rankStr)[0] = cardBytes[0];
// if rank is J, Q, K, A
if (cardBytes[0] == "J") {
rankStr = "11";
} else if (cardBytes[0] == "Q") {
rankStr = "12";
} else if (cardBytes[0] == "K") {
rankStr = "13";
} else if (cardBytes[0] == "A") {
rankStr = "14";
}
}
uint8 rank = uint8(parseInt(rankStr));
uint8 suit = 0;
if (strEq(suitStr, "H")) {
suit = 0;
} else if (strEq(suitStr, "D")) {
suit = 1;
} else if (strEq(suitStr, "C")) {
suit = 2;
} else if (strEq(suitStr, "S")) {
suit = 3;
}
return Card({ rank: rank, suit: suit });
}
/**
* @dev Helper function to parse a string to an integer
* @param s The string to parse
* @return The parsed integer value
*/
function parseInt(string memory s) public pure returns (uint256) {
bytes memory b = bytes(s);
uint256 result = 0;
for (uint256 i = 0; i < b.length; i++) {
uint8 c = uint8(b[i]);
if (c >= 48 && c <= 57) {
result = result * 10 + (c - 48);
}
}
return result;
}
function evaluateHand(Card[2] memory holeCards, Card[5] memory communityCards)
public
pure
returns (Hand memory)
{
Card[7] memory allCards;
allCards[0] = holeCards[0];
allCards[1] = holeCards[1];
for (uint256 i = 0; i < 5; i++) {
allCards[i + 2] = communityCards[i];
}
return findBestHand(allCards);
}
function findBestHandExternal(string[7] memory cards) public pure returns (Hand memory) {
Card[7] memory cardArray;
for (uint256 i = 0; i < 7; i++) {
cardArray[i] = stringToCard(cards[i]);
}
return findBestHand(cardArray);
}
// For test cases to use human readable strings
function findBestHandExternal2(string[7] memory cards) public pure returns (Hand memory) {
Card[7] memory cardArray;
for (uint256 i = 0; i < 7; i++) {
cardArray[i] = humanReadableToCard(cards[i]);
}
return findBestHand(cardArray);
}
// TODO(medium): have this return the indicies for the best hand (5 card indicies)
function findBestHand(Card[7] memory cards) internal pure returns (Hand memory) {
// Sort cards by rank (ascending order)
// Example: [2♥, 3♠, 5♦, 8♣, 10♥, J♦, A♠]
// Cards are sorted from lowest to highest rank
for (uint256 i = 0; i < cards.length - 1; i++) {
for (uint256 j = 0; j < cards.length - i - 1; j++) {
if (cards[j].rank > cards[j + 1].rank) {
Card memory temp = cards[j];
cards[j] = cards[j + 1];
cards[j + 1] = temp;
}
}
}
// emit PHE_Log("after card creation loop ");
// Check for each hand type from highest to lowest
Hand memory bestHand;
uint256 score = 0;
uint8 rank = 0;
// Check Royal Flush
(rank, score) = hasRoyalFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.RoyalFlush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after royal flush ");
// Check Straight Flush
(rank, score) = hasStraightFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.StraightFlush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after straight flush ");
// Check Four of a Kind
(rank, score) = hasFourOfAKind(cards);
if (score > 0) {
bestHand.rank = HandRank.FourOfAKind;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after four of a kind ");
// Check Full House
(rank, score) = hasFullHouse(cards);
if (score > 0) {
bestHand.rank = HandRank.FullHouse;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after full house ");
// Check Flush
(rank, score) = hasFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.Flush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after flush ");
// Check Straight
(rank, score) = hasStraight(cards);
if (score > 0) {
bestHand.rank = HandRank.Straight;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after straight ");
// Check Three of a Kind
(rank, score) = hasThreeOfAKind(cards);
if (score > 0) {
bestHand.rank = HandRank.ThreeOfAKind;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after three of a kind ");
// Check Two Pair
(rank, score) = hasTwoPair(cards);
if (score > 0) {
bestHand.rank = HandRank.TwoPair;
bestHand.score = score;
// emit PHE_Log("has two pair! before return ");
return bestHand;
}
// emit PHE_Log("after two pair ");
// Check Pair
(rank, score) = hasPair(cards);
if (score > 0) {
bestHand.rank = HandRank.Pair;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after pair ");
// Default to high card if no other hand is found
(rank, score) = hasHighCard(cards);
bestHand.rank = HandRank.HighCard;
bestHand.score = score;
// emit PHE_Log("after high card ");
return bestHand;
}
function hasRoyalFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint8 suit = 0; suit < 4; suit++) {
bool hasAce = false;
bool hasKing = false;
bool hasQueen = false;
bool hasJack = false;
bool hasTen = false;
for (uint8 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
if (cards[i].rank == 14) hasAce = true;
if (cards[i].rank == 13) hasKing = true;
if (cards[i].rank == 12) hasQueen = true;
if (cards[i].rank == 11) hasJack = true;
if (cards[i].rank == 10) hasTen = true;
}
}
if (hasAce && hasKing && hasQueen && hasJack && hasTen) {
return (10, 10 * 10 ** 14);
}
}
return (0, 0);
}
function hasStraightFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint8 suit = 0; suit < 4; suit++) {
uint8[] memory suitCards = new uint8[](7);
uint8 suitCount = 0;
for (uint256 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
suitCards[suitCount] = cards[i].rank;
suitCount++;
}
}
if (suitCount >= 5) {
// Sort suit cards
for (uint8 i = 0; i < suitCount - 1; i++) {
for (uint8 j = 0; j < suitCount - i - 1; j++) {
if (suitCards[j] > suitCards[j + 1]) {
uint8 temp = suitCards[j];
suitCards[j] = suitCards[j + 1];
suitCards[j + 1] = temp;
}
}
}
// Check for straight in suited cards
for (uint8 i = 0; i <= suitCount - 5; i++) {
if (suitCards[i + 4] == suitCards[i] + 4) {
return (9, 9 * 10 ** 14 + suitCards[i + 4]);
}
}
}
}
return (0, 0);
}
function hasFourOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint256 i = 0; i < 7; i++) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count == 4) {
// Find highest kicker
uint8 kicker = 0;
for (uint256 k = 0; k < 7; k++) {
if (cards[k].rank != rank && cards[k].rank > kicker) {
kicker = cards[k].rank;
}
}
return (8, 8 * 10 ** 14 + uint256(rank) * 10 ** 2 + uint256(kicker));
}
}
return (0, 0);
}
function hasFullHouse(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8 threeOfAKindRank = 0;
uint8 pairRank = 0;
// Find three of a kind
for (uint256 i = 0; i < 7; i++) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count >= 3 && rank > threeOfAKindRank) {
threeOfAKindRank = rank;
}
}
if (threeOfAKindRank == 0) {
return (0, 0);
}
// Find pair (different from three of a kind)
for (uint256 i = 0; i < 7; i++) {
if (cards[i].rank != threeOfAKindRank) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count >= 2 && rank > pairRank) {
pairRank = rank;
}
}
}
if (pairRank > 0) {
return (7, 7 * 10 ** 14 + uint256(threeOfAKindRank) * 10 ** 2 + uint256(pairRank));
}
return (0, 0);
}
function hasFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[5] memory flushCards;
for (uint8 suit = 0; suit < 4; suit++) {
uint8[] memory suitCards = new uint8[](7);
uint8 suitCount = 0;
for (uint8 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
suitCards[suitCount] = cards[i].rank;
suitCount++;
}
}
if (suitCount >= 5) {
// Sort suit cards in descending order
for (uint8 i = 0; i < suitCount - 1; i++) {
for (uint8 j = 0; j < suitCount - i - 1; j++) {
if (suitCards[j] < suitCards[j + 1]) {
uint8 temp = suitCards[j];
suitCards[j] = suitCards[j + 1];
suitCards[j + 1] = temp;
}
}
}
// Take the highest 5 cards
for (uint8 i = 0; i < 5; i++) {
flushCards[i] = suitCards[i];
}
// Calculate score with proper ordering (highest to lowest)
uint256 score = 6 * 10 ** 14;
score += uint256(flushCards[0]) * 10 ** 8;
score += uint256(flushCards[1]) * 10 ** 6;
score += uint256(flushCards[2]) * 10 ** 4;
score += uint256(flushCards[3]) * 10 ** 2;
score += uint256(flushCards[4]);
return (6, score);
}
}
return (0, 0);
}
function hasStraight(Card[7] memory cards) internal pure returns (uint8, uint256) {
// Remove duplicates
uint8[] memory uniqueRanks = new uint8[](15); // Max 14 ranks + 1 for Ace as 1
uint8 uniqueCount = 0;
for (uint8 i = 0; i < 7; i++) {
bool isDuplicate = false;
for (uint8 j = 0; j < uniqueCount; j++) {
if (uniqueRanks[j] == cards[i].rank) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
uniqueRanks[uniqueCount] = cards[i].rank;
uniqueCount++;
}
}
// Add Ace as 1 if Ace exists
bool hasAce = false;
for (uint8 i = 0; i < uniqueCount; i++) {
if (uniqueRanks[i] == 14) {
hasAce = true;
break;
}
}
if (hasAce) {
uniqueRanks[uniqueCount] = 1;
uniqueCount++;
}
// Sort ranks
for (uint8 i = 0; i < uniqueCount - 1; i++) {
for (uint8 j = 0; j < uniqueCount - i - 1; j++) {
if (uniqueRanks[j] > uniqueRanks[j + 1]) {
uint8 temp = uniqueRanks[j];
uniqueRanks[j] = uniqueRanks[j + 1];
uniqueRanks[j + 1] = temp;
}
}
}
// Check for straight
if (uniqueCount >= 5) {
for (uint8 i = 0; i <= uniqueCount - 5; i++) {
if (uniqueRanks[i + 4] == uniqueRanks[i] + 4) {
return (5, 5 * 10 ** 14 + uint256(uniqueRanks[i + 4]));
}
}
}
return (0, 0);
}
function hasThreeOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[2] memory kickers;
for (uint256 i = 0; i <= 4; i++) {
uint8 count = 1;
uint8 rank = cards[i].rank;
for (uint256 j = i + 1; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count == 3) {
// Find two highest kickers
uint8 kickerCount = 0;
// Start from the highest card and work down
for (int256 j = 6; j >= 0 && kickerCount < 2; j--) {
if (cards[uint256(j)].rank != rank) {
kickers[kickerCount] = cards[uint256(j)].rank;
kickerCount++;
}
}
// Make sure we have enough kickers before using them
// Calculate score with safeguards against overflow
uint256 score = 4 * 10 ** 14; // Reduced exponent to prevent overflow
score += uint256(rank) * 10 ** 6;
if (kickerCount >= 2) {
score += uint256(kickers[0]) * 10 ** 4 + uint256(kickers[1]);
} else if (kickerCount == 1) {
score += uint256(kickers[0]) * 10 ** 2;
}
return (4, score);
}
}
return (0, 0);
}
function hasTwoPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8 highPairRank = 0;
uint8 lowPairRank = 0;
// emit PHE_Log(uintToString(cards[6].rank));
// emit PHE_Log(uintToString(cards[5].rank));
// emit PHE_Log(uintToString(cards[4].rank));
// emit PHE_Log(uintToString(cards[3].rank));
// emit PHE_Log(uintToString(cards[2].rank));
// emit PHE_Log(uintToString(cards[1].rank));
// emit PHE_Log(uintToString(cards[0].rank));
// Find pairs
for (uint256 i = 6; i > 0; i--) {
if (cards[i].rank == cards[i - 1].rank) {
if (highPairRank == 0) {
highPairRank = cards[i].rank;
// if (i == 1) {
// break;
// }
// underflow if we subtract 1 from i twice when i == 1
if (i > 1) {
i--; // Skip the second card of the pair
}
} else {
lowPairRank = cards[i].rank;
break;
}
}
}
if (highPairRank > 0 && lowPairRank > 0) {
// Find highest kicker
uint8 kicker = 0;
for (int256 i = 6; i >= 0; i--) {
if (cards[uint256(i)].rank != highPairRank && cards[uint256(i)].rank != lowPairRank)
{
kicker = cards[uint256(i)].rank;
break;
}
}
// Reduced exponent to prevent overflow
uint256 score = 3 * 10 ** 14;
score += uint256(highPairRank) * 10 ** 6;
score += uint256(lowPairRank) * 10 ** 4;
score += uint256(kicker);
return (3, score);
}
return (0, 0);
}
function hasPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[3] memory kickers;
uint8 pairRank = 0;
// Find pair
for (uint256 i = 6; i > 0; i--) {
if (cards[i].rank == cards[i - 1].rank) {
pairRank = cards[i].rank;
break;
}
}
if (pairRank > 0) {
// Find three highest kickers
uint8 kickerCount = 0;
for (int256 i = 6; i >= 0 && kickerCount < 3; i--) {
if (cards[uint256(i)].rank != pairRank) {
kickers[kickerCount] = cards[uint256(i)].rank;
kickerCount++;
}
}
return (
2,
2 * 10 ** 14 + uint256(pairRank) * 10 ** 6 + uint256(kickers[2]) * 10 ** 4
+ uint256(kickers[1]) * 10 ** 2 + uint256(kickers[0])
);
}
return (0, 0);
}
function hasHighCard(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint256 score = 1 * 10 ** 14;
// Cards are already sorted by rank, so we take the 5 highest cards
// Starting from the highest card (index 6) down to the 5th highest (index 2)
score += uint256(cards[6].rank) * 10 ** 6;
score += uint256(cards[5].rank) * 10 ** 4;
score += uint256(cards[4].rank) * 10 ** 3;
score += uint256(cards[3].rank) * 10 ** 2;
score += uint256(cards[2].rank);
return (1, score);
}
// string equality check
function strEq(string memory a, string memory b) public pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}{
"remappings": [
"forge-std/=lib/forge-std/src/"
],
"optimizer": {
"enabled": false,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false,
"libraries": {
"src/BigNumbers/BigNumbers.sol": {
"BigNumbers": "0xF2635f00300F16D9acA57F955091Cc24DD01F7d1"
}
}
}Contract ABI
API[{"inputs":[{"internalType":"address","name":"_cryptoUtils","type":"address"},{"internalType":"uint256","name":"_smallBlind","type":"uint256"},{"internalType":"bool","name":"_isPrivate","type":"bool"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"dealerPosition","type":"uint256"}],"name":"GameStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addressReporting","type":"address"},{"indexed":true,"internalType":"address","name":"playerReported","type":"address"},{"indexed":false,"internalType":"uint256","name":"timeElapsed","type":"uint256"}],"name":"IdlePlayerKicked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"}],"name":"InvalidCardsReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"enum TexasHoldemRoom.GameStage","name":"stage","type":"uint8"}],"name":"NewStage","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":true,"internalType":"uint8","name":"playerIndex","type":"uint8"},{"indexed":true,"internalType":"uint8","name":"seatPosition","type":"uint8"}],"name":"PlayerJoined","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":true,"internalType":"uint8","name":"playerIndex","type":"uint8"},{"indexed":true,"internalType":"uint8","name":"seatPosition","type":"uint8"}],"name":"PlayerLeft","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":true,"internalType":"enum TexasHoldemRoom.Action","name":"action","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PlayerMoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"winners","type":"address[]"},{"indexed":false,"internalType":"uint8[]","name":"winnerPlayerIndexes","type":"uint8[]"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PotWon","type":"event"},{"inputs":[],"name":"EMPTY_SEAT","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLAYERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_PLAYERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STARTING_CHIPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bigBlind","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"countActivePlayers","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"countOfHandsRevealed","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"countPlayersAtRoundStart","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cryptoUtils","outputs":[{"internalType":"contract CryptoUtils","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentPlayerIndex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentStageBet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dealerPosition","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deckHandler","outputs":[{"internalType":"contract DeckHandler","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"requireActive","type":"bool"}],"name":"getNextActivePlayer","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"playerIndex","type":"uint8"}],"name":"getPlayer","outputs":[{"components":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"chips","type":"uint256"},{"internalType":"uint256","name":"currentStageBet","type":"uint256"},{"internalType":"uint256","name":"totalRoundBet","type":"uint256"},{"internalType":"bool","name":"hasFolded","type":"bool"},{"internalType":"bool","name":"isAllIn","type":"bool"},{"internalType":"bool","name":"hasChecked","type":"bool"},{"internalType":"string[2]","name":"cards","type":"string[2]"},{"internalType":"uint8","name":"seatPosition","type":"uint8"},{"internalType":"uint256","name":"handScore","type":"uint256"},{"internalType":"bool","name":"joinedAndWaitingForNextRound","type":"bool"},{"internalType":"bool","name":"leavingAfterRoundEnds","type":"bool"}],"internalType":"struct TexasHoldemRoom.Player","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getPlayerIndexFromAddr","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPlayers","outputs":[{"components":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"chips","type":"uint256"},{"internalType":"uint256","name":"currentStageBet","type":"uint256"},{"internalType":"uint256","name":"totalRoundBet","type":"uint256"},{"internalType":"bool","name":"hasFolded","type":"bool"},{"internalType":"bool","name":"isAllIn","type":"bool"},{"internalType":"bool","name":"hasChecked","type":"bool"},{"internalType":"string[2]","name":"cards","type":"string[2]"},{"internalType":"uint8","name":"seatPosition","type":"uint8"},{"internalType":"uint256","name":"handScore","type":"uint256"},{"internalType":"bool","name":"joinedAndWaitingForNextRound","type":"bool"},{"internalType":"bool","name":"leavingAfterRoundEnds","type":"bool"}],"internalType":"struct TexasHoldemRoom.Player[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"playerIndex","type":"uint8"}],"name":"getPlayersCardIndexes","outputs":[{"internalType":"uint8[2]","name":"playerCardIndexes","type":"uint8[2]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPrivate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"joinGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastActionTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastRaiseIndex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"leaveGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"numPlayers","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"players","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"chips","type":"uint256"},{"internalType":"uint256","name":"currentStageBet","type":"uint256"},{"internalType":"uint256","name":"totalRoundBet","type":"uint256"},{"internalType":"bool","name":"hasFolded","type":"bool"},{"internalType":"bool","name":"isAllIn","type":"bool"},{"internalType":"bool","name":"hasChecked","type":"bool"},{"internalType":"uint8","name":"seatPosition","type":"uint8"},{"internalType":"uint256","name":"handScore","type":"uint256"},{"internalType":"bool","name":"joinedAndWaitingForNextRound","type":"bool"},{"internalType":"bool","name":"leavingAfterRoundEnds","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"progressGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reportIdlePlayer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reportInvalidCards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resetRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"roundNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"seatPositionToPlayerIndex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_deckHandler","type":"address"}],"name":"setDeckHandler","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"playerIndex","type":"uint8"},{"internalType":"uint256","name":"handScore","type":"uint256"}],"name":"setPlayerHandScore","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"smallBlind","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stage","outputs":[{"internalType":"enum TexasHoldemRoom.GameStage","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum TexasHoldemRoom.Action","name":"action","type":"uint8"},{"internalType":"uint256","name":"raiseAmount","type":"uint256"}],"name":"submitAction","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
608060405234801561000f575f5ffd5b506040516168e33803806168e38339818101604052810190610031919061051c565b8260085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550816004819055506002826100859190610599565b6005819055505f60015f6101000a81548160ff0219169083600c8111156100af576100ae6105da565b5b02179055505f5f8190555080606f60016101000a81548160ff0219169083151502179055505f60065f6101000a81548160ff021916908360ff1602179055505f600660016101000a81548160ff021916908360ff1602179055505f606f5f6101000a81548160ff021916908360ff1602179055505f5f90505b600a60ff168160ff1610156103865760ff606e8260ff16600a81106101505761014f610607565b5b602091828204019190066101000a81548160ff021916908360ff1602179055506040518061018001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f81526020015f151581526020015f151581526020015f15158152602001604051806040016040528060405180602001604052805f815250815260200160405180602001604052805f81525081525081526020018260ff1681526020015f81526020015f151581526020015f1515815250600a8260ff16600a811061022c5761022b610607565b5b600a02015f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015f6101000a81548160ff02191690831515021790555060a08201518160040160016101000a81548160ff02191690831515021790555060c08201518160040160026101000a81548160ff02191690831515021790555060e08201518160050190600261030892919061038f565b50610100820151816007015f6101000a81548160ff021916908360ff1602179055506101208201518160080155610140820151816009015f6101000a81548160ff0219169083151502179055506101608201518160090160016101000a81548160ff0219169083151502179055509050508080600101915050610128565b50505050610937565b82600281019282156103ca579160200282015b828111156103c95782518290816103b99190610868565b50916020019190600101906103a2565b5b5090506103d791906103db565b5090565b5b808211156103fa575f81816103f191906103fe565b506001016103dc565b5090565b50805461040a90610698565b5f825580601f1061041b5750610438565b601f0160209004905f5260205f2090810190610437919061043b565b5b50565b5b80821115610452575f815f90555060010161043c565b5090565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6104838261045a565b9050919050565b61049381610479565b811461049d575f5ffd5b50565b5f815190506104ae8161048a565b92915050565b5f819050919050565b6104c6816104b4565b81146104d0575f5ffd5b50565b5f815190506104e1816104bd565b92915050565b5f8115159050919050565b6104fb816104e7565b8114610505575f5ffd5b50565b5f81519050610516816104f2565b92915050565b5f5f5f6060848603121561053357610532610456565b5b5f610540868287016104a0565b9350506020610551868287016104d3565b925050604061056286828701610508565b9150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6105a3826104b4565b91506105ae836104b4565b92508282026105bc816104b4565b915082820484148315176105d3576105d261056c565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806106af57607f821691505b6020821081036106c2576106c161066b565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026107247fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826106e9565b61072e86836106e9565b95508019841693508086168417925050509392505050565b5f819050919050565b5f61076961076461075f846104b4565b610746565b6104b4565b9050919050565b5f819050919050565b6107828361074f565b61079661078e82610770565b8484546106f5565b825550505050565b5f5f905090565b6107ad61079e565b6107b8818484610779565b505050565b5b818110156107db576107d05f826107a5565b6001810190506107be565b5050565b601f821115610820576107f1816106c8565b6107fa846106da565b81016020851015610809578190505b61081d610815856106da565b8301826107bd565b50505b505050565b5f82821c905092915050565b5f6108405f1984600802610825565b1980831691505092915050565b5f6108588383610831565b9150826002028217905092915050565b61087182610634565b67ffffffffffffffff81111561088a5761088961063e565b5b6108948254610698565b61089f8282856107df565b5f60209050601f8311600181146108d0575f84156108be578287015190505b6108c8858261084d565b86555061092f565b601f1984166108de866106c8565b5f5b82811015610905578489015182556001820191506020850194506020810190506108e0565b86831015610922578489015161091e601f891682610831565b8355505b6001600288020188555050505b505050505050565b615f9f806109445f395ff3fe608060405234801561000f575f5ffd5b5060043610610225575f3560e01c806385f07bbe1161012e578063d4f77b1c116100b6578063eed1b11a1161007a578063eed1b11a146105c5578063f2349ff0146105e3578063f71d96cb14610601578063faff660e1461063b578063fb9bdab41461065957610225565b8063d4f77b1c14610545578063d7b496761461054f578063df5a065c14610559578063e255015614610589578063eac697c9146105a757610225565b8063af5b3799116100fd578063af5b3799146104d9578063b1501f9c146104f7578063bd23a5a714610501578063c040e6b81461051d578063c5caa27b1461053b57610225565b806385f07bbe146104615780638b5b9ccc1461047f5780639597684c1461049d57806397b2f556146104bb57610225565b80634e2786fb116101b15780636427039c116101805780636427039c146103df5780636af2ca69146103fd57806370984e971461041b5780637af3696d1461043957806384d452ff1461045757610225565b80634e2786fb146103695780635905d1fd1461038757806359ebf1f31461039157806359f7af70146103af57610225565b8063367df6d5116101f8578063367df6d5146102af57806339d505fc146102cd57806339fddff6146102fd5780634411b3eb1461032d5780634ba2363a1461034b57610225565b806308853693146102295780630f53fc4b14610245578063219406bd1461026157806322876d511461027f575b5f5ffd5b610243600480360381019061023e9190614423565b610677565b005b61025f600480360381019061025a9190614497565b610ce5565b005b610269610d9b565b60405161027691906144e4565b60405180910390f35b61029960048036038101906102949190614557565b610dad565b6040516102a691906144e4565b60405180910390f35b6102b7610e88565b6040516102c491906145dd565b60405180910390f35b6102e760048036038101906102e291906145f6565b610ead565b6040516102f491906146c6565b60405180910390f35b610317600480360381019061031291906145f6565b6110d6565b6040516103249190614937565b60405180910390f35b6103356112ed565b60405161034291906144e4565b60405180910390f35b6103536112f2565b6040516103609190614966565b60405180910390f35b6103716112f8565b60405161037e9190614966565b60405180910390f35b61038f6112fd565b005b610399611674565b6040516103a69190614966565b60405180910390f35b6103c960048036038101906103c4919061497f565b61167a565b6040516103d691906144e4565b60405180910390f35b6103e76116a3565b6040516103f491906144e4565b60405180910390f35b610405611706565b60405161041291906144e4565b60405180910390f35b61042361170b565b6040516104309190614966565b60405180910390f35b610441611711565b60405161044e91906144e4565b60405180910390f35b61045f6117f8565b005b6104696119d6565b60405161047691906144e4565b60405180910390f35b6104876119db565b6040516104949190614b66565b60405180910390f35b6104a5611c8a565b6040516104b29190614ba6565b60405180910390f35b6104c3611caf565b6040516104d091906144e4565b60405180910390f35b6104e1611cc1565b6040516104ee9190614966565b60405180910390f35b6104ff611cc7565b005b61051b60048036038101906105169190614557565b611e03565b005b610525611ed5565b6040516105329190614c32565b60405180910390f35b610543611ee7565b005b61054d611f80565b005b610557612622565b005b610573600480360381019061056e9190614c75565b612840565b60405161058091906144e4565b60405180910390f35b610591612b6e565b60405161059e9190614966565b60405180910390f35b6105af612b74565b6040516105bc9190614966565b60405180910390f35b6105cd612b7a565b6040516105da91906144e4565b60405180910390f35b6105eb612c90565b6040516105f891906144e4565b60405180910390f35b61061b6004803603810190610616919061497f565b612ca3565b6040516106329b9a99989796959493929190614cbe565b60405180910390f35b610643612d6a565b6040516106509190614d67565b60405180910390f35b610661612d7d565b60405161066e91906144e4565b60405180910390f35b6003600c81111561068b5761068a614bbf565b5b60015f9054906101000a900460ff16600c8111156106ac576106ab614bbf565b5b14806106ea57506005600c8111156106c7576106c6614bbf565b5b60015f9054906101000a900460ff16600c8111156106e8576106e7614bbf565b5b145b8061072757506007600c81111561070457610703614bbf565b5b60015f9054906101000a900460ff16600c81111561072557610724614bbf565b5b145b8061076457506009600c81111561074157610740614bbf565b5b60015f9054906101000a900460ff16600c81111561076257610761614bbf565b5b145b6107a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161079a90614dda565b60405180910390fd5b5f3073ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b81526004016107dd9190614df8565b602060405180830381865afa1580156107f8573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061081c9190614e25565b9050600660019054906101000a900460ff1660ff168160ff1614610875576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161086c90614e9a565b60405180910390fd5b600a8160ff16600a811061088c5761088b614eb8565b5b600a02016004015f9054906101000a900460ff16156108e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d790614f2f565b60405180910390fd5b600a8160ff16600a81106108f7576108f6614eb8565b5b600a020160040160019054906101000a900460ff161561094c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161094390614f97565b60405180910390fd5b60048081111561095f5761095e614bbf565b5b83600481111561097257610971614bbf565b5b036109b2576001600a8260ff16600a81106109905761098f614eb8565b5b600a02016004015f6101000a81548160ff021916908315150217905550610c77565b600160048111156109c6576109c5614bbf565b5b8360048111156109d9576109d8614bbf565b5b03610a7e575f600a8260ff16600a81106109f6576109f5614eb8565b5b600a020160020154600354610a0b9190614fe2565b905080600a8360ff16600a8110610a2557610a24614eb8565b5b600a0201600101541015610a6e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a659061505f565b60405180910390fd5b610a788282612d90565b50610c76565b60026004811115610a9257610a91614bbf565b5b836004811115610aa557610aa4614bbf565b5b03610bae576003548211610aee576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ae5906150ed565b60405180910390fd5b5f600a8260ff16600a8110610b0657610b05614eb8565b5b600a02016002015483610b199190614fe2565b905080600a8360ff16600a8110610b3357610b32614eb8565b5b600a0201600101541015610b7c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b739061505f565b60405180910390fd5b610b868282612d90565b8260038190555081600660026101000a81548160ff021916908360ff16021790555050610c75565b60036004811115610bc257610bc1614bbf565b5b836004811115610bd557610bd4614bbf565b5b03610c7457600354600a8260ff16600a8110610bf457610bf3614eb8565b5b600a02016002015414610c3c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c3390615155565b60405180910390fd5b6001600a8260ff16600a8110610c5557610c54614eb8565b5b600a020160040160026101000a81548160ff0219169083151502179055505b5b5b5b826004811115610c8a57610c89614bbf565b5b3373ffffffffffffffffffffffffffffffffffffffff167fa2b0aae25f339f90f1bd8b3489bdfb93b146ef6808f04c4e39235ca332007b2384604051610cd09190614966565b60405180910390a3610ce0612f06565b505050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610d74576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6b906151bd565b60405180910390fd5b80600a8360ff16600a8110610d8c57610d8b614eb8565b5b600a0201600801819055505050565b60065f9054906101000a900460ff1681565b5f5f5f90505b600a60ff168160ff161015610e47578273ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a8110610df057610def614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610e3a5780915050610e83565b8080600101915050610db3565b506040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e7a9061524b565b60405180910390fd5b919050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b610eb561423a565b5f5f90505f600a8460ff16600a8110610ed157610ed0614eb8565b5b600a02016007015f9054906101000a900460ff1690505f600a60065f9054906101000a900460ff1660ff16600a8110610f0d57610f0c614eb8565b5b600a02016007015f9054906101000a900460ff1690505b8060ff168260ff161461100757600a6001600a610f419190615269565b83610f4c919061529d565b610f5691906152fe565b915060ff8016606e8360ff16600a8110610f7357610f72614eb8565b5b602091828204019190069054906101000a900460ff1660ff1614158015610fee5750600a606e8360ff16600a8110610fae57610fad614eb8565b5b602091828204019190069054906101000a900460ff1660ff16600a8110610fd857610fd7614eb8565b5b600a02016009015f9054906101000a900460ff16155b15611002578280610ffe9061532e565b9350505b610f24565b5f3073ffffffffffffffffffffffffffffffffffffffff16637af3696d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611051573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110759190614e25565b905083855f6002811061108b5761108a614eb8565b5b602002019060ff16908160ff168152505080846110a8919061529d565b856001600281106110bc576110bb614eb8565b5b602002019060ff16908160ff168152505050505050919050565b6110de61425c565b600a8260ff16600a81106110f5576110f4614eb8565b5b600a0201604051806101800160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015481526020016002820154815260200160038201548152602001600482015f9054906101000a900460ff161515151581526020016004820160019054906101000a900460ff161515151581526020016004820160029054906101000a900460ff1615151515815260200160058201600280602002604051908101604052809291905f905b828210156112835783820180546111f890615383565b80601f016020809104026020016040519081016040528092919081815260200182805461122490615383565b801561126f5780601f106112465761010080835404028352916020019161126f565b820191905f5260205f20905b81548152906001019060200180831161125257829003601f168201915b5050505050815260200190600101906111e2565b505050508152602001600782015f9054906101000a900460ff1660ff1660ff16815260200160088201548152602001600982015f9054906101000a900460ff161515151581526020016009820160019054906101000a900460ff1615151515815250509050919050565b600a81565b60025481565b5f5481565b5f6007544261130c9190614fe2565b9050601e8111611351576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611348906153fd565b60405180910390fd5b5f60ff9050600a600c81111561136a57611369614bbf565b5b60015f9054906101000a900460ff16600c81111561138b5761138a614bbf565b5b03611509575f5f90505b600a60ff168160ff1610156114bb575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a81106113d2576113d1614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141580156114445750600a8160ff16600a811061142e5761142d614eb8565b5b600a02016009015f9054906101000a900460ff16155b80156114785750600a8160ff16600a811061146257611461614eb8565b5b600a02016004015f9054906101000a900460ff16155b80156114a157505f600a8260ff16600a811061149757611496614eb8565b5b600a020160080154145b156114ae578091506114bb565b8080600101915050611395565b5060ff80168160ff1603611504576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fb9061548b565b60405180910390fd5b61151c565b600660019054906101000a900460ff1690505b600a8160ff16600a811061153357611532614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f4f5c3d12cd160dbd97779d8d82003e4edc995550312b60d71dfdf4fe1932182a846040516115b59190614966565b60405180910390a36001600a8260ff16600a81106115d6576115d5614eb8565b5b600a020160090160016101000a81548160ff0219169083151502179055505f5f90505b600a60ff168160ff16101561166757600a8160ff16600a811061161f5761161e614eb8565b5b600a020160030154600a8260ff16600a811061163e5761163d614eb8565b5b600a02016001015f82825461165391906154a9565b9250508190555080806001019150506115f9565b50611670613435565b5050565b6103e881565b606e81600a8110611689575f80fd5b60209182820401919006915054906101000a900460ff1681565b5f5f5f90505f5f90505b600a60ff168160ff1610156116fe575f600a8260ff16600a81106116d4576116d3614eb8565b5b600a02016008015411156116f15781806116ed9061532e565b9250505b80806001019150506116ad565b508091505090565b60ff81565b60055481565b5f5f5f90505f5f90505b600a60ff168160ff1610156117f0575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061175857611757614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141580156117cf57505f1515600a8260ff16600a81106117b7576117b6614eb8565b5b600a02016009015f9054906101000a900460ff161515145b156117e35781806117df9061532e565b9250505b808060010191505061171b565b508091505090565b732a99ec82d658f7a77ddebfd83d0f8f591769cb6473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480611885575073101a25d0fdc4e9aca9fa65584a28781046f1beee73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b806118cf5750737d20fd2bd3d13b03571a36568cfcc2a4eb3c749e73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b806119195750733797a1f60c46d2d6f02c3568366712d8a8a69a7373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b611958576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161194f90615526565b60405180910390fd5b5f5f90505b600a60ff168160ff1610156119cb57600a8160ff16600a811061198357611982614eb8565b5b600a020160030154600a8260ff16600a81106119a2576119a1614eb8565b5b600a02016001015f8282546119b791906154a9565b92505081905550808060010191505061195d565b506119d4613435565b565b600281565b60605f600a60ff1667ffffffffffffffff8111156119fc576119fb615544565b5b604051908082528060200260200182016040528015611a3557816020015b611a2261425c565b815260200190600190039081611a1a5790505b5090505f5f90505b600a60ff168160ff161015611c8257600a8160ff16600a8110611a6357611a62614eb8565b5b600a0201604051806101800160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015481526020016002820154815260200160038201548152602001600482015f9054906101000a900460ff161515151581526020016004820160019054906101000a900460ff161515151581526020016004820160029054906101000a900460ff1615151515815260200160058201600280602002604051908101604052809291905f905b82821015611bf1578382018054611b6690615383565b80601f0160208091040260200160405190810160405280929190818152602001828054611b9290615383565b8015611bdd5780601f10611bb457610100808354040283529160200191611bdd565b820191905f5260205f20905b815481529060010190602001808311611bc057829003601f168201915b505050505081526020019060010190611b50565b505050508152602001600782015f9054906101000a900460ff1660ff1660ff16815260200160088201548152602001600982015f9054906101000a900460ff161515151581526020016009820160019054906101000a900460ff161515151581525050828260ff1681518110611c6a57611c69614eb8565b5b60200260200101819052508080600101915050611a3d565b508091505090565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606f5f9054906101000a900460ff1681565b60035481565b5f3073ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611d019190614df8565b602060405180830381865afa158015611d1c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d409190614e25565b905060ff80168160ff1603611d8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d81906155bb565b60405180910390fd5b600a8160ff16600a8110611da157611da0614eb8565b5b600a02016009015f9054906101000a900460ff1615611dc857611dc3816139d1565b611e00565b6001600a8260ff16600a8110611de157611de0614eb8565b5b600a020160090160016101000a81548160ff0219169083151502179055505b50565b5f73ffffffffffffffffffffffffffffffffffffffff1660095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611e92576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e8990615623565b60405180910390fd5b8060095f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900460ff1681565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f76576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f6d906151bd565b60405180910390fd5b611f7e612f06565b565b600a60ff16606f5f9054906101000a900460ff1660ff1610611fd7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611fce9061568b565b60405180910390fd5b5f5f90505b600a60ff168160ff1610156120a2573373ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061201957612018614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603612095576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161208c906156f3565b60405180910390fd5b8080600101915050611fdc565b505f5f90505b60ff8016606e8260ff16600a81106120c3576120c2614eb8565b5b602091828204019190069054906101000a900460ff1660ff16141580156120f05750600a60ff168160ff16105b156121085780806121009061532e565b9150506120a8565b600a60ff168160ff1610612151576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121489061575b565b60405180910390fd5b5f5f90505b5f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061218457612183614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141580156121d45750600a60ff168160ff16105b156121ec5780806121e49061532e565b915050612156565b600a60ff168160ff1610612235576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161222c906157c3565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061226357612262614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146122df576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122d69061582b565b60405180910390fd5b5f5f600c8111156122f3576122f2614bbf565b5b60015f9054906101000a900460ff16600c81111561231457612313614bbf565b5b101590506040518061018001604052803373ffffffffffffffffffffffffffffffffffffffff1681526020016103e881526020015f81526020015f81526020015f151581526020015f151581526020015f15158152602001604051806040016040528060405180602001604052805f815250815260200160405180602001604052805f81525081525081526020018460ff1681526020015f815260200182151581526020015f1515815250600a8360ff16600a81106123d6576123d5614eb8565b5b600a02015f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015f6101000a81548160ff02191690831515021790555060a08201518160040160016101000a81548160ff02191690831515021790555060c08201518160040160026101000a81548160ff02191690831515021790555060e0820151816005019060026124b29291906142db565b50610100820151816007015f6101000a81548160ff021916908360ff1602179055506101208201518160080155610140820151816009015f6101000a81548160ff0219169083151502179055506101608201518160090160016101000a81548160ff02191690831515021790555090505081606e8460ff16600a811061253b5761253a614eb8565b5b602091828204019190066101000a81548160ff021916908360ff160217905550606f5f81819054906101000a900460ff16809291906125799061532e565b91906101000a81548160ff021916908360ff160217905550508260ff168260ff163373ffffffffffffffffffffffffffffffffffffffff167f48fd1b53f938fc69bea369036925e24c1dc8d00c49430e46d1ccdd5c66b6290660405160405180910390a4600260ff16606f5f9054906101000a900460ff1660ff161015801561260f5750606f60019054906101000a900460ff16155b1561261d5761261c612f06565b5b505050565b6003600c81111561263657612635614bbf565b5b60015f9054906101000a900460ff16600c81111561265757612656614bbf565b5b1015612698576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161268f906158b9565b60405180910390fd5b5f3073ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b81526004016126d29190614df8565b602060405180830381865afa1580156126ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127119190614e25565b9050600a8160ff16600a811061272a57612729614eb8565b5b600a02016009015f9054906101000a900460ff161561277e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161277590615921565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167faca59e978a7cd7d8951a0c811dae34e013d0994836c236297513c3fb442bdef160405160405180910390a25f5f90505b600a60ff168160ff16101561283457600a8160ff16600a81106127ec576127eb614eb8565b5b600a020160030154600a8260ff16600a811061280b5761280a614eb8565b5b600a02016001015f82825461282091906154a9565b9250508190555080806001019150506127c6565b5061283d613435565b50565b5f5f600a600660019054906101000a900460ff1660ff16600a811061286857612867614eb8565b5b600a02016007015f9054906101000a900460ff1690505f600a60018361288e919061529d565b61289891906152fe565b90505b8160ff168160ff1614612b54575f606e8260ff16600a81106128c0576128bf614eb8565b5b602091828204019190069054906101000a900460ff16905060ff80168160ff1614612b33575f600a8260ff16600a81106128fd576128fc614eb8565b5b600a0201604051806101800160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015481526020016002820154815260200160038201548152602001600482015f9054906101000a900460ff161515151581526020016004820160019054906101000a900460ff161515151581526020016004820160029054906101000a900460ff1615151515815260200160058201600280602002604051908101604052809291905f905b82821015612a8b578382018054612a0090615383565b80601f0160208091040260200160405190810160405280929190818152602001828054612a2c90615383565b8015612a775780601f10612a4e57610100808354040283529160200191612a77565b820191905f5260205f20905b815481529060010190602001808311612a5a57829003601f168201915b5050505050815260200190600101906129ea565b505050508152602001600782015f9054906101000a900460ff1660ff1660ff16815260200160088201548152602001600982015f9054906101000a900460ff161515151581526020016009820160019054906101000a900460ff1615151515815250509050806101400151612b315785612b0b5781945050505050612b69565b8060800151158015612b1f57508060a00151155b15612b305781945050505050612b69565b5b505b600a600183612b42919061529d565b612b4c91906152fe565b91505061289b565b600660019054906101000a900460ff16925050505b919050565b60045481565b60075481565b5f5f5f90505f5f90505b600a60ff168160ff161015612c88575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a8110612bc157612bc0614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015612c335750600a8160ff16600a8110612c1d57612c1c614eb8565b5b600a02016004015f9054906101000a900460ff16155b8015612c675750600a8160ff16600a8110612c5157612c50614eb8565b5b600a02016009015f9054906101000a900460ff16155b15612c7b578180612c779061532e565b9250505b8080600101915050612b84565b508091505090565b600660029054906101000a900460ff1681565b600a81600a8110612cb2575f80fd5b600a02015f91509050805f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015490806002015490806003015490806004015f9054906101000a900460ff16908060040160019054906101000a900460ff16908060040160029054906101000a900460ff1690806007015f9054906101000a900460ff1690806008015490806009015f9054906101000a900460ff16908060090160019054906101000a900460ff1690508b565b606f60019054906101000a900460ff1681565b600660019054906101000a900460ff1681565b80600a8360ff16600a8110612da857612da7614eb8565b5b600a0201600101541015612df1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612de89061505f565b60405180910390fd5b80600a8360ff16600a8110612e0957612e08614eb8565b5b600a02016001015f828254612e1e9190614fe2565b9250508190555080600a8360ff16600a8110612e3d57612e3c614eb8565b5b600a02016002015f828254612e5291906154a9565b9250508190555080600a8360ff16600a8110612e7157612e70614eb8565b5b600a02016003015f828254612e8691906154a9565b925050819055508060025f828254612e9e91906154a9565b925050819055505f600a8360ff16600a8110612ebd57612ebc614eb8565b5b600a02016001015403612f02576001600a8360ff16600a8110612ee357612ee2614eb8565b5b600a020160040160016101000a81548160ff0219169083151502179055505b5050565b600a600c811115612f1a57612f19614bbf565b5b60015f9054906101000a900460ff16600c811115612f3b57612f3a614bbf565b5b148015612f5b5750612f4b612b7a565b60ff16612f566116a3565b60ff16105b612f6757426007819055505b5f600c811115612f7a57612f79614bbf565b5b60015f9054906101000a900460ff16600c811115612f9b57612f9a614bbf565b5b03613035575f5f90505b600a60ff168160ff16101561302757600a8160ff16600a8110612fcb57612fca614eb8565b5b600a02016009015f9054906101000a900460ff161561301a575f600a8260ff16600a8110612ffc57612ffb614eb8565b5b600a02016009015f6101000a81548160ff0219169083151502179055505b8080600101915050612fa5565b50613030613c08565b613433565b6001600c81111561304957613048614bbf565b5b60015f9054906101000a900460ff16600c81111561306a57613069614bbf565b5b14806130a857506002600c81111561308557613084614bbf565b5b60015f9054906101000a900460ff16600c8111156130a6576130a5614bbf565b5b145b806130e557506004600c8111156130c2576130c1614bbf565b5b60015f9054906101000a900460ff16600c8111156130e3576130e2614bbf565b5b145b8061312257506006600c8111156130ff576130fe614bbf565b5b60015f9054906101000a900460ff16600c8111156131205761311f614bbf565b5b145b8061315f57506008600c81111561313c5761313b614bbf565b5b60015f9054906101000a900460ff16600c81111561315d5761315c614bbf565b5b145b1561329b575f5f90505f61317282612840565b905060065f9054906101000a900460ff1660ff168160ff1603613279576001600c8111156131a3576131a2614bbf565b5b60015f9054906101000a900460ff16600c8111156131c4576131c3614bbf565b5b036131f75760065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff16021790555061326a565b5f6001905060065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff1602179055505f61322f82612840565b905080600660016101000a81548160ff021916908360ff16021790555080600660026101000a81548160ff021916908360ff16021790555050505b613272613c08565b5050613433565b80600660016101000a81548160ff021916908360ff1602179055505050613432565b600a600c8111156132af576132ae614bbf565b5b60015f9054906101000a900460ff16600c8111156132d0576132cf614bbf565b5b03613306576132dd612b7a565b60ff166132e86116a3565b60ff1603613301576132f8613cb8565b613300613435565b5b613433565b6001613310612b7a565b60ff160361332d57613320613cb8565b613328613435565b613433565b5f600190505f61333c82612840565b9050600660029054906101000a900460ff1660ff168160ff1603613414575f6003819055505f5f90505b600a60ff168160ff1610156133db575f600a8260ff16600a811061338d5761338c614eb8565b5b600a0201600201819055505f600a8260ff16600a81106133b0576133af614eb8565b5b600a020160040160026101000a81548160ff0219169083151502179055508080600101915050613366565b5060065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff16021790555061340d613c08565b5050613433565b80600660016101000a81548160ff021916908360ff16021790555050505b5b565b5f5f8154809291906134469061593f565b91905055505f60015f6101000a81548160ff0219169083600c81111561346f5761346e614bbf565b5b02179055505f6002819055505f6003819055505f600a60065f9054906101000a900460ff1660ff16600a81106134a8576134a7614eb8565b5b600a02016007015f9054906101000a900460ff1690505f5f90505b600a60ff168160ff16101561351a57600a8160ff16600a81106134e9576134e8614eb8565b5b600a020160090160019054906101000a900460ff161561350d5761350c816139d1565b5b80806001019150506134c3565b505f5f90505b600a60ff168160ff16101561370a57600a8160ff16600a811061354657613545614eb8565b5b600a02016009015f9054906101000a900460ff1615613595575f600a8260ff16600a811061357757613576614eb8565b5b600a02016009015f6101000a81548160ff0219169083151502179055505b5f600a8260ff16600a81106135ad576135ac614eb8565b5b600a0201600201819055505f600a8260ff16600a81106135d0576135cf614eb8565b5b600a0201600301819055505f600a8260ff16600a81106135f3576135f2614eb8565b5b600a02016004015f6101000a81548160ff0219169083151502179055505f600a8260ff16600a811061362857613627614eb8565b5b600a020160040160026101000a81548160ff0219169083151502179055505f600a8260ff16600a811061365e5761365d614eb8565b5b600a020160040160016101000a81548160ff021916908315150217905550604051806040016040528060405180602001604052805f815250815260200160405180602001604052805f815250815250600a8260ff16600a81106136c4576136c3614eb8565b5b600a02016005019060026136d99291906142db565b505f600a8260ff16600a81106136f2576136f1614eb8565b5b600a0201600801819055508080600101915050613520565b5060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16637243087a6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015613771575f5ffd5b505af1158015613783573d5f5f3e3d5ffd5b50505050600260ff16606f5f9054906101000a900460ff1660ff1610156137e6575f6007819055505f60065f6101000a81548160ff021916908360ff1602179055505f600660016101000a81548160ff021916908360ff160217905550506139cf565b5f600a6001836137f6919061529d565b61380091906152fe565b90505b60ff8016606e8260ff16600a811061381e5761381d614eb8565b5b602091828204019190069054906101000a900460ff1660ff1614801561384a57508160ff168160ff1614155b1561386f57600a60018261385e919061529d565b61386891906152fe565b9050613803565b60ff8016606e8260ff16600a811061388a57613889614eb8565b5b602091828204019190069054906101000a900460ff1660ff16036138e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016138da906159f6565b60405180910390fd5b8160ff168160ff160361392b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161392290615a84565b60405180910390fd5b606e8160ff16600a811061394257613941614eb8565b5b602091828204019190069054906101000a900460ff1660065f6101000a81548160ff021916908360ff16021790555060065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff160217905550600660019054906101000a900460ff16600660026101000a81548160ff021916908360ff1602179055506139cc612f06565b50505b565b5f600a8260ff16600a81106139e9576139e8614eb8565b5b600a02016009015f6101000a81548160ff0219169083151502179055505f600a8260ff16600a8110613a1e57613a1d614eb8565b5b600a020160090160016101000a81548160ff0219169083151502179055505f600a8260ff16600a8110613a5457613a53614eb8565b5b600a02016008018190555060ff606e600a8360ff16600a8110613a7a57613a79614eb8565b5b600a02016007015f9054906101000a900460ff1660ff16600a8110613aa257613aa1614eb8565b5b602091828204019190066101000a81548160ff021916908360ff160217905550606f5f81819054906101000a900460ff1680929190613ae090615aa2565b91906101000a81548160ff021916908360ff16021790555050600a8160ff16600a8110613b1057613b0f614eb8565b5b600a02016007015f9054906101000a900460ff1660ff168160ff16600a8360ff16600a8110613b4257613b41614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f28245a946274b1107d714f5e9866b8aec561e4bd74aae266a4709caa59dce12660405160405180910390a45f600a8260ff16600a8110613bc257613bc1614eb8565b5b600a02015f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6001805f9054906101000a900460ff16600c811115613c2a57613c29614bbf565b5b613c34919061529d565b60ff16600c811115613c4957613c48614bbf565b5b60015f6101000a81548160ff0219169083600c811115613c6c57613c6b614bbf565b5b02179055507f2813df2508ecb23fa82b5b4208c20d282c618a88460be290fc9954fc3f750d4460015f9054906101000a900460ff16604051613cae9190614c32565b60405180910390a1565b600a600c811115613ccc57613ccb614bbf565b5b60015f9054906101000a900460ff16600c811115613ced57613cec614bbf565b5b1480613d0357506001613cfe612b7a565b60ff16145b613d42576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401613d3990615b39565b60405180910390fd5b5f5f90505f5f613d506116a3565b60ff1611613d5f576001613d68565b613d676116a3565b5b90505f8160ff1667ffffffffffffffff811115613d8857613d87615544565b5b604051908082528060200260200182016040528015613db65781602001602082028036833780820191505090505b5090505f5f90506001613dc7612b7a565b60ff1603613f05575f5f5f90505b600a60ff168160ff161015613ed2575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a8110613e1257613e11614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015613e845750600a8160ff16600a8110613e6e57613e6d614eb8565b5b600a02016009015f9054906101000a900460ff16155b8015613eb85750600a8160ff16600a8110613ea257613ea1614eb8565b5b600a02016004015f9054906101000a900460ff16155b15613ec557809150613ed2565b8080600101915050613dd5565b5080835f81518110613ee757613ee6614eb8565b5b602002602001019060ff16908160ff16815250506001915050613fd7565b5f5f90505b600a60ff168160ff161015613fd5575f600a8260ff16600a8110613f3157613f30614eb8565b5b600a02016008015490505f8103613f485750613fc8565b85811115613f845780955081845f81518110613f6757613f66614eb8565b5b602002602001019060ff16908160ff168152505060019250613fc6565b858103613fc55781848460ff1681518110613fa257613fa1614eb8565b5b602002602001019060ff16908160ff16815250508280613fc19061532e565b9350505b5b505b8080600101915050613f0a565b505b5f8160ff16600254613fe99190615b57565b90505f8260ff1667ffffffffffffffff81111561400957614008615544565b5b6040519080825280602002602001820160405280156140375781602001602082028036833780820191505090505b5090505f5f90505b8360ff168160ff1610156140db575f858260ff168151811061406457614063614eb8565b5b6020026020010151905080838360ff168151811061408557614084614eb8565b5b602002602001019060ff16908160ff168152505083600a8260ff16600a81106140b1576140b0614eb8565b5b600a02016001015f8282546140c691906154a9565b9250508190555050808060010191505061403f565b505f8360ff1667ffffffffffffffff8111156140fa576140f9615544565b5b6040519080825280602002602001820160405280156141285781602001602082028036833780820191505090505b5090505f5f90505b8460ff168160ff1610156141f557600a838260ff168151811061415657614155614eb8565b5b602002602001015160ff16600a811061417257614171614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16828260ff16815181106141ae576141ad614eb8565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508080600101915050614130565b507fa050791fd343ccccb174329b5bb9dbc0760e236055988ffec7d849991a160f9c81838560405161422993929190615cc0565b60405180910390a150505050505050565b6040518060400160405280600290602082028036833780820191505090505090565b6040518061018001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f81526020015f151581526020015f151581526020015f151581526020016142b6614327565b81526020015f60ff1681526020015f81526020015f151581526020015f151581525090565b8260028101928215614316579160200282015b828111156143155782518290816143059190615e9a565b50916020019190600101906142ee565b5b509050614323919061434e565b5090565b60405180604001604052806002905b60608152602001906001900390816143365790505090565b5b8082111561436d575f81816143649190614371565b5060010161434f565b5090565b50805461437d90615383565b5f825580601f1061438e57506143ab565b601f0160209004905f5260205f20908101906143aa91906143ae565b5b50565b5b808211156143c5575f815f9055506001016143af565b5090565b5f5ffd5b600581106143d9575f5ffd5b50565b5f813590506143ea816143cd565b92915050565b5f819050919050565b614402816143f0565b811461440c575f5ffd5b50565b5f8135905061441d816143f9565b92915050565b5f5f60408385031215614439576144386143c9565b5b5f614446858286016143dc565b92505060206144578582860161440f565b9150509250929050565b5f60ff82169050919050565b61447681614461565b8114614480575f5ffd5b50565b5f813590506144918161446d565b92915050565b5f5f604083850312156144ad576144ac6143c9565b5b5f6144ba85828601614483565b92505060206144cb8582860161440f565b9150509250929050565b6144de81614461565b82525050565b5f6020820190506144f75f8301846144d5565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f614526826144fd565b9050919050565b6145368161451c565b8114614540575f5ffd5b50565b5f813590506145518161452d565b92915050565b5f6020828403121561456c5761456b6143c9565b5b5f61457984828501614543565b91505092915050565b5f819050919050565b5f6145a56145a061459b846144fd565b614582565b6144fd565b9050919050565b5f6145b68261458b565b9050919050565b5f6145c7826145ac565b9050919050565b6145d7816145bd565b82525050565b5f6020820190506145f05f8301846145ce565b92915050565b5f6020828403121561460b5761460a6143c9565b5b5f61461884828501614483565b91505092915050565b5f60029050919050565b5f81905092915050565b5f819050919050565b61464781614461565b82525050565b5f614658838361463e565b60208301905092915050565b5f602082019050919050565b61467981614621565b614683818461462b565b925061468e82614635565b805f5b838110156146be5781516146a5878261464d565b96506146b083614664565b925050600181019050614691565b505050505050565b5f6040820190506146d95f830184614670565b92915050565b6146e88161451c565b82525050565b6146f7816143f0565b82525050565b5f8115159050919050565b614711816146fd565b82525050565b5f60029050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61477682614734565b614780818561473e565b935061479081856020860161474e565b6147998161475c565b840191505092915050565b5f6147af838361476c565b905092915050565b5f602082019050919050565b5f6147cd82614717565b6147d78185614721565b9350836020820285016147e98561472b565b805f5b85811015614824578484038952815161480585826147a4565b9450614810836147b7565b925060208a019950506001810190506147ec565b50829750879550505050505092915050565b5f61018083015f83015161484c5f8601826146df565b50602083015161485f60208601826146ee565b50604083015161487260408601826146ee565b50606083015161488560608601826146ee565b5060808301516148986080860182614708565b5060a08301516148ab60a0860182614708565b5060c08301516148be60c0860182614708565b5060e083015184820360e08601526148d682826147c3565b9150506101008301516148ed61010086018261463e565b506101208301516149026101208601826146ee565b50610140830151614917610140860182614708565b5061016083015161492c610160860182614708565b508091505092915050565b5f6020820190508181035f83015261494f8184614836565b905092915050565b614960816143f0565b82525050565b5f6020820190506149795f830184614957565b92915050565b5f60208284031215614994576149936143c9565b5b5f6149a18482850161440f565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f61018083015f8301516149e95f8601826146df565b5060208301516149fc60208601826146ee565b506040830151614a0f60408601826146ee565b506060830151614a2260608601826146ee565b506080830151614a356080860182614708565b5060a0830151614a4860a0860182614708565b5060c0830151614a5b60c0860182614708565b5060e083015184820360e0860152614a7382826147c3565b915050610100830151614a8a61010086018261463e565b50610120830151614a9f6101208601826146ee565b50610140830151614ab4610140860182614708565b50610160830151614ac9610160860182614708565b508091505092915050565b5f614adf83836149d3565b905092915050565b5f602082019050919050565b5f614afd826149aa565b614b0781856149b4565b935083602082028501614b19856149c4565b805f5b85811015614b545784840389528151614b358582614ad4565b9450614b4083614ae7565b925060208a01995050600181019050614b1c565b50829750879550505050505092915050565b5f6020820190508181035f830152614b7e8184614af3565b905092915050565b5f614b90826145ac565b9050919050565b614ba081614b86565b82525050565b5f602082019050614bb95f830184614b97565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d8110614bfd57614bfc614bbf565b5b50565b5f819050614c0d82614bec565b919050565b5f614c1c82614c00565b9050919050565b614c2c81614c12565b82525050565b5f602082019050614c455f830184614c23565b92915050565b614c54816146fd565b8114614c5e575f5ffd5b50565b5f81359050614c6f81614c4b565b92915050565b5f60208284031215614c8a57614c896143c9565b5b5f614c9784828501614c61565b91505092915050565b614ca98161451c565b82525050565b614cb8816146fd565b82525050565b5f61016082019050614cd25f83018e614ca0565b614cdf602083018d614957565b614cec604083018c614957565b614cf9606083018b614957565b614d06608083018a614caf565b614d1360a0830189614caf565b614d2060c0830188614caf565b614d2d60e08301876144d5565b614d3b610100830186614957565b614d49610120830185614caf565b614d57610140830184614caf565b9c9b505050505050505050505050565b5f602082019050614d7a5f830184614caf565b92915050565b5f82825260208201905092915050565b7f47616d65206e6f7420696e20612062657474696e6720737461676500000000005f82015250565b5f614dc4601b83614d80565b9150614dcf82614d90565b602082019050919050565b5f6020820190508181035f830152614df181614db8565b9050919050565b5f602082019050614e0b5f830184614ca0565b92915050565b5f81519050614e1f8161446d565b92915050565b5f60208284031215614e3a57614e396143c9565b5b5f614e4784828501614e11565b91505092915050565b7f4e6f7420796f7572207475726e000000000000000000000000000000000000005f82015250565b5f614e84600d83614d80565b9150614e8f82614e50565b602082019050919050565b5f6020820190508181035f830152614eb181614e78565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f506c617965722068617320666f6c6465640000000000000000000000000000005f82015250565b5f614f19601183614d80565b9150614f2482614ee5565b602082019050919050565b5f6020820190508181035f830152614f4681614f0d565b9050919050565b7f506c6179657220697320616c6c2d696e000000000000000000000000000000005f82015250565b5f614f81601083614d80565b9150614f8c82614f4d565b602082019050919050565b5f6020820190508181035f830152614fae81614f75565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f614fec826143f0565b9150614ff7836143f0565b925082820390508181111561500f5761500e614fb5565b5b92915050565b7f4e6f7420656e6f756768206368697073000000000000000000000000000000005f82015250565b5f615049601083614d80565b915061505482615015565b602082019050919050565b5f6020820190508181035f8301526150768161503d565b9050919050565b7f5261697365206d75737420626520686967686572207468616e2063757272656e5f8201527f7420626574000000000000000000000000000000000000000000000000000000602082015250565b5f6150d7602583614d80565b91506150e28261507d565b604082019050919050565b5f6020820190508181035f830152615104816150cb565b9050919050565b7f4d7573742063616c6c206f7220726169736500000000000000000000000000005f82015250565b5f61513f601283614d80565b915061514a8261510b565b602082019050919050565b5f6020820190508181035f83015261516c81615133565b9050919050565b7f4f6e6c79204465636b48616e646c65722063616e2063616c6c207468697300005f82015250565b5f6151a7601e83614d80565b91506151b282615173565b602082019050919050565b5f6020820190508181035f8301526151d48161519b565b9050919050565b7f506c61796572206e6f7420666f756e6420666f7220676976656e2061646472655f8201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b5f615235602283614d80565b9150615240826151db565b604082019050919050565b5f6020820190508181035f83015261526281615229565b9050919050565b5f61527382614461565b915061527e83614461565b9250828203905060ff81111561529757615296614fb5565b5b92915050565b5f6152a782614461565b91506152b283614461565b9250828201905060ff8111156152cb576152ca614fb5565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f61530882614461565b915061531383614461565b925082615323576153226152d1565b5b828206905092915050565b5f61533882614461565b915060ff820361534b5761534a614fb5565b5b600182019050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061539a57607f821691505b6020821081036153ad576153ac615356565b5b50919050565b7f506c6179657220686173203330207365636f6e647320746f20616374000000005f82015250565b5f6153e7601c83614d80565b91506153f2826153b3565b602082019050919050565b5f6020820190508181035f830152615414816153db565b9050919050565b7f416c6c20706c617965727320686176652072657665616c6564207468656972205f8201527f6361726473000000000000000000000000000000000000000000000000000000602082015250565b5f615475602583614d80565b91506154808261541b565b604082019050919050565b5f6020820190508181035f8301526154a281615469565b9050919050565b5f6154b3826143f0565b91506154be836143f0565b92508282019050808211156154d6576154d5614fb5565b5b92915050565b7f4f6e6c79204a6f686e732063616e2063616c6c207468697300000000000000005f82015250565b5f615510601883614d80565b915061551b826154dc565b602082019050919050565b5f6020820190508181035f83015261553d81615504565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f506c61796572206e6f7420696e2067616d6500000000000000000000000000005f82015250565b5f6155a5601283614d80565b91506155b082615571565b602082019050919050565b5f6020820190508181035f8301526155d281615599565b9050919050565b7f4465636b48616e646c657220616c7265616479207365740000000000000000005f82015250565b5f61560d601783614d80565b9150615618826155d9565b602082019050919050565b5f6020820190508181035f83015261563a81615601565b9050919050565b7f526f6f6d2069732066756c6c00000000000000000000000000000000000000005f82015250565b5f615675600c83614d80565b915061568082615641565b602082019050919050565b5f6020820190508181035f8301526156a281615669565b9050919050565b7f416c726561647920696e2067616d6500000000000000000000000000000000005f82015250565b5f6156dd600f83614d80565b91506156e8826156a9565b602082019050919050565b5f6020820190508181035f83015261570a816156d1565b9050919050565b7f4e6f20656d7074792073656174730000000000000000000000000000000000005f82015250565b5f615745600e83614d80565b915061575082615711565b602082019050919050565b5f6020820190508181035f83015261577281615739565b9050919050565b7f4e6f20656d70747920706c6179657273000000000000000000000000000000005f82015250565b5f6157ad601083614d80565b91506157b882615779565b602082019050919050565b5f6020820190508181035f8301526157da816157a1565b9050919050565b7f4e756c6c20706c6179657220696e646578206e6f7420666f756e6400000000005f82015250565b5f615815601b83614d80565b9150615820826157e1565b602082019050919050565b5f6020820190508181035f83015261584281615809565b9050919050565b7f43616e6e6f74207265706f727420696e76616c6964206361726473206265666f5f8201527f726520707265666c6f7000000000000000000000000000000000000000000000602082015250565b5f6158a3602a83614d80565b91506158ae82615849565b604082019050919050565b5f6020820190508181035f8301526158d081615897565b9050919050565b7f506c61796572206973206a6f696e696e67206e65787420726f756e64000000005f82015250565b5f61590b601c83614d80565b9150615916826158d7565b602082019050919050565b5f6020820190508181035f830152615938816158ff565b9050919050565b5f615949826143f0565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361597b5761597a614fb5565b5b600182019050919050565b7f4e657874206465616c6572206d757374206e6f7420626520616e20656d7074795f8201527f2073656174000000000000000000000000000000000000000000000000000000602082015250565b5f6159e0602583614d80565b91506159eb82615986565b604082019050919050565b5f6020820190508181035f830152615a0d816159d4565b9050919050565b7f4e657874206465616c6572206d757374206e6f742062652074686520707265765f8201527f696f7573206465616c6572000000000000000000000000000000000000000000602082015250565b5f615a6e602b83614d80565b9150615a7982615a14565b604082019050919050565b5f6020820190508181035f830152615a9b81615a62565b9050919050565b5f615aac82614461565b91505f8203615abe57615abd614fb5565b5b600182039050919050565b7f4e6f742073686f77646f776e207374616765206f72206d6f7265207468616e205f8201527f312061637469766520706c617965720000000000000000000000000000000000602082015250565b5f615b23602f83614d80565b9150615b2e82615ac9565b604082019050919050565b5f6020820190508181035f830152615b5081615b17565b9050919050565b5f615b61826143f0565b9150615b6c836143f0565b925082615b7c57615b7b6152d1565b5b828204905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f615bbb83836146df565b60208301905092915050565b5f602082019050919050565b5f615bdd82615b87565b615be78185615b91565b9350615bf283615ba1565b805f5b83811015615c22578151615c098882615bb0565b9750615c1483615bc7565b925050600181019050615bf5565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f602082019050919050565b5f615c6e82615c2f565b615c788185615c39565b9350615c8383615c49565b805f5b83811015615cb3578151615c9a888261464d565b9750615ca583615c58565b925050600181019050615c86565b5085935050505092915050565b5f6060820190508181035f830152615cd88186615bd3565b90508181036020830152615cec8185615c64565b9050615cfb6040830184614957565b949350505050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302615d5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82615d24565b615d698683615d24565b95508019841693508086168417925050509392505050565b5f615d9b615d96615d91846143f0565b614582565b6143f0565b9050919050565b5f819050919050565b615db483615d81565b615dc8615dc082615da2565b848454615d30565b825550505050565b5f5f905090565b615ddf615dd0565b615dea818484615dab565b505050565b5b81811015615e0d57615e025f82615dd7565b600181019050615df0565b5050565b601f821115615e5257615e2381615d03565b615e2c84615d15565b81016020851015615e3b578190505b615e4f615e4785615d15565b830182615def565b50505b505050565b5f82821c905092915050565b5f615e725f1984600802615e57565b1980831691505092915050565b5f615e8a8383615e63565b9150826002028217905092915050565b615ea382614734565b67ffffffffffffffff811115615ebc57615ebb615544565b5b615ec68254615383565b615ed1828285615e11565b5f60209050601f831160018114615f02575f8415615ef0578287015190505b615efa8582615e7f565b865550615f61565b601f198416615f1086615d03565b5f5b82811015615f3757848901518255600182019150602085019450602081019050615f12565b86831015615f545784890151615f50601f891682615e63565b8355505b6001600288020188555050505b50505050505056fea26469706673582212208b8fa2e2b00d51a22efe8ead6354d79e6d8911552f12b2a7275f37f72384c50e64736f6c634300081d003300000000000000000000000027748f13cb0ae5cffd6c6a29499a98eb04da839e00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000
Deployed Bytecode
0x608060405234801561000f575f5ffd5b5060043610610225575f3560e01c806385f07bbe1161012e578063d4f77b1c116100b6578063eed1b11a1161007a578063eed1b11a146105c5578063f2349ff0146105e3578063f71d96cb14610601578063faff660e1461063b578063fb9bdab41461065957610225565b8063d4f77b1c14610545578063d7b496761461054f578063df5a065c14610559578063e255015614610589578063eac697c9146105a757610225565b8063af5b3799116100fd578063af5b3799146104d9578063b1501f9c146104f7578063bd23a5a714610501578063c040e6b81461051d578063c5caa27b1461053b57610225565b806385f07bbe146104615780638b5b9ccc1461047f5780639597684c1461049d57806397b2f556146104bb57610225565b80634e2786fb116101b15780636427039c116101805780636427039c146103df5780636af2ca69146103fd57806370984e971461041b5780637af3696d1461043957806384d452ff1461045757610225565b80634e2786fb146103695780635905d1fd1461038757806359ebf1f31461039157806359f7af70146103af57610225565b8063367df6d5116101f8578063367df6d5146102af57806339d505fc146102cd57806339fddff6146102fd5780634411b3eb1461032d5780634ba2363a1461034b57610225565b806308853693146102295780630f53fc4b14610245578063219406bd1461026157806322876d511461027f575b5f5ffd5b610243600480360381019061023e9190614423565b610677565b005b61025f600480360381019061025a9190614497565b610ce5565b005b610269610d9b565b60405161027691906144e4565b60405180910390f35b61029960048036038101906102949190614557565b610dad565b6040516102a691906144e4565b60405180910390f35b6102b7610e88565b6040516102c491906145dd565b60405180910390f35b6102e760048036038101906102e291906145f6565b610ead565b6040516102f491906146c6565b60405180910390f35b610317600480360381019061031291906145f6565b6110d6565b6040516103249190614937565b60405180910390f35b6103356112ed565b60405161034291906144e4565b60405180910390f35b6103536112f2565b6040516103609190614966565b60405180910390f35b6103716112f8565b60405161037e9190614966565b60405180910390f35b61038f6112fd565b005b610399611674565b6040516103a69190614966565b60405180910390f35b6103c960048036038101906103c4919061497f565b61167a565b6040516103d691906144e4565b60405180910390f35b6103e76116a3565b6040516103f491906144e4565b60405180910390f35b610405611706565b60405161041291906144e4565b60405180910390f35b61042361170b565b6040516104309190614966565b60405180910390f35b610441611711565b60405161044e91906144e4565b60405180910390f35b61045f6117f8565b005b6104696119d6565b60405161047691906144e4565b60405180910390f35b6104876119db565b6040516104949190614b66565b60405180910390f35b6104a5611c8a565b6040516104b29190614ba6565b60405180910390f35b6104c3611caf565b6040516104d091906144e4565b60405180910390f35b6104e1611cc1565b6040516104ee9190614966565b60405180910390f35b6104ff611cc7565b005b61051b60048036038101906105169190614557565b611e03565b005b610525611ed5565b6040516105329190614c32565b60405180910390f35b610543611ee7565b005b61054d611f80565b005b610557612622565b005b610573600480360381019061056e9190614c75565b612840565b60405161058091906144e4565b60405180910390f35b610591612b6e565b60405161059e9190614966565b60405180910390f35b6105af612b74565b6040516105bc9190614966565b60405180910390f35b6105cd612b7a565b6040516105da91906144e4565b60405180910390f35b6105eb612c90565b6040516105f891906144e4565b60405180910390f35b61061b6004803603810190610616919061497f565b612ca3565b6040516106329b9a99989796959493929190614cbe565b60405180910390f35b610643612d6a565b6040516106509190614d67565b60405180910390f35b610661612d7d565b60405161066e91906144e4565b60405180910390f35b6003600c81111561068b5761068a614bbf565b5b60015f9054906101000a900460ff16600c8111156106ac576106ab614bbf565b5b14806106ea57506005600c8111156106c7576106c6614bbf565b5b60015f9054906101000a900460ff16600c8111156106e8576106e7614bbf565b5b145b8061072757506007600c81111561070457610703614bbf565b5b60015f9054906101000a900460ff16600c81111561072557610724614bbf565b5b145b8061076457506009600c81111561074157610740614bbf565b5b60015f9054906101000a900460ff16600c81111561076257610761614bbf565b5b145b6107a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161079a90614dda565b60405180910390fd5b5f3073ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b81526004016107dd9190614df8565b602060405180830381865afa1580156107f8573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061081c9190614e25565b9050600660019054906101000a900460ff1660ff168160ff1614610875576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161086c90614e9a565b60405180910390fd5b600a8160ff16600a811061088c5761088b614eb8565b5b600a02016004015f9054906101000a900460ff16156108e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d790614f2f565b60405180910390fd5b600a8160ff16600a81106108f7576108f6614eb8565b5b600a020160040160019054906101000a900460ff161561094c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161094390614f97565b60405180910390fd5b60048081111561095f5761095e614bbf565b5b83600481111561097257610971614bbf565b5b036109b2576001600a8260ff16600a81106109905761098f614eb8565b5b600a02016004015f6101000a81548160ff021916908315150217905550610c77565b600160048111156109c6576109c5614bbf565b5b8360048111156109d9576109d8614bbf565b5b03610a7e575f600a8260ff16600a81106109f6576109f5614eb8565b5b600a020160020154600354610a0b9190614fe2565b905080600a8360ff16600a8110610a2557610a24614eb8565b5b600a0201600101541015610a6e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a659061505f565b60405180910390fd5b610a788282612d90565b50610c76565b60026004811115610a9257610a91614bbf565b5b836004811115610aa557610aa4614bbf565b5b03610bae576003548211610aee576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ae5906150ed565b60405180910390fd5b5f600a8260ff16600a8110610b0657610b05614eb8565b5b600a02016002015483610b199190614fe2565b905080600a8360ff16600a8110610b3357610b32614eb8565b5b600a0201600101541015610b7c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b739061505f565b60405180910390fd5b610b868282612d90565b8260038190555081600660026101000a81548160ff021916908360ff16021790555050610c75565b60036004811115610bc257610bc1614bbf565b5b836004811115610bd557610bd4614bbf565b5b03610c7457600354600a8260ff16600a8110610bf457610bf3614eb8565b5b600a02016002015414610c3c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c3390615155565b60405180910390fd5b6001600a8260ff16600a8110610c5557610c54614eb8565b5b600a020160040160026101000a81548160ff0219169083151502179055505b5b5b5b826004811115610c8a57610c89614bbf565b5b3373ffffffffffffffffffffffffffffffffffffffff167fa2b0aae25f339f90f1bd8b3489bdfb93b146ef6808f04c4e39235ca332007b2384604051610cd09190614966565b60405180910390a3610ce0612f06565b505050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610d74576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6b906151bd565b60405180910390fd5b80600a8360ff16600a8110610d8c57610d8b614eb8565b5b600a0201600801819055505050565b60065f9054906101000a900460ff1681565b5f5f5f90505b600a60ff168160ff161015610e47578273ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a8110610df057610def614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610e3a5780915050610e83565b8080600101915050610db3565b506040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e7a9061524b565b60405180910390fd5b919050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b610eb561423a565b5f5f90505f600a8460ff16600a8110610ed157610ed0614eb8565b5b600a02016007015f9054906101000a900460ff1690505f600a60065f9054906101000a900460ff1660ff16600a8110610f0d57610f0c614eb8565b5b600a02016007015f9054906101000a900460ff1690505b8060ff168260ff161461100757600a6001600a610f419190615269565b83610f4c919061529d565b610f5691906152fe565b915060ff8016606e8360ff16600a8110610f7357610f72614eb8565b5b602091828204019190069054906101000a900460ff1660ff1614158015610fee5750600a606e8360ff16600a8110610fae57610fad614eb8565b5b602091828204019190069054906101000a900460ff1660ff16600a8110610fd857610fd7614eb8565b5b600a02016009015f9054906101000a900460ff16155b15611002578280610ffe9061532e565b9350505b610f24565b5f3073ffffffffffffffffffffffffffffffffffffffff16637af3696d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611051573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110759190614e25565b905083855f6002811061108b5761108a614eb8565b5b602002019060ff16908160ff168152505080846110a8919061529d565b856001600281106110bc576110bb614eb8565b5b602002019060ff16908160ff168152505050505050919050565b6110de61425c565b600a8260ff16600a81106110f5576110f4614eb8565b5b600a0201604051806101800160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015481526020016002820154815260200160038201548152602001600482015f9054906101000a900460ff161515151581526020016004820160019054906101000a900460ff161515151581526020016004820160029054906101000a900460ff1615151515815260200160058201600280602002604051908101604052809291905f905b828210156112835783820180546111f890615383565b80601f016020809104026020016040519081016040528092919081815260200182805461122490615383565b801561126f5780601f106112465761010080835404028352916020019161126f565b820191905f5260205f20905b81548152906001019060200180831161125257829003601f168201915b5050505050815260200190600101906111e2565b505050508152602001600782015f9054906101000a900460ff1660ff1660ff16815260200160088201548152602001600982015f9054906101000a900460ff161515151581526020016009820160019054906101000a900460ff1615151515815250509050919050565b600a81565b60025481565b5f5481565b5f6007544261130c9190614fe2565b9050601e8111611351576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611348906153fd565b60405180910390fd5b5f60ff9050600a600c81111561136a57611369614bbf565b5b60015f9054906101000a900460ff16600c81111561138b5761138a614bbf565b5b03611509575f5f90505b600a60ff168160ff1610156114bb575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a81106113d2576113d1614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141580156114445750600a8160ff16600a811061142e5761142d614eb8565b5b600a02016009015f9054906101000a900460ff16155b80156114785750600a8160ff16600a811061146257611461614eb8565b5b600a02016004015f9054906101000a900460ff16155b80156114a157505f600a8260ff16600a811061149757611496614eb8565b5b600a020160080154145b156114ae578091506114bb565b8080600101915050611395565b5060ff80168160ff1603611504576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fb9061548b565b60405180910390fd5b61151c565b600660019054906101000a900460ff1690505b600a8160ff16600a811061153357611532614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f4f5c3d12cd160dbd97779d8d82003e4edc995550312b60d71dfdf4fe1932182a846040516115b59190614966565b60405180910390a36001600a8260ff16600a81106115d6576115d5614eb8565b5b600a020160090160016101000a81548160ff0219169083151502179055505f5f90505b600a60ff168160ff16101561166757600a8160ff16600a811061161f5761161e614eb8565b5b600a020160030154600a8260ff16600a811061163e5761163d614eb8565b5b600a02016001015f82825461165391906154a9565b9250508190555080806001019150506115f9565b50611670613435565b5050565b6103e881565b606e81600a8110611689575f80fd5b60209182820401919006915054906101000a900460ff1681565b5f5f5f90505f5f90505b600a60ff168160ff1610156116fe575f600a8260ff16600a81106116d4576116d3614eb8565b5b600a02016008015411156116f15781806116ed9061532e565b9250505b80806001019150506116ad565b508091505090565b60ff81565b60055481565b5f5f5f90505f5f90505b600a60ff168160ff1610156117f0575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061175857611757614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141580156117cf57505f1515600a8260ff16600a81106117b7576117b6614eb8565b5b600a02016009015f9054906101000a900460ff161515145b156117e35781806117df9061532e565b9250505b808060010191505061171b565b508091505090565b732a99ec82d658f7a77ddebfd83d0f8f591769cb6473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480611885575073101a25d0fdc4e9aca9fa65584a28781046f1beee73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b806118cf5750737d20fd2bd3d13b03571a36568cfcc2a4eb3c749e73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b806119195750733797a1f60c46d2d6f02c3568366712d8a8a69a7373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b611958576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161194f90615526565b60405180910390fd5b5f5f90505b600a60ff168160ff1610156119cb57600a8160ff16600a811061198357611982614eb8565b5b600a020160030154600a8260ff16600a81106119a2576119a1614eb8565b5b600a02016001015f8282546119b791906154a9565b92505081905550808060010191505061195d565b506119d4613435565b565b600281565b60605f600a60ff1667ffffffffffffffff8111156119fc576119fb615544565b5b604051908082528060200260200182016040528015611a3557816020015b611a2261425c565b815260200190600190039081611a1a5790505b5090505f5f90505b600a60ff168160ff161015611c8257600a8160ff16600a8110611a6357611a62614eb8565b5b600a0201604051806101800160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015481526020016002820154815260200160038201548152602001600482015f9054906101000a900460ff161515151581526020016004820160019054906101000a900460ff161515151581526020016004820160029054906101000a900460ff1615151515815260200160058201600280602002604051908101604052809291905f905b82821015611bf1578382018054611b6690615383565b80601f0160208091040260200160405190810160405280929190818152602001828054611b9290615383565b8015611bdd5780601f10611bb457610100808354040283529160200191611bdd565b820191905f5260205f20905b815481529060010190602001808311611bc057829003601f168201915b505050505081526020019060010190611b50565b505050508152602001600782015f9054906101000a900460ff1660ff1660ff16815260200160088201548152602001600982015f9054906101000a900460ff161515151581526020016009820160019054906101000a900460ff161515151581525050828260ff1681518110611c6a57611c69614eb8565b5b60200260200101819052508080600101915050611a3d565b508091505090565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606f5f9054906101000a900460ff1681565b60035481565b5f3073ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611d019190614df8565b602060405180830381865afa158015611d1c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d409190614e25565b905060ff80168160ff1603611d8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d81906155bb565b60405180910390fd5b600a8160ff16600a8110611da157611da0614eb8565b5b600a02016009015f9054906101000a900460ff1615611dc857611dc3816139d1565b611e00565b6001600a8260ff16600a8110611de157611de0614eb8565b5b600a020160090160016101000a81548160ff0219169083151502179055505b50565b5f73ffffffffffffffffffffffffffffffffffffffff1660095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611e92576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e8990615623565b60405180910390fd5b8060095f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900460ff1681565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f76576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f6d906151bd565b60405180910390fd5b611f7e612f06565b565b600a60ff16606f5f9054906101000a900460ff1660ff1610611fd7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611fce9061568b565b60405180910390fd5b5f5f90505b600a60ff168160ff1610156120a2573373ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061201957612018614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603612095576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161208c906156f3565b60405180910390fd5b8080600101915050611fdc565b505f5f90505b60ff8016606e8260ff16600a81106120c3576120c2614eb8565b5b602091828204019190069054906101000a900460ff1660ff16141580156120f05750600a60ff168160ff16105b156121085780806121009061532e565b9150506120a8565b600a60ff168160ff1610612151576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121489061575b565b60405180910390fd5b5f5f90505b5f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061218457612183614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141580156121d45750600a60ff168160ff16105b156121ec5780806121e49061532e565b915050612156565b600a60ff168160ff1610612235576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161222c906157c3565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a811061226357612262614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146122df576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122d69061582b565b60405180910390fd5b5f5f600c8111156122f3576122f2614bbf565b5b60015f9054906101000a900460ff16600c81111561231457612313614bbf565b5b101590506040518061018001604052803373ffffffffffffffffffffffffffffffffffffffff1681526020016103e881526020015f81526020015f81526020015f151581526020015f151581526020015f15158152602001604051806040016040528060405180602001604052805f815250815260200160405180602001604052805f81525081525081526020018460ff1681526020015f815260200182151581526020015f1515815250600a8360ff16600a81106123d6576123d5614eb8565b5b600a02015f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015f6101000a81548160ff02191690831515021790555060a08201518160040160016101000a81548160ff02191690831515021790555060c08201518160040160026101000a81548160ff02191690831515021790555060e0820151816005019060026124b29291906142db565b50610100820151816007015f6101000a81548160ff021916908360ff1602179055506101208201518160080155610140820151816009015f6101000a81548160ff0219169083151502179055506101608201518160090160016101000a81548160ff02191690831515021790555090505081606e8460ff16600a811061253b5761253a614eb8565b5b602091828204019190066101000a81548160ff021916908360ff160217905550606f5f81819054906101000a900460ff16809291906125799061532e565b91906101000a81548160ff021916908360ff160217905550508260ff168260ff163373ffffffffffffffffffffffffffffffffffffffff167f48fd1b53f938fc69bea369036925e24c1dc8d00c49430e46d1ccdd5c66b6290660405160405180910390a4600260ff16606f5f9054906101000a900460ff1660ff161015801561260f5750606f60019054906101000a900460ff16155b1561261d5761261c612f06565b5b505050565b6003600c81111561263657612635614bbf565b5b60015f9054906101000a900460ff16600c81111561265757612656614bbf565b5b1015612698576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161268f906158b9565b60405180910390fd5b5f3073ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b81526004016126d29190614df8565b602060405180830381865afa1580156126ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127119190614e25565b9050600a8160ff16600a811061272a57612729614eb8565b5b600a02016009015f9054906101000a900460ff161561277e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161277590615921565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167faca59e978a7cd7d8951a0c811dae34e013d0994836c236297513c3fb442bdef160405160405180910390a25f5f90505b600a60ff168160ff16101561283457600a8160ff16600a81106127ec576127eb614eb8565b5b600a020160030154600a8260ff16600a811061280b5761280a614eb8565b5b600a02016001015f82825461282091906154a9565b9250508190555080806001019150506127c6565b5061283d613435565b50565b5f5f600a600660019054906101000a900460ff1660ff16600a811061286857612867614eb8565b5b600a02016007015f9054906101000a900460ff1690505f600a60018361288e919061529d565b61289891906152fe565b90505b8160ff168160ff1614612b54575f606e8260ff16600a81106128c0576128bf614eb8565b5b602091828204019190069054906101000a900460ff16905060ff80168160ff1614612b33575f600a8260ff16600a81106128fd576128fc614eb8565b5b600a0201604051806101800160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015481526020016002820154815260200160038201548152602001600482015f9054906101000a900460ff161515151581526020016004820160019054906101000a900460ff161515151581526020016004820160029054906101000a900460ff1615151515815260200160058201600280602002604051908101604052809291905f905b82821015612a8b578382018054612a0090615383565b80601f0160208091040260200160405190810160405280929190818152602001828054612a2c90615383565b8015612a775780601f10612a4e57610100808354040283529160200191612a77565b820191905f5260205f20905b815481529060010190602001808311612a5a57829003601f168201915b5050505050815260200190600101906129ea565b505050508152602001600782015f9054906101000a900460ff1660ff1660ff16815260200160088201548152602001600982015f9054906101000a900460ff161515151581526020016009820160019054906101000a900460ff1615151515815250509050806101400151612b315785612b0b5781945050505050612b69565b8060800151158015612b1f57508060a00151155b15612b305781945050505050612b69565b5b505b600a600183612b42919061529d565b612b4c91906152fe565b91505061289b565b600660019054906101000a900460ff16925050505b919050565b60045481565b60075481565b5f5f5f90505f5f90505b600a60ff168160ff161015612c88575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a8110612bc157612bc0614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015612c335750600a8160ff16600a8110612c1d57612c1c614eb8565b5b600a02016004015f9054906101000a900460ff16155b8015612c675750600a8160ff16600a8110612c5157612c50614eb8565b5b600a02016009015f9054906101000a900460ff16155b15612c7b578180612c779061532e565b9250505b8080600101915050612b84565b508091505090565b600660029054906101000a900460ff1681565b600a81600a8110612cb2575f80fd5b600a02015f91509050805f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015490806002015490806003015490806004015f9054906101000a900460ff16908060040160019054906101000a900460ff16908060040160029054906101000a900460ff1690806007015f9054906101000a900460ff1690806008015490806009015f9054906101000a900460ff16908060090160019054906101000a900460ff1690508b565b606f60019054906101000a900460ff1681565b600660019054906101000a900460ff1681565b80600a8360ff16600a8110612da857612da7614eb8565b5b600a0201600101541015612df1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612de89061505f565b60405180910390fd5b80600a8360ff16600a8110612e0957612e08614eb8565b5b600a02016001015f828254612e1e9190614fe2565b9250508190555080600a8360ff16600a8110612e3d57612e3c614eb8565b5b600a02016002015f828254612e5291906154a9565b9250508190555080600a8360ff16600a8110612e7157612e70614eb8565b5b600a02016003015f828254612e8691906154a9565b925050819055508060025f828254612e9e91906154a9565b925050819055505f600a8360ff16600a8110612ebd57612ebc614eb8565b5b600a02016001015403612f02576001600a8360ff16600a8110612ee357612ee2614eb8565b5b600a020160040160016101000a81548160ff0219169083151502179055505b5050565b600a600c811115612f1a57612f19614bbf565b5b60015f9054906101000a900460ff16600c811115612f3b57612f3a614bbf565b5b148015612f5b5750612f4b612b7a565b60ff16612f566116a3565b60ff16105b612f6757426007819055505b5f600c811115612f7a57612f79614bbf565b5b60015f9054906101000a900460ff16600c811115612f9b57612f9a614bbf565b5b03613035575f5f90505b600a60ff168160ff16101561302757600a8160ff16600a8110612fcb57612fca614eb8565b5b600a02016009015f9054906101000a900460ff161561301a575f600a8260ff16600a8110612ffc57612ffb614eb8565b5b600a02016009015f6101000a81548160ff0219169083151502179055505b8080600101915050612fa5565b50613030613c08565b613433565b6001600c81111561304957613048614bbf565b5b60015f9054906101000a900460ff16600c81111561306a57613069614bbf565b5b14806130a857506002600c81111561308557613084614bbf565b5b60015f9054906101000a900460ff16600c8111156130a6576130a5614bbf565b5b145b806130e557506004600c8111156130c2576130c1614bbf565b5b60015f9054906101000a900460ff16600c8111156130e3576130e2614bbf565b5b145b8061312257506006600c8111156130ff576130fe614bbf565b5b60015f9054906101000a900460ff16600c8111156131205761311f614bbf565b5b145b8061315f57506008600c81111561313c5761313b614bbf565b5b60015f9054906101000a900460ff16600c81111561315d5761315c614bbf565b5b145b1561329b575f5f90505f61317282612840565b905060065f9054906101000a900460ff1660ff168160ff1603613279576001600c8111156131a3576131a2614bbf565b5b60015f9054906101000a900460ff16600c8111156131c4576131c3614bbf565b5b036131f75760065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff16021790555061326a565b5f6001905060065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff1602179055505f61322f82612840565b905080600660016101000a81548160ff021916908360ff16021790555080600660026101000a81548160ff021916908360ff16021790555050505b613272613c08565b5050613433565b80600660016101000a81548160ff021916908360ff1602179055505050613432565b600a600c8111156132af576132ae614bbf565b5b60015f9054906101000a900460ff16600c8111156132d0576132cf614bbf565b5b03613306576132dd612b7a565b60ff166132e86116a3565b60ff1603613301576132f8613cb8565b613300613435565b5b613433565b6001613310612b7a565b60ff160361332d57613320613cb8565b613328613435565b613433565b5f600190505f61333c82612840565b9050600660029054906101000a900460ff1660ff168160ff1603613414575f6003819055505f5f90505b600a60ff168160ff1610156133db575f600a8260ff16600a811061338d5761338c614eb8565b5b600a0201600201819055505f600a8260ff16600a81106133b0576133af614eb8565b5b600a020160040160026101000a81548160ff0219169083151502179055508080600101915050613366565b5060065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff16021790555061340d613c08565b5050613433565b80600660016101000a81548160ff021916908360ff16021790555050505b5b565b5f5f8154809291906134469061593f565b91905055505f60015f6101000a81548160ff0219169083600c81111561346f5761346e614bbf565b5b02179055505f6002819055505f6003819055505f600a60065f9054906101000a900460ff1660ff16600a81106134a8576134a7614eb8565b5b600a02016007015f9054906101000a900460ff1690505f5f90505b600a60ff168160ff16101561351a57600a8160ff16600a81106134e9576134e8614eb8565b5b600a020160090160019054906101000a900460ff161561350d5761350c816139d1565b5b80806001019150506134c3565b505f5f90505b600a60ff168160ff16101561370a57600a8160ff16600a811061354657613545614eb8565b5b600a02016009015f9054906101000a900460ff1615613595575f600a8260ff16600a811061357757613576614eb8565b5b600a02016009015f6101000a81548160ff0219169083151502179055505b5f600a8260ff16600a81106135ad576135ac614eb8565b5b600a0201600201819055505f600a8260ff16600a81106135d0576135cf614eb8565b5b600a0201600301819055505f600a8260ff16600a81106135f3576135f2614eb8565b5b600a02016004015f6101000a81548160ff0219169083151502179055505f600a8260ff16600a811061362857613627614eb8565b5b600a020160040160026101000a81548160ff0219169083151502179055505f600a8260ff16600a811061365e5761365d614eb8565b5b600a020160040160016101000a81548160ff021916908315150217905550604051806040016040528060405180602001604052805f815250815260200160405180602001604052805f815250815250600a8260ff16600a81106136c4576136c3614eb8565b5b600a02016005019060026136d99291906142db565b505f600a8260ff16600a81106136f2576136f1614eb8565b5b600a0201600801819055508080600101915050613520565b5060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16637243087a6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015613771575f5ffd5b505af1158015613783573d5f5f3e3d5ffd5b50505050600260ff16606f5f9054906101000a900460ff1660ff1610156137e6575f6007819055505f60065f6101000a81548160ff021916908360ff1602179055505f600660016101000a81548160ff021916908360ff160217905550506139cf565b5f600a6001836137f6919061529d565b61380091906152fe565b90505b60ff8016606e8260ff16600a811061381e5761381d614eb8565b5b602091828204019190069054906101000a900460ff1660ff1614801561384a57508160ff168160ff1614155b1561386f57600a60018261385e919061529d565b61386891906152fe565b9050613803565b60ff8016606e8260ff16600a811061388a57613889614eb8565b5b602091828204019190069054906101000a900460ff1660ff16036138e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016138da906159f6565b60405180910390fd5b8160ff168160ff160361392b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161392290615a84565b60405180910390fd5b606e8160ff16600a811061394257613941614eb8565b5b602091828204019190069054906101000a900460ff1660065f6101000a81548160ff021916908360ff16021790555060065f9054906101000a900460ff16600660016101000a81548160ff021916908360ff160217905550600660019054906101000a900460ff16600660026101000a81548160ff021916908360ff1602179055506139cc612f06565b50505b565b5f600a8260ff16600a81106139e9576139e8614eb8565b5b600a02016009015f6101000a81548160ff0219169083151502179055505f600a8260ff16600a8110613a1e57613a1d614eb8565b5b600a020160090160016101000a81548160ff0219169083151502179055505f600a8260ff16600a8110613a5457613a53614eb8565b5b600a02016008018190555060ff606e600a8360ff16600a8110613a7a57613a79614eb8565b5b600a02016007015f9054906101000a900460ff1660ff16600a8110613aa257613aa1614eb8565b5b602091828204019190066101000a81548160ff021916908360ff160217905550606f5f81819054906101000a900460ff1680929190613ae090615aa2565b91906101000a81548160ff021916908360ff16021790555050600a8160ff16600a8110613b1057613b0f614eb8565b5b600a02016007015f9054906101000a900460ff1660ff168160ff16600a8360ff16600a8110613b4257613b41614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f28245a946274b1107d714f5e9866b8aec561e4bd74aae266a4709caa59dce12660405160405180910390a45f600a8260ff16600a8110613bc257613bc1614eb8565b5b600a02015f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6001805f9054906101000a900460ff16600c811115613c2a57613c29614bbf565b5b613c34919061529d565b60ff16600c811115613c4957613c48614bbf565b5b60015f6101000a81548160ff0219169083600c811115613c6c57613c6b614bbf565b5b02179055507f2813df2508ecb23fa82b5b4208c20d282c618a88460be290fc9954fc3f750d4460015f9054906101000a900460ff16604051613cae9190614c32565b60405180910390a1565b600a600c811115613ccc57613ccb614bbf565b5b60015f9054906101000a900460ff16600c811115613ced57613cec614bbf565b5b1480613d0357506001613cfe612b7a565b60ff16145b613d42576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401613d3990615b39565b60405180910390fd5b5f5f90505f5f613d506116a3565b60ff1611613d5f576001613d68565b613d676116a3565b5b90505f8160ff1667ffffffffffffffff811115613d8857613d87615544565b5b604051908082528060200260200182016040528015613db65781602001602082028036833780820191505090505b5090505f5f90506001613dc7612b7a565b60ff1603613f05575f5f5f90505b600a60ff168160ff161015613ed2575f73ffffffffffffffffffffffffffffffffffffffff16600a8260ff16600a8110613e1257613e11614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015613e845750600a8160ff16600a8110613e6e57613e6d614eb8565b5b600a02016009015f9054906101000a900460ff16155b8015613eb85750600a8160ff16600a8110613ea257613ea1614eb8565b5b600a02016004015f9054906101000a900460ff16155b15613ec557809150613ed2565b8080600101915050613dd5565b5080835f81518110613ee757613ee6614eb8565b5b602002602001019060ff16908160ff16815250506001915050613fd7565b5f5f90505b600a60ff168160ff161015613fd5575f600a8260ff16600a8110613f3157613f30614eb8565b5b600a02016008015490505f8103613f485750613fc8565b85811115613f845780955081845f81518110613f6757613f66614eb8565b5b602002602001019060ff16908160ff168152505060019250613fc6565b858103613fc55781848460ff1681518110613fa257613fa1614eb8565b5b602002602001019060ff16908160ff16815250508280613fc19061532e565b9350505b5b505b8080600101915050613f0a565b505b5f8160ff16600254613fe99190615b57565b90505f8260ff1667ffffffffffffffff81111561400957614008615544565b5b6040519080825280602002602001820160405280156140375781602001602082028036833780820191505090505b5090505f5f90505b8360ff168160ff1610156140db575f858260ff168151811061406457614063614eb8565b5b6020026020010151905080838360ff168151811061408557614084614eb8565b5b602002602001019060ff16908160ff168152505083600a8260ff16600a81106140b1576140b0614eb8565b5b600a02016001015f8282546140c691906154a9565b9250508190555050808060010191505061403f565b505f8360ff1667ffffffffffffffff8111156140fa576140f9615544565b5b6040519080825280602002602001820160405280156141285781602001602082028036833780820191505090505b5090505f5f90505b8460ff168160ff1610156141f557600a838260ff168151811061415657614155614eb8565b5b602002602001015160ff16600a811061417257614171614eb8565b5b600a02015f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16828260ff16815181106141ae576141ad614eb8565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508080600101915050614130565b507fa050791fd343ccccb174329b5bb9dbc0760e236055988ffec7d849991a160f9c81838560405161422993929190615cc0565b60405180910390a150505050505050565b6040518060400160405280600290602082028036833780820191505090505090565b6040518061018001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f81526020015f151581526020015f151581526020015f151581526020016142b6614327565b81526020015f60ff1681526020015f81526020015f151581526020015f151581525090565b8260028101928215614316579160200282015b828111156143155782518290816143059190615e9a565b50916020019190600101906142ee565b5b509050614323919061434e565b5090565b60405180604001604052806002905b60608152602001906001900390816143365790505090565b5b8082111561436d575f81816143649190614371565b5060010161434f565b5090565b50805461437d90615383565b5f825580601f1061438e57506143ab565b601f0160209004905f5260205f20908101906143aa91906143ae565b5b50565b5b808211156143c5575f815f9055506001016143af565b5090565b5f5ffd5b600581106143d9575f5ffd5b50565b5f813590506143ea816143cd565b92915050565b5f819050919050565b614402816143f0565b811461440c575f5ffd5b50565b5f8135905061441d816143f9565b92915050565b5f5f60408385031215614439576144386143c9565b5b5f614446858286016143dc565b92505060206144578582860161440f565b9150509250929050565b5f60ff82169050919050565b61447681614461565b8114614480575f5ffd5b50565b5f813590506144918161446d565b92915050565b5f5f604083850312156144ad576144ac6143c9565b5b5f6144ba85828601614483565b92505060206144cb8582860161440f565b9150509250929050565b6144de81614461565b82525050565b5f6020820190506144f75f8301846144d5565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f614526826144fd565b9050919050565b6145368161451c565b8114614540575f5ffd5b50565b5f813590506145518161452d565b92915050565b5f6020828403121561456c5761456b6143c9565b5b5f61457984828501614543565b91505092915050565b5f819050919050565b5f6145a56145a061459b846144fd565b614582565b6144fd565b9050919050565b5f6145b68261458b565b9050919050565b5f6145c7826145ac565b9050919050565b6145d7816145bd565b82525050565b5f6020820190506145f05f8301846145ce565b92915050565b5f6020828403121561460b5761460a6143c9565b5b5f61461884828501614483565b91505092915050565b5f60029050919050565b5f81905092915050565b5f819050919050565b61464781614461565b82525050565b5f614658838361463e565b60208301905092915050565b5f602082019050919050565b61467981614621565b614683818461462b565b925061468e82614635565b805f5b838110156146be5781516146a5878261464d565b96506146b083614664565b925050600181019050614691565b505050505050565b5f6040820190506146d95f830184614670565b92915050565b6146e88161451c565b82525050565b6146f7816143f0565b82525050565b5f8115159050919050565b614711816146fd565b82525050565b5f60029050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61477682614734565b614780818561473e565b935061479081856020860161474e565b6147998161475c565b840191505092915050565b5f6147af838361476c565b905092915050565b5f602082019050919050565b5f6147cd82614717565b6147d78185614721565b9350836020820285016147e98561472b565b805f5b85811015614824578484038952815161480585826147a4565b9450614810836147b7565b925060208a019950506001810190506147ec565b50829750879550505050505092915050565b5f61018083015f83015161484c5f8601826146df565b50602083015161485f60208601826146ee565b50604083015161487260408601826146ee565b50606083015161488560608601826146ee565b5060808301516148986080860182614708565b5060a08301516148ab60a0860182614708565b5060c08301516148be60c0860182614708565b5060e083015184820360e08601526148d682826147c3565b9150506101008301516148ed61010086018261463e565b506101208301516149026101208601826146ee565b50610140830151614917610140860182614708565b5061016083015161492c610160860182614708565b508091505092915050565b5f6020820190508181035f83015261494f8184614836565b905092915050565b614960816143f0565b82525050565b5f6020820190506149795f830184614957565b92915050565b5f60208284031215614994576149936143c9565b5b5f6149a18482850161440f565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f61018083015f8301516149e95f8601826146df565b5060208301516149fc60208601826146ee565b506040830151614a0f60408601826146ee565b506060830151614a2260608601826146ee565b506080830151614a356080860182614708565b5060a0830151614a4860a0860182614708565b5060c0830151614a5b60c0860182614708565b5060e083015184820360e0860152614a7382826147c3565b915050610100830151614a8a61010086018261463e565b50610120830151614a9f6101208601826146ee565b50610140830151614ab4610140860182614708565b50610160830151614ac9610160860182614708565b508091505092915050565b5f614adf83836149d3565b905092915050565b5f602082019050919050565b5f614afd826149aa565b614b0781856149b4565b935083602082028501614b19856149c4565b805f5b85811015614b545784840389528151614b358582614ad4565b9450614b4083614ae7565b925060208a01995050600181019050614b1c565b50829750879550505050505092915050565b5f6020820190508181035f830152614b7e8184614af3565b905092915050565b5f614b90826145ac565b9050919050565b614ba081614b86565b82525050565b5f602082019050614bb95f830184614b97565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d8110614bfd57614bfc614bbf565b5b50565b5f819050614c0d82614bec565b919050565b5f614c1c82614c00565b9050919050565b614c2c81614c12565b82525050565b5f602082019050614c455f830184614c23565b92915050565b614c54816146fd565b8114614c5e575f5ffd5b50565b5f81359050614c6f81614c4b565b92915050565b5f60208284031215614c8a57614c896143c9565b5b5f614c9784828501614c61565b91505092915050565b614ca98161451c565b82525050565b614cb8816146fd565b82525050565b5f61016082019050614cd25f83018e614ca0565b614cdf602083018d614957565b614cec604083018c614957565b614cf9606083018b614957565b614d06608083018a614caf565b614d1360a0830189614caf565b614d2060c0830188614caf565b614d2d60e08301876144d5565b614d3b610100830186614957565b614d49610120830185614caf565b614d57610140830184614caf565b9c9b505050505050505050505050565b5f602082019050614d7a5f830184614caf565b92915050565b5f82825260208201905092915050565b7f47616d65206e6f7420696e20612062657474696e6720737461676500000000005f82015250565b5f614dc4601b83614d80565b9150614dcf82614d90565b602082019050919050565b5f6020820190508181035f830152614df181614db8565b9050919050565b5f602082019050614e0b5f830184614ca0565b92915050565b5f81519050614e1f8161446d565b92915050565b5f60208284031215614e3a57614e396143c9565b5b5f614e4784828501614e11565b91505092915050565b7f4e6f7420796f7572207475726e000000000000000000000000000000000000005f82015250565b5f614e84600d83614d80565b9150614e8f82614e50565b602082019050919050565b5f6020820190508181035f830152614eb181614e78565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f506c617965722068617320666f6c6465640000000000000000000000000000005f82015250565b5f614f19601183614d80565b9150614f2482614ee5565b602082019050919050565b5f6020820190508181035f830152614f4681614f0d565b9050919050565b7f506c6179657220697320616c6c2d696e000000000000000000000000000000005f82015250565b5f614f81601083614d80565b9150614f8c82614f4d565b602082019050919050565b5f6020820190508181035f830152614fae81614f75565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f614fec826143f0565b9150614ff7836143f0565b925082820390508181111561500f5761500e614fb5565b5b92915050565b7f4e6f7420656e6f756768206368697073000000000000000000000000000000005f82015250565b5f615049601083614d80565b915061505482615015565b602082019050919050565b5f6020820190508181035f8301526150768161503d565b9050919050565b7f5261697365206d75737420626520686967686572207468616e2063757272656e5f8201527f7420626574000000000000000000000000000000000000000000000000000000602082015250565b5f6150d7602583614d80565b91506150e28261507d565b604082019050919050565b5f6020820190508181035f830152615104816150cb565b9050919050565b7f4d7573742063616c6c206f7220726169736500000000000000000000000000005f82015250565b5f61513f601283614d80565b915061514a8261510b565b602082019050919050565b5f6020820190508181035f83015261516c81615133565b9050919050565b7f4f6e6c79204465636b48616e646c65722063616e2063616c6c207468697300005f82015250565b5f6151a7601e83614d80565b91506151b282615173565b602082019050919050565b5f6020820190508181035f8301526151d48161519b565b9050919050565b7f506c61796572206e6f7420666f756e6420666f7220676976656e2061646472655f8201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b5f615235602283614d80565b9150615240826151db565b604082019050919050565b5f6020820190508181035f83015261526281615229565b9050919050565b5f61527382614461565b915061527e83614461565b9250828203905060ff81111561529757615296614fb5565b5b92915050565b5f6152a782614461565b91506152b283614461565b9250828201905060ff8111156152cb576152ca614fb5565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f61530882614461565b915061531383614461565b925082615323576153226152d1565b5b828206905092915050565b5f61533882614461565b915060ff820361534b5761534a614fb5565b5b600182019050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061539a57607f821691505b6020821081036153ad576153ac615356565b5b50919050565b7f506c6179657220686173203330207365636f6e647320746f20616374000000005f82015250565b5f6153e7601c83614d80565b91506153f2826153b3565b602082019050919050565b5f6020820190508181035f830152615414816153db565b9050919050565b7f416c6c20706c617965727320686176652072657665616c6564207468656972205f8201527f6361726473000000000000000000000000000000000000000000000000000000602082015250565b5f615475602583614d80565b91506154808261541b565b604082019050919050565b5f6020820190508181035f8301526154a281615469565b9050919050565b5f6154b3826143f0565b91506154be836143f0565b92508282019050808211156154d6576154d5614fb5565b5b92915050565b7f4f6e6c79204a6f686e732063616e2063616c6c207468697300000000000000005f82015250565b5f615510601883614d80565b915061551b826154dc565b602082019050919050565b5f6020820190508181035f83015261553d81615504565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f506c61796572206e6f7420696e2067616d6500000000000000000000000000005f82015250565b5f6155a5601283614d80565b91506155b082615571565b602082019050919050565b5f6020820190508181035f8301526155d281615599565b9050919050565b7f4465636b48616e646c657220616c7265616479207365740000000000000000005f82015250565b5f61560d601783614d80565b9150615618826155d9565b602082019050919050565b5f6020820190508181035f83015261563a81615601565b9050919050565b7f526f6f6d2069732066756c6c00000000000000000000000000000000000000005f82015250565b5f615675600c83614d80565b915061568082615641565b602082019050919050565b5f6020820190508181035f8301526156a281615669565b9050919050565b7f416c726561647920696e2067616d6500000000000000000000000000000000005f82015250565b5f6156dd600f83614d80565b91506156e8826156a9565b602082019050919050565b5f6020820190508181035f83015261570a816156d1565b9050919050565b7f4e6f20656d7074792073656174730000000000000000000000000000000000005f82015250565b5f615745600e83614d80565b915061575082615711565b602082019050919050565b5f6020820190508181035f83015261577281615739565b9050919050565b7f4e6f20656d70747920706c6179657273000000000000000000000000000000005f82015250565b5f6157ad601083614d80565b91506157b882615779565b602082019050919050565b5f6020820190508181035f8301526157da816157a1565b9050919050565b7f4e756c6c20706c6179657220696e646578206e6f7420666f756e6400000000005f82015250565b5f615815601b83614d80565b9150615820826157e1565b602082019050919050565b5f6020820190508181035f83015261584281615809565b9050919050565b7f43616e6e6f74207265706f727420696e76616c6964206361726473206265666f5f8201527f726520707265666c6f7000000000000000000000000000000000000000000000602082015250565b5f6158a3602a83614d80565b91506158ae82615849565b604082019050919050565b5f6020820190508181035f8301526158d081615897565b9050919050565b7f506c61796572206973206a6f696e696e67206e65787420726f756e64000000005f82015250565b5f61590b601c83614d80565b9150615916826158d7565b602082019050919050565b5f6020820190508181035f830152615938816158ff565b9050919050565b5f615949826143f0565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361597b5761597a614fb5565b5b600182019050919050565b7f4e657874206465616c6572206d757374206e6f7420626520616e20656d7074795f8201527f2073656174000000000000000000000000000000000000000000000000000000602082015250565b5f6159e0602583614d80565b91506159eb82615986565b604082019050919050565b5f6020820190508181035f830152615a0d816159d4565b9050919050565b7f4e657874206465616c6572206d757374206e6f742062652074686520707265765f8201527f696f7573206465616c6572000000000000000000000000000000000000000000602082015250565b5f615a6e602b83614d80565b9150615a7982615a14565b604082019050919050565b5f6020820190508181035f830152615a9b81615a62565b9050919050565b5f615aac82614461565b91505f8203615abe57615abd614fb5565b5b600182039050919050565b7f4e6f742073686f77646f776e207374616765206f72206d6f7265207468616e205f8201527f312061637469766520706c617965720000000000000000000000000000000000602082015250565b5f615b23602f83614d80565b9150615b2e82615ac9565b604082019050919050565b5f6020820190508181035f830152615b5081615b17565b9050919050565b5f615b61826143f0565b9150615b6c836143f0565b925082615b7c57615b7b6152d1565b5b828204905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f615bbb83836146df565b60208301905092915050565b5f602082019050919050565b5f615bdd82615b87565b615be78185615b91565b9350615bf283615ba1565b805f5b83811015615c22578151615c098882615bb0565b9750615c1483615bc7565b925050600181019050615bf5565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f602082019050919050565b5f615c6e82615c2f565b615c788185615c39565b9350615c8383615c49565b805f5b83811015615cb3578151615c9a888261464d565b9750615ca583615c58565b925050600181019050615c86565b5085935050505092915050565b5f6060820190508181035f830152615cd88186615bd3565b90508181036020830152615cec8185615c64565b9050615cfb6040830184614957565b949350505050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302615d5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82615d24565b615d698683615d24565b95508019841693508086168417925050509392505050565b5f615d9b615d96615d91846143f0565b614582565b6143f0565b9050919050565b5f819050919050565b615db483615d81565b615dc8615dc082615da2565b848454615d30565b825550505050565b5f5f905090565b615ddf615dd0565b615dea818484615dab565b505050565b5b81811015615e0d57615e025f82615dd7565b600181019050615df0565b5050565b601f821115615e5257615e2381615d03565b615e2c84615d15565b81016020851015615e3b578190505b615e4f615e4785615d15565b830182615def565b50505b505050565b5f82821c905092915050565b5f615e725f1984600802615e57565b1980831691505092915050565b5f615e8a8383615e63565b9150826002028217905092915050565b615ea382614734565b67ffffffffffffffff811115615ebc57615ebb615544565b5b615ec68254615383565b615ed1828285615e11565b5f60209050601f831160018114615f02575f8415615ef0578287015190505b615efa8582615e7f565b865550615f61565b601f198416615f1086615d03565b5f5b82811015615f3757848901518255600182019150602085019450602081019050615f12565b86831015615f545784890151615f50601f891682615e63565b8355505b6001600288020188555050505b50505050505056fea26469706673582212208b8fa2e2b00d51a22efe8ead6354d79e6d8911552f12b2a7275f37f72384c50e64736f6c634300081d0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000027748f13cb0ae5cffd6c6a29499a98eb04da839e00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : _cryptoUtils (address): 0x27748f13cb0AE5CffD6c6a29499A98eB04da839E
Arg [1] : _smallBlind (uint256): 40
Arg [2] : _isPrivate (bool): False
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 00000000000000000000000027748f13cb0ae5cffd6c6a29499a98eb04da839e
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000028
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000000
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.