Source Code
Overview
ETH Balance
0 ETH
ETH Value
$0.00Latest 25 from a total of 37 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Reveal My Cards | 30020550 | 280 days ago | IN | 0 ETH | 0.00000065 | ||||
| Submit Decryptio... | 30020508 | 280 days ago | IN | 0 ETH | 0.0000004 | ||||
| Submit Decryptio... | 30020500 | 280 days ago | IN | 0 ETH | 0.00000015 | ||||
| Submit Decryptio... | 30020464 | 280 days ago | IN | 0 ETH | 0.0000004 | ||||
| Submit Decryptio... | 30020454 | 280 days ago | IN | 0 ETH | 0.00000014 | ||||
| Submit Decryptio... | 30020429 | 280 days ago | IN | 0 ETH | 0.00000067 | ||||
| Submit Decryptio... | 30020423 | 280 days ago | IN | 0 ETH | 0.00000021 | ||||
| Submit Decryptio... | 30020343 | 280 days ago | IN | 0 ETH | 0.00000038 | ||||
| Submit Decryptio... | 30020322 | 280 days ago | IN | 0 ETH | 0.00000018 | ||||
| Submit Encrypted... | 30020292 | 280 days ago | IN | 0 ETH | 0.00000281 | ||||
| Submit Encrypted... | 30018901 | 280 days ago | IN | 0 ETH | 0.00000294 | ||||
| Submit Decryptio... | 30015710 | 280 days ago | IN | 0 ETH | 0.00000052 | ||||
| Submit Decryptio... | 30015696 | 280 days ago | IN | 0 ETH | 0.00000039 | ||||
| Submit Decryptio... | 30015616 | 280 days ago | IN | 0 ETH | 0.00000023 | ||||
| Submit Decryptio... | 30015610 | 280 days ago | IN | 0 ETH | 0.00000034 | ||||
| Submit Encrypted... | 30015598 | 280 days ago | IN | 0 ETH | 0.00000173 | ||||
| Submit Encrypted... | 30015590 | 280 days ago | IN | 0 ETH | 0.00000527 | ||||
| Reveal My Cards | 30014906 | 280 days ago | IN | 0 ETH | 0.0000013 | ||||
| Reveal My Cards | 30012613 | 280 days ago | IN | 0 ETH | 0.00000131 | ||||
| Reveal My Cards | 29977446 | 281 days ago | IN | 0 ETH | 0.00000396 | ||||
| Reveal My Cards | 29977305 | 281 days ago | IN | 0 ETH | 0.0000042 | ||||
| Reveal My Cards | 29977276 | 281 days ago | IN | 0 ETH | 0.00000438 | ||||
| Submit Decryptio... | 29977256 | 281 days ago | IN | 0 ETH | 0.00000361 | ||||
| Submit Decryptio... | 29977251 | 281 days ago | IN | 0 ETH | 0.00000093 | ||||
| Submit Decryptio... | 29977217 | 281 days ago | IN | 0 ETH | 0.00000364 |
Cross-Chain Transactions
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
DeckHandler
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 "./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);
}
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);
}
// 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;
// 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 "./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
bool hasPlayerRevealedCards = players[currentPlayerIndex].handScore > 0;
if (stage == GameStage.Showdown) {
// revert only if the player has already revealed their cards
require(!hasPlayerRevealedCards, "Player has already revealed their cards");
}
emit IdlePlayerKicked(msg.sender, players[currentPlayerIndex].addr, timeElapsed);
// todo: split the kicked player's chips between the other active players
players[currentPlayerIndex].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;
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 Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_texasHoldemRoom","type":"address"},{"internalType":"address","name":"_cryptoUtils","type":"address"},{"internalType":"address","name":"_handEvaluator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint8[]","name":"cardIndexes","type":"uint8[]"},{"indexed":false,"internalType":"bytes[]","name":"decryptionValues","type":"bytes[]"}],"name":"DecryptionValuesSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"bytes[]","name":"encryptedShuffle","type":"bytes[]"}],"name":"EncryptedShuffleSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"card1","type":"string"},{"indexed":false,"internalType":"string","name":"card2","type":"string"},{"indexed":false,"internalType":"string","name":"card3","type":"string"}],"name":"FlopRevealed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"string","name":"card1","type":"string"},{"indexed":false,"internalType":"string","name":"card2","type":"string"},{"indexed":false,"internalType":"enum PokerHandEvaluatorv2.HandRank","name":"rank","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"handScore","type":"uint256"}],"name":"PlayerCardsRevealed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"bytes","name":"c1","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"privateKey","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"c1InversePowPrivateKey","type":"bytes"}],"name":"PlayerRevealingCards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"card1","type":"string"}],"name":"RiverRevealed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"card1","type":"string"}],"name":"TurnRevealed","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"communityCards","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cryptoUtils","outputs":[{"internalType":"contract CryptoUtils","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"encryptedDeck","outputs":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBulkRoomData","outputs":[{"components":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"enum TexasHoldemRoom.GameStage","name":"stage","type":"uint8"},{"internalType":"uint256","name":"smallBlind","type":"uint256"},{"internalType":"uint256","name":"bigBlind","type":"uint256"},{"internalType":"uint8","name":"dealerPosition","type":"uint8"},{"internalType":"uint8","name":"currentPlayerIndex","type":"uint8"},{"internalType":"uint8","name":"lastRaiseIndex","type":"uint8"},{"internalType":"uint256","name":"pot","type":"uint256"},{"internalType":"uint256","name":"currentStageBet","type":"uint256"},{"internalType":"uint8","name":"numPlayers","type":"uint8"},{"internalType":"bool","name":"isPrivate","type":"bool"},{"internalType":"string[5]","name":"communityCards","type":"string[5]"},{"internalType":"bytes[]","name":"encryptedDeck","type":"bytes[]"},{"internalType":"uint256","name":"lastActionTimestamp","type":"uint256"}],"internalType":"struct DeckHandler.BulkRoomData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCommunityCards","outputs":[{"internalType":"string[5]","name":"","type":"string[5]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"cardIndex","type":"uint256"}],"name":"getEncrypedCard","outputs":[{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getEncryptedDeck","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"handEvaluator","outputs":[{"internalType":"contract PokerHandEvaluatorv2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"resetDeck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"c1","type":"bytes"},{"internalType":"bytes","name":"privateKey","type":"bytes"},{"internalType":"bytes","name":"c1InversePowPrivateKey","type":"bytes"}],"name":"revealMyCards","outputs":[{"internalType":"string","name":"card1","type":"string"},{"internalType":"string","name":"card2","type":"string"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"cardIndexes","type":"uint8[]"},{"internalType":"bytes[]","name":"decryptionValues","type":"bytes[]"}],"name":"submitDecryptionValues","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"encryptedShuffle","type":"bytes[]"}],"name":"submitEncryptedShuffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"texasHoldemRoom","outputs":[{"internalType":"contract TexasHoldemRoom","name":"","type":"address"}],"stateMutability":"view","type":"function"}]Contract Creation Code
608060405234801561000f575f5ffd5b506040516162293803806162298339818101604052810190610031919061022d565b8260075f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160065f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f5f90505b60348110156101c6575f60405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f15158152602001610100815250908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f01908161018d91906104ba565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155505080806001019150506100f6565b50505050610589565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101fc826101d3565b9050919050565b61020c816101f2565b8114610216575f5ffd5b50565b5f8151905061022781610203565b92915050565b5f5f5f60608486031215610244576102436101cf565b5b5f61025186828701610219565b935050602061026286828701610219565b925050604061027386828701610219565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806102f857607f821691505b60208210810361030b5761030a6102b4565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261036d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610332565b6103778683610332565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6103bb6103b66103b18461038f565b610398565b61038f565b9050919050565b5f819050919050565b6103d4836103a1565b6103e86103e0826103c2565b84845461033e565b825550505050565b5f5f905090565b6103ff6103f0565b61040a8184846103cb565b505050565b5b8181101561042d576104225f826103f7565b600181019050610410565b5050565b601f8211156104725761044381610311565b61044c84610323565b8101602085101561045b578190505b61046f61046785610323565b83018261040f565b50505b505050565b5f82821c905092915050565b5f6104925f1984600802610477565b1980831691505092915050565b5f6104aa8383610483565b9150826002028217905092915050565b6104c38261027d565b67ffffffffffffffff8111156104dc576104db610287565b5b6104e682546102e1565b6104f1828285610431565b5f60209050601f831160018114610522575f8415610510578287015190505b61051a858261049f565b865550610581565b601f19841661053086610311565b5f5b8281101561055757848901518255600182019150602085019450602081019050610532565b868310156105745784890151610570601f891682610483565b8355505b6001600288020188555050505b505050505050565b615c93806105965f395ff3fe608060405234801561000f575f5ffd5b50600436106100cd575f3560e01c80637243087a1161008a578063a377ac5f11610064578063a377ac5f146101ee578063c27a67581461020c578063c3f8295114610228578063eff02a071461025a576100cd565b80637243087a146101a857806374844e19146101b25780639597684c146101d0576100cd565b806316660082146100d15780631870fd6f146101015780632c242a181461011f578063523aadd41461013d57806362224385146101595780636681d2dd1461018a575b5f5ffd5b6100eb60048036038101906100e691906135f6565b61028a565b6040516100f89190613707565b60405180910390f35b610109610376565b60405161011691906137e2565b60405180910390f35b6101276104c8565b6040516101349190613b46565b60405180910390f35b61015760048036038101906101529190613e5e565b610d1a565b005b610173600480360381019061016e9190613ed4565b6119d5565b604051610181929190613fc0565b60405180910390f35b610192612ad6565b60405161019f919061406f565b60405180910390f35b6101b0612afb565b005b6101ba612cdf565b6040516101c79190614105565b60405180910390f35b6101d8612da9565b6040516101e59190614145565b60405180910390f35b6101f6612dce565b604051610203919061417e565b60405180910390f35b61022660048036038101906102219190614197565b612df3565b005b610242600480360381019061023d91906135f6565b61328e565b60405161025193929190614244565b60405180910390f35b610274600480360381019061026f91906135f6565b613354565b6040516102819190614280565b60405180910390f35b6102926133f3565b5f82815481106102a5576102a46142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546102cb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546102f7906142fa565b80156103425780601f1061031957610100808354040283529160200191610342565b820191905f5260205f20905b81548152906001019060200180831161032557829003601f168201915b50505050508152602001600182015f9054906101000a900460ff161515151581526020016002820154815250509050919050565b60605f5f8054905067ffffffffffffffff81111561039757610396613b6a565b5b6040519080825280602002602001820160405280156103ca57816020015b60608152602001906001900390816103b55790505b5090505f5f90505b5f805490508160ff1610156104c0575f8160ff16815481106103f7576103f66142a0565b5b905f5260205f2090600302015f018054610410906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461043c906142fa565b80156104875780601f1061045e57610100808354040283529160200191610487565b820191905f5260205f20905b81548152906001019060200180831161046a57829003601f168201915b5050505050828260ff16815181106104a2576104a16142a0565b5b602002602001018190525080806104b890614357565b9150506103d2565b508091505090565b6104d0613414565b604051806101c0016040528060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634e2786fb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610546573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105fd91906143e1565b600c81111561060f5761060e613802565b5b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e25501566040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106a29190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370984e976040518163ffffffff1660e01b8152600401602060405180830381865afa158015610711573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107359190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c89190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085e9190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2349ff06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108f49190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634ba2363a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610966573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061098a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663af5b37996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a1d9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166397b2f5566040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab09190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663faff660e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b469190614475565b151581526020016001600580602002604051908101604052809291905f905b82821015610c06578382018054610b7b906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054610ba7906142fa565b8015610bf25780601f10610bc957610100808354040283529160200191610bf2565b820191905f5260205f20905b815481529060010190602001808311610bd557829003601f168201915b505050505081526020019060010190610b65565b5050505081526020013073ffffffffffffffffffffffffffffffffffffffff16631870fd6f6040518163ffffffff1660e01b81526004015f60405180830381865afa158015610c57573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c7f91906145c1565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663eac697c96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cee573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d129190614393565b815250905090565b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da991906143e1565b90506002600c811115610dbf57610dbe613802565b5b81600c811115610dd257610dd1613802565b5b1480610e0257506004600c811115610ded57610dec613802565b5b81600c811115610e0057610dff613802565b5b145b80610e3157506006600c811115610e1c57610e1b613802565b5b81600c811115610e2f57610e2e613802565b5b145b80610e6057506008600c811115610e4b57610e4a613802565b5b81600c811115610e5e57610e5d613802565b5b145b610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e9690614652565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401610efa9190614690565b602060405180830381865afa158015610f15573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f399190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fca9190614420565b90508160ff168160ff1614611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161100b906146f3565b60405180910390fd5b8351855114611058576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104f90614781565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f19c122811f9e4d381304169d982476aabc6977d2467b14da176c96fee621b30386866040516110a0929190614847565b60405180910390a25f5f90505b85518160ff1610156111e35773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856868360ff16815181106110e9576110e86142a0565b5b60200260200101515f6040518363ffffffff1660e01b815260040161110f9291906148d3565b5f60405180830381865af4158015611129573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906111519190614986565b5f878360ff1681518110611168576111676142a0565b5b602002602001015160ff1681548110611184576111836142a0565b5b905f5260205f2090600302015f820151815f0190816111a39190614b64565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505080806111db90614357565b9150506110ad565b505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663df5a065c60016040518263ffffffff1660e01b81526004016112409190614c33565b602060405180830381865afa15801561125b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061127f9190614420565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112eb573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061130f9190614420565b60ff168160ff1603611951576004600c81111561132f5761132e613802565b5b84600c81111561134257611341613802565b5b03611684575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611399576113986142a0565b5b602002602001015160ff16815481106113b5576113b46142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016113dd9190614d83565b5f60405180830381865afa1580156113f7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061141f9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8a600181518110611474576114736142a0565b5b602002602001015160ff16815481106114905761148f6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016114b89190614d83565b5f60405180830381865afa1580156114d2573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114fa9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8b60028151811061154f5761154e6142a0565b5b602002602001015160ff168154811061156b5761156a6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016115939190614d83565b5f60405180830381865afa1580156115ad573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115d59190614e41565b90507f96e672e4b09efde01ec2235824d9eb4bf5003ba7bf63407a45bff0974415696f83838360405161160a93929190614e88565b60405180910390a18260015f60058110611627576116266142a0565b5b0190816116349190614f2a565b50816001806005811061164a576116496142a0565b5b0190816116579190614f2a565b5080600160026005811061166e5761166d6142a0565b5b01908161167b9190614f2a565b50505050611950565b6006600c81111561169857611697613802565b5b84600c8111156116ab576116aa613802565b5b036117eb575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611702576117016142a0565b5b602002602001015160ff168154811061171e5761171d6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016117469190614d83565b5f60405180830381865afa158015611760573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117889190614e41565b90507ff503b0d30585a8adce94d89b991f26d6f675f78304e4b85e7c0d75a1d8cb0bd3816040516117b99190614280565b60405180910390a18060016003600581106117d7576117d66142a0565b5b0190816117e49190614f2a565b505061194f565b6008600c8111156117ff576117fe613802565b5b84600c81111561181257611811613802565b5b0361194e575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611869576118686142a0565b5b602002602001015160ff1681548110611885576118846142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016118ad9190614d83565b5f60405180830381865afa1580156118c7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118ef9190614e41565b90507f17415641f4bcccb6710699f9b6ddf69fac7f253bd194cc81dd9a2ee32c2db0a3816040516119209190614280565b60405180910390a180600160046005811061193e5761193d6142a0565b5b01908161194b9190614f2a565b50505b5b5b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156119b7575f5ffd5b505af11580156119c9573d5f5f3e3d5ffd5b50505050505050505050565b6060805f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611a339190614690565b602060405180830381865afa158015611a4e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a729190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339fddff6836040518263ffffffff1660e01b8152600401611acf9190615008565b5f60405180830381865afa158015611ae9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b119190615252565b905080610140015115611b59576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5090615309565b60405180910390fd5b806080015115611b9e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9590615371565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e001515f60028110611bf257611bf16142a0565b5b60200201516040518263ffffffff1660e01b8152600401611c1391906153b2565b602060405180830381865afa158015611c2e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c529190614475565b611c91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8890615455565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e00151600160028110611ce657611ce56142a0565b5b60200201516040518263ffffffff1660e01b8152600401611d0791906153b2565b602060405180830381865afa158015611d22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d469190614475565b611d85576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d7c906154e3565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fcf675dbb98179665682abaede1b14f82aa2dde6d99e83b602058a320c435f29f888888604051611dcf93929190615501565b60405180910390a25f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339d505fc846040518263ffffffff1660e01b8152600401611e329190615008565b6040805180830381865afa158015611e4c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e7091906155f9565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16895f6101006040518463ffffffff1660e01b8152600401611eb09392919061565d565b5f60405180830381865af4158015611eca573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ef29190614986565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168b5f6101006040518463ffffffff1660e01b8152600401611f329392919061565d565b5f60405180830381865af4158015611f4c573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611f749190614986565b90505f5f845f60028110611f8b57611f8a6142a0565b5b602002015160ff1681548110611fa457611fa36142a0565b5b905f5260205f2090600302016040518060600160405290815f82018054611fca906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054611ff6906142fa565b80156120415780601f1061201857610100808354040283529160200191612041565b820191905f5260205f20905b81548152906001019060200180831161202457829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f5f85600160028110612086576120856142a0565b5b602002015160ff168154811061209f5761209e6142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546120c5906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546120f1906142fa565b801561213c5780601f106121135761010080835404028352916020019161213c565b820191905f5260205f20905b81548152906001019060200180831161211f57829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f60405180604001604052808581526020018481525090505f60405180604001604052808681526020018481525090505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168e5f6101006040518463ffffffff1660e01b81526004016121d99392919061565d565b5f60405180830381865af41580156121f3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061221b9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858a856040518463ffffffff1660e01b815260040161227c93929190615727565b5f604051808303815f875af1158015612297573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906122bf9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858b866040518463ffffffff1660e01b815260040161232093929190615727565b5f604051808303815f875af115801561233b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906123639190614986565b905060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9836040518263ffffffff1660e01b81526004016123bf9190613707565b5f60405180830381865afa1580156123d9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124019190614e41565b9d5060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9826040518263ffffffff1660e01b815260040161245d9190613707565b5f60405180830381865afa158015612477573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061249f9190614e41565b9c5050505050505050505050838160e001515f600281106124c3576124c26142a0565b5b6020020181905250828160e001516001600281106124e4576124e36142a0565b5b60200201819052506124f461349d565b84815f60078110612508576125076142a0565b5b60200201819052508381600160078110612525576125246142a0565b5b602002018190525060015f60058110612541576125406142a0565b5b01805461254d906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612579906142fa565b80156125c45780601f1061259b576101008083540402835291602001916125c4565b820191905f5260205f20905b8154815290600101906020018083116125a757829003601f168201915b5050505050816002600781106125dd576125dc6142a0565b5b6020020181905250600180600581106125f9576125f86142a0565b5b018054612605906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612631906142fa565b801561267c5780601f106126535761010080835404028352916020019161267c565b820191905f5260205f20905b81548152906001019060200180831161265f57829003601f168201915b505050505081600360078110612695576126946142a0565b5b602002018190525060016002600581106126b2576126b16142a0565b5b0180546126be906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546126ea906142fa565b80156127355780601f1061270c57610100808354040283529160200191612735565b820191905f5260205f20905b81548152906001019060200180831161271857829003601f168201915b50505050508160046007811061274e5761274d6142a0565b5b6020020181905250600160036005811061276b5761276a6142a0565b5b018054612777906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546127a3906142fa565b80156127ee5780601f106127c5576101008083540402835291602001916127ee565b820191905f5260205f20905b8154815290600101906020018083116127d157829003601f168201915b505050505081600560078110612807576128066142a0565b5b60200201819052506001600460058110612824576128236142a0565b5b018054612830906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461285c906142fa565b80156128a75780601f1061287e576101008083540402835291602001916128a7565b820191905f5260205f20905b81548152906001019060200180831161288a57829003601f168201915b5050505050816006600781106128c0576128bf6142a0565b5b60200201819052505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166369a684f6836040518263ffffffff1660e01b8152600401612923919061580d565b61018060405180830381865afa15801561293f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061296391906159ad565b90505f8160200151905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630f53fc4b86836040518363ffffffff1660e01b81526004016129c99291906159d9565b5f604051808303815f87803b1580156129e0575f5ffd5b505af11580156129f2573d5f5f3e3d5ffd5b505050503373ffffffffffffffffffffffffffffffffffffffff167f56d64de549930cfd6e6b5189293e8fdc29c0f675f36e2b435eef17bd777bc24e8888855f015185604051612a459493929190615a46565b60405180910390a260075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015612ab3575f5ffd5b505af1158015612ac5573d5f5f3e3d5ffd5b505050505050505050935093915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612b8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b8190615b07565b60405180910390fd5b5f5f90505b60348160ff161015612c5e5760405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f151581526020016101008152505f8260ff1681548110612c0557612c046142a0565b5b905f5260205f2090600302015f820151815f019081612c249190614b64565b506020820151816001015f6101000a81548160ff021916908315150217905550604082015181600201559050508080600101915050612b8f565b506040518060a0016040528060405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f8152508152506001906005612cdc9291906134c4565b50565b612ce7613510565b6001600580602002604051908101604052809291905f905b82821015612da0578382018054612d15906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612d41906142fa565b8015612d8c5780601f10612d6357610100808354040283529160200191612d8c565b820191905f5260205f20905b815481529060010190602001808311612d6f57829003601f168201915b505050505081526020019060010190612cff565b50505050905090565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6034815114612e37576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e2e90615b6f565b60405180910390fd5b6001600c811115612e4b57612e4a613802565b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015612eb5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed991906143e1565b600c811115612eeb57612eea613802565b5b14612f2b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f2290615bd7565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401612f869190614690565b602060405180830381865afa158015612fa1573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fc59190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613032573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130569190614420565b90508160ff168160ff16146130a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161309790615c3f565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f73928f31daa4d318dd150e598b3ba6a20b7809ac97177fd3871ccbd66314394d846040516130e691906137e2565b60405180910390a25f5f90505b83518160ff16101561320c5773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856858360ff168151811061312f5761312e6142a0565b5b60200260200101515f6040518363ffffffff1660e01b81526004016131559291906148d3565b5f60405180830381865af415801561316f573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906131979190614986565b5f8260ff16815481106131ad576131ac6142a0565b5b905f5260205f2090600302015f820151815f0190816131cc9190614b64565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050808061320490614357565b9150506130f3565b5060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015613273575f5ffd5b505af1158015613285573d5f5f3e3d5ffd5b50505050505050565b5f818154811061329c575f80fd5b905f5260205f2090600302015f91509050805f0180546132bb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546132e7906142fa565b80156133325780601f1061330957610100808354040283529160200191613332565b820191905f5260205f20905b81548152906001019060200180831161331557829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b60018160058110613363575f80fd5b015f915090508054613374906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546133a0906142fa565b80156133eb5780601f106133c2576101008083540402835291602001916133eb565b820191905f5260205f20905b8154815290600101906020018083116133ce57829003601f168201915b505050505081565b6040518060600160405280606081526020015f151581526020015f81525090565b604051806101c001604052805f81526020015f600c81111561343957613438613802565b5b81526020015f81526020015f81526020015f60ff1681526020015f60ff1681526020015f60ff1681526020015f81526020015f81526020015f60ff1681526020015f1515815260200161348a613510565b8152602001606081526020015f81525090565b6040518060e001604052806007905b60608152602001906001900390816134ac5790505090565b82600581019282156134ff579160200282015b828111156134fe5782518290816134ee9190614f2a565b50916020019190600101906134d7565b5b50905061350c9190613537565b5090565b6040518060a001604052806005905b606081526020019060019003908161351f5790505090565b5b80821115613556575f818161354d919061355a565b50600101613538565b5090565b508054613566906142fa565b5f825580601f106135775750613594565b601f0160209004905f5260205f20908101906135939190613597565b5b50565b5b808211156135ae575f815f905550600101613598565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6135d5816135c3565b81146135df575f5ffd5b50565b5f813590506135f0816135cc565b92915050565b5f6020828403121561360b5761360a6135bb565b5b5f613618848285016135e2565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61366382613621565b61366d818561362b565b935061367d81856020860161363b565b61368681613649565b840191505092915050565b5f8115159050919050565b6136a581613691565b82525050565b6136b4816135c3565b82525050565b5f606083015f8301518482035f8601526136d48282613659565b91505060208301516136e9602086018261369c565b5060408301516136fc60408601826136ab565b508091505092915050565b5f6020820190508181035f83015261371f81846136ba565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f61375b8383613659565b905092915050565b5f602082019050919050565b5f61377982613727565b6137838185613731565b93508360208202850161379585613741565b805f5b858110156137d057848403895281516137b18582613750565b94506137bc83613763565b925060208a01995050600181019050613798565b50829750879550505050505092915050565b5f6020820190508181035f8301526137fa818461376f565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d81106138405761383f613802565b5b50565b5f8190506138508261382f565b919050565b5f61385f82613843565b9050919050565b61386f81613855565b82525050565b5f60ff82169050919050565b61388a81613875565b82525050565b5f60059050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b5f6138d1826138ad565b6138db81856138b7565b93506138eb81856020860161363b565b6138f481613649565b840191505092915050565b5f61390a83836138c7565b905092915050565b5f602082019050919050565b5f61392882613890565b613932818561389a565b935083602082028501613944856138a4565b805f5b8581101561397f578484038952815161396085826138ff565b945061396b83613912565b925060208a01995050600181019050613947565b50829750879550505050505092915050565b5f82825260208201905092915050565b5f6139ab82613727565b6139b58185613991565b9350836020820285016139c785613741565b805f5b85811015613a0257848403895281516139e38582613750565b94506139ee83613763565b925060208a019950506001810190506139ca565b50829750879550505050505092915050565b5f6101c083015f830151613a2a5f8601826136ab565b506020830151613a3d6020860182613866565b506040830151613a5060408601826136ab565b506060830151613a6360608601826136ab565b506080830151613a766080860182613881565b5060a0830151613a8960a0860182613881565b5060c0830151613a9c60c0860182613881565b5060e0830151613aaf60e08601826136ab565b50610100830151613ac46101008601826136ab565b50610120830151613ad9610120860182613881565b50610140830151613aee61014086018261369c565b50610160830151848203610160860152613b08828261391e565b915050610180830151848203610180860152613b2482826139a1565b9150506101a0830151613b3b6101a08601826136ab565b508091505092915050565b5f6020820190508181035f830152613b5e8184613a14565b905092915050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613ba082613649565b810181811067ffffffffffffffff82111715613bbf57613bbe613b6a565b5b80604052505050565b5f613bd16135b2565b9050613bdd8282613b97565b919050565b5f67ffffffffffffffff821115613bfc57613bfb613b6a565b5b602082029050602081019050919050565b5f5ffd5b613c1a81613875565b8114613c24575f5ffd5b50565b5f81359050613c3581613c11565b92915050565b5f613c4d613c4884613be2565b613bc8565b90508083825260208201905060208402830185811115613c7057613c6f613c0d565b5b835b81811015613c995780613c858882613c27565b845260208401935050602081019050613c72565b5050509392505050565b5f82601f830112613cb757613cb6613b66565b5b8135613cc7848260208601613c3b565b91505092915050565b5f67ffffffffffffffff821115613cea57613ce9613b6a565b5b602082029050602081019050919050565b5f5ffd5b5f67ffffffffffffffff821115613d1957613d18613b6a565b5b613d2282613649565b9050602081019050919050565b828183375f83830152505050565b5f613d4f613d4a84613cff565b613bc8565b905082815260208101848484011115613d6b57613d6a613cfb565b5b613d76848285613d2f565b509392505050565b5f82601f830112613d9257613d91613b66565b5b8135613da2848260208601613d3d565b91505092915050565b5f613dbd613db884613cd0565b613bc8565b90508083825260208201905060208402830185811115613de057613ddf613c0d565b5b835b81811015613e2757803567ffffffffffffffff811115613e0557613e04613b66565b5b808601613e128982613d7e565b85526020850194505050602081019050613de2565b5050509392505050565b5f82601f830112613e4557613e44613b66565b5b8135613e55848260208601613dab565b91505092915050565b5f5f60408385031215613e7457613e736135bb565b5b5f83013567ffffffffffffffff811115613e9157613e906135bf565b5b613e9d85828601613ca3565b925050602083013567ffffffffffffffff811115613ebe57613ebd6135bf565b5b613eca85828601613e31565b9150509250929050565b5f5f5f60608486031215613eeb57613eea6135bb565b5b5f84013567ffffffffffffffff811115613f0857613f076135bf565b5b613f1486828701613d7e565b935050602084013567ffffffffffffffff811115613f3557613f346135bf565b5b613f4186828701613d7e565b925050604084013567ffffffffffffffff811115613f6257613f616135bf565b5b613f6e86828701613d7e565b9150509250925092565b5f82825260208201905092915050565b5f613f92826138ad565b613f9c8185613f78565b9350613fac81856020860161363b565b613fb581613649565b840191505092915050565b5f6040820190508181035f830152613fd88185613f88565b90508181036020830152613fec8184613f88565b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61403761403261402d84613ff5565b614014565b613ff5565b9050919050565b5f6140488261401d565b9050919050565b5f6140598261403e565b9050919050565b6140698161404f565b82525050565b5f6020820190506140825f830184614060565b92915050565b5f81905092915050565b5f61409c82613890565b6140a68185614088565b9350836020820285016140b8856138a4565b805f5b858110156140f357848403895281516140d485826138ff565b94506140df83613912565b925060208a019950506001810190506140bb565b50829750879550505050505092915050565b5f6020820190508181035f83015261411d8184614092565b905092915050565b5f61412f8261403e565b9050919050565b61413f81614125565b82525050565b5f6020820190506141585f830184614136565b92915050565b5f6141688261403e565b9050919050565b6141788161415e565b82525050565b5f6020820190506141915f83018461416f565b92915050565b5f602082840312156141ac576141ab6135bb565b5b5f82013567ffffffffffffffff8111156141c9576141c86135bf565b5b6141d584828501613e31565b91505092915050565b5f82825260208201905092915050565b5f6141f882613621565b61420281856141de565b935061421281856020860161363b565b61421b81613649565b840191505092915050565b61422f81613691565b82525050565b61423e816135c3565b82525050565b5f6060820190508181035f83015261425c81866141ee565b905061426b6020830185614226565b6142786040830184614235565b949350505050565b5f6020820190508181035f8301526142988184613f88565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061431157607f821691505b602082108103614324576143236142cd565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61436182613875565b915060ff82036143745761437361432a565b5b600182019050919050565b5f8151905061438d816135cc565b92915050565b5f602082840312156143a8576143a76135bb565b5b5f6143b58482850161437f565b91505092915050565b600d81106143ca575f5ffd5b50565b5f815190506143db816143be565b92915050565b5f602082840312156143f6576143f56135bb565b5b5f614403848285016143cd565b91505092915050565b5f8151905061441a81613c11565b92915050565b5f60208284031215614435576144346135bb565b5b5f6144428482850161440c565b91505092915050565b61445481613691565b811461445e575f5ffd5b50565b5f8151905061446f8161444b565b92915050565b5f6020828403121561448a576144896135bb565b5b5f61449784828501614461565b91505092915050565b5f6144b26144ad84613cff565b613bc8565b9050828152602081018484840111156144ce576144cd613cfb565b5b6144d984828561363b565b509392505050565b5f82601f8301126144f5576144f4613b66565b5b81516145058482602086016144a0565b91505092915050565b5f61452061451b84613cd0565b613bc8565b9050808382526020820190506020840283018581111561454357614542613c0d565b5b835b8181101561458a57805167ffffffffffffffff81111561456857614567613b66565b5b80860161457589826144e1565b85526020850194505050602081019050614545565b5050509392505050565b5f82601f8301126145a8576145a7613b66565b5b81516145b884826020860161450e565b91505092915050565b5f602082840312156145d6576145d56135bb565b5b5f82015167ffffffffffffffff8111156145f3576145f26135bf565b5b6145ff84828501614594565b91505092915050565b7f47616d65206973206e6f7420696e20612072657665616c2073746167650000005f82015250565b5f61463c601d83613f78565b915061464782614608565b602082019050919050565b5f6020820190508181035f83015261466981614630565b9050919050565b5f61467a82613ff5565b9050919050565b61468a81614670565b82525050565b5f6020820190506146a35f830184614681565b92915050565b7f4e6f7420796f7572207475726e20746f206465637279707400000000000000005f82015250565b5f6146dd601883613f78565b91506146e8826146a9565b602082019050919050565b5f6020820190508181035f83015261470a816146d1565b9050919050565b7f4d69736d6174636820696e2063617264496e646578657320616e6420646563725f8201527f797074696f6e56616c756573206c656e67746873000000000000000000000000602082015250565b5f61476b603483613f78565b915061477682614711565b604082019050919050565b5f6020820190508181035f8301526147988161475f565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6147d38383613881565b60208301905092915050565b5f602082019050919050565b5f6147f58261479f565b6147ff81856147a9565b935061480a836147b9565b805f5b8381101561483a57815161482188826147c8565b975061482c836147df565b92505060018101905061480d565b5085935050505092915050565b5f6040820190508181035f83015261485f81856147eb565b90508181036020830152614873818461376f565b90509392505050565b5f82825260208201905092915050565b5f61489682613621565b6148a0818561487c565b93506148b081856020860161363b565b6148b981613649565b840191505092915050565b6148cd81613691565b82525050565b5f6040820190508181035f8301526148eb818561488c565b90506148fa60208301846148c4565b9392505050565b5f5ffd5b5f5ffd5b5f6060828403121561491e5761491d614901565b5b6149286060613bc8565b90505f82015167ffffffffffffffff81111561494757614946614905565b5b614953848285016144e1565b5f83015250602061496684828501614461565b602083015250604061497a8482850161437f565b60408301525092915050565b5f6020828403121561499b5761499a6135bb565b5b5f82015167ffffffffffffffff8111156149b8576149b76135bf565b5b6149c484828501614909565b91505092915050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302614a297fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826149ee565b614a3386836149ee565b95508019841693508086168417925050509392505050565b5f614a65614a60614a5b846135c3565b614014565b6135c3565b9050919050565b5f819050919050565b614a7e83614a4b565b614a92614a8a82614a6c565b8484546149fa565b825550505050565b5f5f905090565b614aa9614a9a565b614ab4818484614a75565b505050565b5b81811015614ad757614acc5f82614aa1565b600181019050614aba565b5050565b601f821115614b1c57614aed816149cd565b614af6846149df565b81016020851015614b05578190505b614b19614b11856149df565b830182614ab9565b50505b505050565b5f82821c905092915050565b5f614b3c5f1984600802614b21565b1980831691505092915050565b5f614b548383614b2d565b9150826002028217905092915050565b614b6d82613621565b67ffffffffffffffff811115614b8657614b85613b6a565b5b614b9082546142fa565b614b9b828285614adb565b5f60209050601f831160018114614bcc575f8415614bba578287015190505b614bc48582614b49565b865550614c2b565b601f198416614bda866149cd565b5f5b82811015614c0157848901518255600182019150602085019450602081019050614bdc565b86831015614c1e5784890151614c1a601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b5f602082019050614c465f830184614226565b92915050565b5f8154614c58816142fa565b614c62818661362b565b9450600182165f8114614c7c5760018114614c9257614cc4565b60ff198316865281151560200286019350614cc4565b614c9b856149cd565b5f5b83811015614cbc57815481890152600182019150602081019050614c9d565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f614cf6614cf183614ccd565b614cd8565b9050919050565b5f819050919050565b5f614d18614d1383614ccd565b614cfd565b9050919050565b5f606083015f5f84018583035f870152614d398382614c4c565b92505060018401549050614d4c81614ce4565b614d59602087018261369c565b5060028401549050614d6a81614d06565b614d7760408701826136ab565b50819250505092915050565b5f6020820190508181035f830152614d9b8184614d1f565b905092915050565b5f67ffffffffffffffff821115614dbd57614dbc613b6a565b5b614dc682613649565b9050602081019050919050565b5f614de5614de084614da3565b613bc8565b905082815260208101848484011115614e0157614e00613cfb565b5b614e0c84828561363b565b509392505050565b5f82601f830112614e2857614e27613b66565b5b8151614e38848260208601614dd3565b91505092915050565b5f60208284031215614e5657614e556135bb565b5b5f82015167ffffffffffffffff811115614e7357614e726135bf565b5b614e7f84828501614e14565b91505092915050565b5f6060820190508181035f830152614ea08186613f88565b90508181036020830152614eb48185613f88565b90508181036040830152614ec88184613f88565b9050949350505050565b5f819050815f5260205f209050919050565b601f821115614f2557614ef681614ed2565b614eff846149df565b81016020851015614f0e578190505b614f22614f1a856149df565b830182614ab9565b50505b505050565b614f33826138ad565b67ffffffffffffffff811115614f4c57614f4b613b6a565b5b614f5682546142fa565b614f61828285614ee4565b5f60209050601f831160018114614f92575f8415614f80578287015190505b614f8a8582614b49565b865550614ff1565b601f198416614fa086614ed2565b5f5b82811015614fc757848901518255600182019150602085019450602081019050614fa2565b86831015614fe45784890151614fe0601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b61500281613875565b82525050565b5f60208201905061501b5f830184614ff9565b92915050565b61502a81614670565b8114615034575f5ffd5b50565b5f8151905061504581615021565b92915050565b5f67ffffffffffffffff82111561506557615064613b6a565b5b602082029050919050565b5f61508261507d8461504b565b613bc8565b9050806020840283018581111561509c5761509b613c0d565b5b835b818110156150e357805167ffffffffffffffff8111156150c1576150c0613b66565b5b8086016150ce8982614e14565b8552602085019450505060208101905061509e565b5050509392505050565b5f82601f83011261510157615100613b66565b5b600261510e848285615070565b91505092915050565b5f610180828403121561512d5761512c614901565b5b615138610180613bc8565b90505f61514784828501615037565b5f83015250602061515a8482850161437f565b602083015250604061516e8482850161437f565b60408301525060606151828482850161437f565b606083015250608061519684828501614461565b60808301525060a06151aa84828501614461565b60a08301525060c06151be84828501614461565b60c08301525060e082015167ffffffffffffffff8111156151e2576151e1614905565b5b6151ee848285016150ed565b60e0830152506101006152038482850161440c565b610100830152506101206152198482850161437f565b6101208301525061014061522f84828501614461565b6101408301525061016061524584828501614461565b6101608301525092915050565b5f60208284031215615267576152666135bb565b5b5f82015167ffffffffffffffff811115615284576152836135bf565b5b61529084828501615117565b91505092915050565b7f506c61796572206e6f74206a6f696e656420616674657220726f756e642073745f8201527f6172746564000000000000000000000000000000000000000000000000000000602082015250565b5f6152f3602583613f78565b91506152fe82615299565b604082019050919050565b5f6020820190508181035f830152615320816152e7565b9050919050565b7f506c6179657220666f6c646564000000000000000000000000000000000000005f82015250565b5f61535b600d83613f78565b915061536682615327565b602082019050919050565b5f6020820190508181035f8301526153888161534f565b9050919050565b50565b5f61539d5f83613f78565b91506153a88261538f565b5f82019050919050565b5f6040820190508181035f8301526153ca8184613f88565b905081810360208301526153dd81615392565b905092915050565b7f506c6179657220616c72656164792072657665616c65642063617264732028305f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61543f602f83613f78565b915061544a826153e5565b604082019050919050565b5f6020820190508181035f83015261546c81615433565b9050919050565b7f506c6179657220616c72656164792072657665616c65642063617264732028315f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f6154cd602f83613f78565b91506154d882615473565b604082019050919050565b5f6020820190508181035f8301526154fa816154c1565b9050919050565b5f6060820190508181035f83015261551981866141ee565b9050818103602083015261552d81856141ee565b9050818103604083015261554181846141ee565b9050949350505050565b5f67ffffffffffffffff82111561556557615564613b6a565b5b602082029050919050565b5f61558261557d8461554b565b613bc8565b9050806020840283018581111561559c5761559b613c0d565b5b835b818110156155c557806155b1888261440c565b84526020840193505060208101905061559e565b5050509392505050565b5f82601f8301126155e3576155e2613b66565b5b60026155f0848285615570565b91505092915050565b5f6040828403121561560e5761560d6135bb565b5b5f61561b848285016155cf565b91505092915050565b5f819050919050565b5f61564761564261563d84615624565b614014565b6135c3565b9050919050565b6156578161562d565b82525050565b5f6060820190508181035f830152615675818661488c565b905061568460208301856148c4565b615691604083018461564e565b949350505050565b5f606083015f8301518482035f8601526156b38282613659565b91505060208301516156c8602086018261369c565b5060408301516156db60408601826136ab565b508091505092915050565b5f604083015f8301518482035f8601526157008282615699565b9150506020830151848203602086015261571a8282615699565b9150508091505092915050565b5f6060820190508181035f83015261573f81866156e6565b9050818103602083015261575381856136ba565b9050818103604083015261576781846136ba565b9050949350505050565b5f60079050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b5f6157a482615771565b6157ae818561577b565b9350836020820285016157c085615785565b805f5b858110156157fb57848403895281516157dc85826138ff565b94506157e78361578e565b925060208a019950506001810190506157c3565b50829750879550505050505092915050565b5f6020820190508181035f830152615825818461579a565b905092915050565b600a8110615839575f5ffd5b50565b5f8151905061584a8161582d565b92915050565b5f67ffffffffffffffff82111561586a57615869613b6a565b5b602082029050919050565b5f6040828403121561588a57615889614901565b5b6158946040613bc8565b90505f6158a38482850161440c565b5f8301525060206158b68482850161440c565b60208301525092915050565b5f6158d46158cf84615850565b613bc8565b905080604084028301858111156158ee576158ed613c0d565b5b835b8181101561591757806159038882615875565b8452602084019350506040810190506158f0565b5050509392505050565b5f82601f83011261593557615934613b66565b5b60056159428482856158c2565b91505092915050565b5f610180828403121561596157615960614901565b5b61596b6060613bc8565b90505f61597a8482850161583c565b5f83015250602061598d8482850161437f565b60208301525060406159a184828501615921565b60408301525092915050565b5f61018082840312156159c3576159c26135bb565b5b5f6159d08482850161594b565b91505092915050565b5f6040820190506159ec5f830185614ff9565b6159f96020830184614235565b9392505050565b600a8110615a1157615a10613802565b5b50565b5f819050615a2182615a00565b919050565b5f615a3082615a14565b9050919050565b615a4081615a26565b82525050565b5f6080820190508181035f830152615a5e8187613f88565b90508181036020830152615a728186613f88565b9050615a816040830185615a37565b615a8e6060830184614235565b95945050505050565b7f4f6e6c792074686520726f6f6d20636f6e74726163742063616e2072657365745f8201527f20746865206465636b0000000000000000000000000000000000000000000000602082015250565b5f615af1602983613f78565b9150615afc82615a97565b604082019050919050565b5f6020820190508181035f830152615b1e81615ae5565b9050919050565b7f4d7573742070726f766964652065786163746c792035322063617264730000005f82015250565b5f615b59601d83613f78565b9150615b6482615b25565b602082019050919050565b5f6020820190508181035f830152615b8681615b4d565b9050919050565b7f57726f6e672073746167650000000000000000000000000000000000000000005f82015250565b5f615bc1600b83613f78565b9150615bcc82615b8d565b602082019050919050565b5f6020820190508181035f830152615bee81615bb5565b9050919050565b7f4e6f7420796f7572207475726e20746f2073687566666c6500000000000000005f82015250565b5f615c29601883613f78565b9150615c3482615bf5565b602082019050919050565b5f6020820190508181035f830152615c5681615c1d565b905091905056fea264697066735822122067650a6f6653c75c3c95a6305287e031cf5af14f2246b23333e2b61683cab17d64736f6c634300081d00330000000000000000000000000b377624bd9bfdeb5fa6d0c4621edbd9b2e7c1f90000000000000000000000009b6336c9b008e774c95ae1958e541cf791a54698000000000000000000000000af542de5c211a84330b17a3e25fd073373f593c9
Deployed Bytecode
0x608060405234801561000f575f5ffd5b50600436106100cd575f3560e01c80637243087a1161008a578063a377ac5f11610064578063a377ac5f146101ee578063c27a67581461020c578063c3f8295114610228578063eff02a071461025a576100cd565b80637243087a146101a857806374844e19146101b25780639597684c146101d0576100cd565b806316660082146100d15780631870fd6f146101015780632c242a181461011f578063523aadd41461013d57806362224385146101595780636681d2dd1461018a575b5f5ffd5b6100eb60048036038101906100e691906135f6565b61028a565b6040516100f89190613707565b60405180910390f35b610109610376565b60405161011691906137e2565b60405180910390f35b6101276104c8565b6040516101349190613b46565b60405180910390f35b61015760048036038101906101529190613e5e565b610d1a565b005b610173600480360381019061016e9190613ed4565b6119d5565b604051610181929190613fc0565b60405180910390f35b610192612ad6565b60405161019f919061406f565b60405180910390f35b6101b0612afb565b005b6101ba612cdf565b6040516101c79190614105565b60405180910390f35b6101d8612da9565b6040516101e59190614145565b60405180910390f35b6101f6612dce565b604051610203919061417e565b60405180910390f35b61022660048036038101906102219190614197565b612df3565b005b610242600480360381019061023d91906135f6565b61328e565b60405161025193929190614244565b60405180910390f35b610274600480360381019061026f91906135f6565b613354565b6040516102819190614280565b60405180910390f35b6102926133f3565b5f82815481106102a5576102a46142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546102cb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546102f7906142fa565b80156103425780601f1061031957610100808354040283529160200191610342565b820191905f5260205f20905b81548152906001019060200180831161032557829003601f168201915b50505050508152602001600182015f9054906101000a900460ff161515151581526020016002820154815250509050919050565b60605f5f8054905067ffffffffffffffff81111561039757610396613b6a565b5b6040519080825280602002602001820160405280156103ca57816020015b60608152602001906001900390816103b55790505b5090505f5f90505b5f805490508160ff1610156104c0575f8160ff16815481106103f7576103f66142a0565b5b905f5260205f2090600302015f018054610410906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461043c906142fa565b80156104875780601f1061045e57610100808354040283529160200191610487565b820191905f5260205f20905b81548152906001019060200180831161046a57829003601f168201915b5050505050828260ff16815181106104a2576104a16142a0565b5b602002602001018190525080806104b890614357565b9150506103d2565b508091505090565b6104d0613414565b604051806101c0016040528060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634e2786fb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610546573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105fd91906143e1565b600c81111561060f5761060e613802565b5b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e25501566040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106a29190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370984e976040518163ffffffff1660e01b8152600401602060405180830381865afa158015610711573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107359190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c89190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085e9190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2349ff06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108f49190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634ba2363a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610966573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061098a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663af5b37996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a1d9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166397b2f5566040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab09190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663faff660e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b469190614475565b151581526020016001600580602002604051908101604052809291905f905b82821015610c06578382018054610b7b906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054610ba7906142fa565b8015610bf25780601f10610bc957610100808354040283529160200191610bf2565b820191905f5260205f20905b815481529060010190602001808311610bd557829003601f168201915b505050505081526020019060010190610b65565b5050505081526020013073ffffffffffffffffffffffffffffffffffffffff16631870fd6f6040518163ffffffff1660e01b81526004015f60405180830381865afa158015610c57573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c7f91906145c1565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663eac697c96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cee573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d129190614393565b815250905090565b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da991906143e1565b90506002600c811115610dbf57610dbe613802565b5b81600c811115610dd257610dd1613802565b5b1480610e0257506004600c811115610ded57610dec613802565b5b81600c811115610e0057610dff613802565b5b145b80610e3157506006600c811115610e1c57610e1b613802565b5b81600c811115610e2f57610e2e613802565b5b145b80610e6057506008600c811115610e4b57610e4a613802565b5b81600c811115610e5e57610e5d613802565b5b145b610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e9690614652565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401610efa9190614690565b602060405180830381865afa158015610f15573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f399190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fca9190614420565b90508160ff168160ff1614611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161100b906146f3565b60405180910390fd5b8351855114611058576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104f90614781565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f19c122811f9e4d381304169d982476aabc6977d2467b14da176c96fee621b30386866040516110a0929190614847565b60405180910390a25f5f90505b85518160ff1610156111e35773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856868360ff16815181106110e9576110e86142a0565b5b60200260200101515f6040518363ffffffff1660e01b815260040161110f9291906148d3565b5f60405180830381865af4158015611129573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906111519190614986565b5f878360ff1681518110611168576111676142a0565b5b602002602001015160ff1681548110611184576111836142a0565b5b905f5260205f2090600302015f820151815f0190816111a39190614b64565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505080806111db90614357565b9150506110ad565b505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663df5a065c60016040518263ffffffff1660e01b81526004016112409190614c33565b602060405180830381865afa15801561125b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061127f9190614420565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112eb573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061130f9190614420565b60ff168160ff1603611951576004600c81111561132f5761132e613802565b5b84600c81111561134257611341613802565b5b03611684575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611399576113986142a0565b5b602002602001015160ff16815481106113b5576113b46142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016113dd9190614d83565b5f60405180830381865afa1580156113f7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061141f9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8a600181518110611474576114736142a0565b5b602002602001015160ff16815481106114905761148f6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016114b89190614d83565b5f60405180830381865afa1580156114d2573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114fa9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8b60028151811061154f5761154e6142a0565b5b602002602001015160ff168154811061156b5761156a6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016115939190614d83565b5f60405180830381865afa1580156115ad573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115d59190614e41565b90507f96e672e4b09efde01ec2235824d9eb4bf5003ba7bf63407a45bff0974415696f83838360405161160a93929190614e88565b60405180910390a18260015f60058110611627576116266142a0565b5b0190816116349190614f2a565b50816001806005811061164a576116496142a0565b5b0190816116579190614f2a565b5080600160026005811061166e5761166d6142a0565b5b01908161167b9190614f2a565b50505050611950565b6006600c81111561169857611697613802565b5b84600c8111156116ab576116aa613802565b5b036117eb575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611702576117016142a0565b5b602002602001015160ff168154811061171e5761171d6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016117469190614d83565b5f60405180830381865afa158015611760573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117889190614e41565b90507ff503b0d30585a8adce94d89b991f26d6f675f78304e4b85e7c0d75a1d8cb0bd3816040516117b99190614280565b60405180910390a18060016003600581106117d7576117d66142a0565b5b0190816117e49190614f2a565b505061194f565b6008600c8111156117ff576117fe613802565b5b84600c81111561181257611811613802565b5b0361194e575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611869576118686142a0565b5b602002602001015160ff1681548110611885576118846142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016118ad9190614d83565b5f60405180830381865afa1580156118c7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118ef9190614e41565b90507f17415641f4bcccb6710699f9b6ddf69fac7f253bd194cc81dd9a2ee32c2db0a3816040516119209190614280565b60405180910390a180600160046005811061193e5761193d6142a0565b5b01908161194b9190614f2a565b50505b5b5b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156119b7575f5ffd5b505af11580156119c9573d5f5f3e3d5ffd5b50505050505050505050565b6060805f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611a339190614690565b602060405180830381865afa158015611a4e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a729190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339fddff6836040518263ffffffff1660e01b8152600401611acf9190615008565b5f60405180830381865afa158015611ae9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b119190615252565b905080610140015115611b59576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5090615309565b60405180910390fd5b806080015115611b9e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9590615371565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e001515f60028110611bf257611bf16142a0565b5b60200201516040518263ffffffff1660e01b8152600401611c1391906153b2565b602060405180830381865afa158015611c2e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c529190614475565b611c91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8890615455565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e00151600160028110611ce657611ce56142a0565b5b60200201516040518263ffffffff1660e01b8152600401611d0791906153b2565b602060405180830381865afa158015611d22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d469190614475565b611d85576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d7c906154e3565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fcf675dbb98179665682abaede1b14f82aa2dde6d99e83b602058a320c435f29f888888604051611dcf93929190615501565b60405180910390a25f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339d505fc846040518263ffffffff1660e01b8152600401611e329190615008565b6040805180830381865afa158015611e4c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e7091906155f9565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16895f6101006040518463ffffffff1660e01b8152600401611eb09392919061565d565b5f60405180830381865af4158015611eca573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ef29190614986565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168b5f6101006040518463ffffffff1660e01b8152600401611f329392919061565d565b5f60405180830381865af4158015611f4c573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611f749190614986565b90505f5f845f60028110611f8b57611f8a6142a0565b5b602002015160ff1681548110611fa457611fa36142a0565b5b905f5260205f2090600302016040518060600160405290815f82018054611fca906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054611ff6906142fa565b80156120415780601f1061201857610100808354040283529160200191612041565b820191905f5260205f20905b81548152906001019060200180831161202457829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f5f85600160028110612086576120856142a0565b5b602002015160ff168154811061209f5761209e6142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546120c5906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546120f1906142fa565b801561213c5780601f106121135761010080835404028352916020019161213c565b820191905f5260205f20905b81548152906001019060200180831161211f57829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f60405180604001604052808581526020018481525090505f60405180604001604052808681526020018481525090505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168e5f6101006040518463ffffffff1660e01b81526004016121d99392919061565d565b5f60405180830381865af41580156121f3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061221b9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858a856040518463ffffffff1660e01b815260040161227c93929190615727565b5f604051808303815f875af1158015612297573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906122bf9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858b866040518463ffffffff1660e01b815260040161232093929190615727565b5f604051808303815f875af115801561233b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906123639190614986565b905060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9836040518263ffffffff1660e01b81526004016123bf9190613707565b5f60405180830381865afa1580156123d9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124019190614e41565b9d5060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9826040518263ffffffff1660e01b815260040161245d9190613707565b5f60405180830381865afa158015612477573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061249f9190614e41565b9c5050505050505050505050838160e001515f600281106124c3576124c26142a0565b5b6020020181905250828160e001516001600281106124e4576124e36142a0565b5b60200201819052506124f461349d565b84815f60078110612508576125076142a0565b5b60200201819052508381600160078110612525576125246142a0565b5b602002018190525060015f60058110612541576125406142a0565b5b01805461254d906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612579906142fa565b80156125c45780601f1061259b576101008083540402835291602001916125c4565b820191905f5260205f20905b8154815290600101906020018083116125a757829003601f168201915b5050505050816002600781106125dd576125dc6142a0565b5b6020020181905250600180600581106125f9576125f86142a0565b5b018054612605906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612631906142fa565b801561267c5780601f106126535761010080835404028352916020019161267c565b820191905f5260205f20905b81548152906001019060200180831161265f57829003601f168201915b505050505081600360078110612695576126946142a0565b5b602002018190525060016002600581106126b2576126b16142a0565b5b0180546126be906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546126ea906142fa565b80156127355780601f1061270c57610100808354040283529160200191612735565b820191905f5260205f20905b81548152906001019060200180831161271857829003601f168201915b50505050508160046007811061274e5761274d6142a0565b5b6020020181905250600160036005811061276b5761276a6142a0565b5b018054612777906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546127a3906142fa565b80156127ee5780601f106127c5576101008083540402835291602001916127ee565b820191905f5260205f20905b8154815290600101906020018083116127d157829003601f168201915b505050505081600560078110612807576128066142a0565b5b60200201819052506001600460058110612824576128236142a0565b5b018054612830906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461285c906142fa565b80156128a75780601f1061287e576101008083540402835291602001916128a7565b820191905f5260205f20905b81548152906001019060200180831161288a57829003601f168201915b5050505050816006600781106128c0576128bf6142a0565b5b60200201819052505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166369a684f6836040518263ffffffff1660e01b8152600401612923919061580d565b61018060405180830381865afa15801561293f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061296391906159ad565b90505f8160200151905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630f53fc4b86836040518363ffffffff1660e01b81526004016129c99291906159d9565b5f604051808303815f87803b1580156129e0575f5ffd5b505af11580156129f2573d5f5f3e3d5ffd5b505050503373ffffffffffffffffffffffffffffffffffffffff167f56d64de549930cfd6e6b5189293e8fdc29c0f675f36e2b435eef17bd777bc24e8888855f015185604051612a459493929190615a46565b60405180910390a260075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015612ab3575f5ffd5b505af1158015612ac5573d5f5f3e3d5ffd5b505050505050505050935093915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612b8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b8190615b07565b60405180910390fd5b5f5f90505b60348160ff161015612c5e5760405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f151581526020016101008152505f8260ff1681548110612c0557612c046142a0565b5b905f5260205f2090600302015f820151815f019081612c249190614b64565b506020820151816001015f6101000a81548160ff021916908315150217905550604082015181600201559050508080600101915050612b8f565b506040518060a0016040528060405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f8152508152506001906005612cdc9291906134c4565b50565b612ce7613510565b6001600580602002604051908101604052809291905f905b82821015612da0578382018054612d15906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612d41906142fa565b8015612d8c5780601f10612d6357610100808354040283529160200191612d8c565b820191905f5260205f20905b815481529060010190602001808311612d6f57829003601f168201915b505050505081526020019060010190612cff565b50505050905090565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6034815114612e37576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e2e90615b6f565b60405180910390fd5b6001600c811115612e4b57612e4a613802565b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015612eb5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed991906143e1565b600c811115612eeb57612eea613802565b5b14612f2b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f2290615bd7565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401612f869190614690565b602060405180830381865afa158015612fa1573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fc59190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613032573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130569190614420565b90508160ff168160ff16146130a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161309790615c3f565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f73928f31daa4d318dd150e598b3ba6a20b7809ac97177fd3871ccbd66314394d846040516130e691906137e2565b60405180910390a25f5f90505b83518160ff16101561320c5773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856858360ff168151811061312f5761312e6142a0565b5b60200260200101515f6040518363ffffffff1660e01b81526004016131559291906148d3565b5f60405180830381865af415801561316f573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906131979190614986565b5f8260ff16815481106131ad576131ac6142a0565b5b905f5260205f2090600302015f820151815f0190816131cc9190614b64565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050808061320490614357565b9150506130f3565b5060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015613273575f5ffd5b505af1158015613285573d5f5f3e3d5ffd5b50505050505050565b5f818154811061329c575f80fd5b905f5260205f2090600302015f91509050805f0180546132bb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546132e7906142fa565b80156133325780601f1061330957610100808354040283529160200191613332565b820191905f5260205f20905b81548152906001019060200180831161331557829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b60018160058110613363575f80fd5b015f915090508054613374906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546133a0906142fa565b80156133eb5780601f106133c2576101008083540402835291602001916133eb565b820191905f5260205f20905b8154815290600101906020018083116133ce57829003601f168201915b505050505081565b6040518060600160405280606081526020015f151581526020015f81525090565b604051806101c001604052805f81526020015f600c81111561343957613438613802565b5b81526020015f81526020015f81526020015f60ff1681526020015f60ff1681526020015f60ff1681526020015f81526020015f81526020015f60ff1681526020015f1515815260200161348a613510565b8152602001606081526020015f81525090565b6040518060e001604052806007905b60608152602001906001900390816134ac5790505090565b82600581019282156134ff579160200282015b828111156134fe5782518290816134ee9190614f2a565b50916020019190600101906134d7565b5b50905061350c9190613537565b5090565b6040518060a001604052806005905b606081526020019060019003908161351f5790505090565b5b80821115613556575f818161354d919061355a565b50600101613538565b5090565b508054613566906142fa565b5f825580601f106135775750613594565b601f0160209004905f5260205f20908101906135939190613597565b5b50565b5b808211156135ae575f815f905550600101613598565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6135d5816135c3565b81146135df575f5ffd5b50565b5f813590506135f0816135cc565b92915050565b5f6020828403121561360b5761360a6135bb565b5b5f613618848285016135e2565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61366382613621565b61366d818561362b565b935061367d81856020860161363b565b61368681613649565b840191505092915050565b5f8115159050919050565b6136a581613691565b82525050565b6136b4816135c3565b82525050565b5f606083015f8301518482035f8601526136d48282613659565b91505060208301516136e9602086018261369c565b5060408301516136fc60408601826136ab565b508091505092915050565b5f6020820190508181035f83015261371f81846136ba565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f61375b8383613659565b905092915050565b5f602082019050919050565b5f61377982613727565b6137838185613731565b93508360208202850161379585613741565b805f5b858110156137d057848403895281516137b18582613750565b94506137bc83613763565b925060208a01995050600181019050613798565b50829750879550505050505092915050565b5f6020820190508181035f8301526137fa818461376f565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d81106138405761383f613802565b5b50565b5f8190506138508261382f565b919050565b5f61385f82613843565b9050919050565b61386f81613855565b82525050565b5f60ff82169050919050565b61388a81613875565b82525050565b5f60059050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b5f6138d1826138ad565b6138db81856138b7565b93506138eb81856020860161363b565b6138f481613649565b840191505092915050565b5f61390a83836138c7565b905092915050565b5f602082019050919050565b5f61392882613890565b613932818561389a565b935083602082028501613944856138a4565b805f5b8581101561397f578484038952815161396085826138ff565b945061396b83613912565b925060208a01995050600181019050613947565b50829750879550505050505092915050565b5f82825260208201905092915050565b5f6139ab82613727565b6139b58185613991565b9350836020820285016139c785613741565b805f5b85811015613a0257848403895281516139e38582613750565b94506139ee83613763565b925060208a019950506001810190506139ca565b50829750879550505050505092915050565b5f6101c083015f830151613a2a5f8601826136ab565b506020830151613a3d6020860182613866565b506040830151613a5060408601826136ab565b506060830151613a6360608601826136ab565b506080830151613a766080860182613881565b5060a0830151613a8960a0860182613881565b5060c0830151613a9c60c0860182613881565b5060e0830151613aaf60e08601826136ab565b50610100830151613ac46101008601826136ab565b50610120830151613ad9610120860182613881565b50610140830151613aee61014086018261369c565b50610160830151848203610160860152613b08828261391e565b915050610180830151848203610180860152613b2482826139a1565b9150506101a0830151613b3b6101a08601826136ab565b508091505092915050565b5f6020820190508181035f830152613b5e8184613a14565b905092915050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613ba082613649565b810181811067ffffffffffffffff82111715613bbf57613bbe613b6a565b5b80604052505050565b5f613bd16135b2565b9050613bdd8282613b97565b919050565b5f67ffffffffffffffff821115613bfc57613bfb613b6a565b5b602082029050602081019050919050565b5f5ffd5b613c1a81613875565b8114613c24575f5ffd5b50565b5f81359050613c3581613c11565b92915050565b5f613c4d613c4884613be2565b613bc8565b90508083825260208201905060208402830185811115613c7057613c6f613c0d565b5b835b81811015613c995780613c858882613c27565b845260208401935050602081019050613c72565b5050509392505050565b5f82601f830112613cb757613cb6613b66565b5b8135613cc7848260208601613c3b565b91505092915050565b5f67ffffffffffffffff821115613cea57613ce9613b6a565b5b602082029050602081019050919050565b5f5ffd5b5f67ffffffffffffffff821115613d1957613d18613b6a565b5b613d2282613649565b9050602081019050919050565b828183375f83830152505050565b5f613d4f613d4a84613cff565b613bc8565b905082815260208101848484011115613d6b57613d6a613cfb565b5b613d76848285613d2f565b509392505050565b5f82601f830112613d9257613d91613b66565b5b8135613da2848260208601613d3d565b91505092915050565b5f613dbd613db884613cd0565b613bc8565b90508083825260208201905060208402830185811115613de057613ddf613c0d565b5b835b81811015613e2757803567ffffffffffffffff811115613e0557613e04613b66565b5b808601613e128982613d7e565b85526020850194505050602081019050613de2565b5050509392505050565b5f82601f830112613e4557613e44613b66565b5b8135613e55848260208601613dab565b91505092915050565b5f5f60408385031215613e7457613e736135bb565b5b5f83013567ffffffffffffffff811115613e9157613e906135bf565b5b613e9d85828601613ca3565b925050602083013567ffffffffffffffff811115613ebe57613ebd6135bf565b5b613eca85828601613e31565b9150509250929050565b5f5f5f60608486031215613eeb57613eea6135bb565b5b5f84013567ffffffffffffffff811115613f0857613f076135bf565b5b613f1486828701613d7e565b935050602084013567ffffffffffffffff811115613f3557613f346135bf565b5b613f4186828701613d7e565b925050604084013567ffffffffffffffff811115613f6257613f616135bf565b5b613f6e86828701613d7e565b9150509250925092565b5f82825260208201905092915050565b5f613f92826138ad565b613f9c8185613f78565b9350613fac81856020860161363b565b613fb581613649565b840191505092915050565b5f6040820190508181035f830152613fd88185613f88565b90508181036020830152613fec8184613f88565b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61403761403261402d84613ff5565b614014565b613ff5565b9050919050565b5f6140488261401d565b9050919050565b5f6140598261403e565b9050919050565b6140698161404f565b82525050565b5f6020820190506140825f830184614060565b92915050565b5f81905092915050565b5f61409c82613890565b6140a68185614088565b9350836020820285016140b8856138a4565b805f5b858110156140f357848403895281516140d485826138ff565b94506140df83613912565b925060208a019950506001810190506140bb565b50829750879550505050505092915050565b5f6020820190508181035f83015261411d8184614092565b905092915050565b5f61412f8261403e565b9050919050565b61413f81614125565b82525050565b5f6020820190506141585f830184614136565b92915050565b5f6141688261403e565b9050919050565b6141788161415e565b82525050565b5f6020820190506141915f83018461416f565b92915050565b5f602082840312156141ac576141ab6135bb565b5b5f82013567ffffffffffffffff8111156141c9576141c86135bf565b5b6141d584828501613e31565b91505092915050565b5f82825260208201905092915050565b5f6141f882613621565b61420281856141de565b935061421281856020860161363b565b61421b81613649565b840191505092915050565b61422f81613691565b82525050565b61423e816135c3565b82525050565b5f6060820190508181035f83015261425c81866141ee565b905061426b6020830185614226565b6142786040830184614235565b949350505050565b5f6020820190508181035f8301526142988184613f88565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061431157607f821691505b602082108103614324576143236142cd565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61436182613875565b915060ff82036143745761437361432a565b5b600182019050919050565b5f8151905061438d816135cc565b92915050565b5f602082840312156143a8576143a76135bb565b5b5f6143b58482850161437f565b91505092915050565b600d81106143ca575f5ffd5b50565b5f815190506143db816143be565b92915050565b5f602082840312156143f6576143f56135bb565b5b5f614403848285016143cd565b91505092915050565b5f8151905061441a81613c11565b92915050565b5f60208284031215614435576144346135bb565b5b5f6144428482850161440c565b91505092915050565b61445481613691565b811461445e575f5ffd5b50565b5f8151905061446f8161444b565b92915050565b5f6020828403121561448a576144896135bb565b5b5f61449784828501614461565b91505092915050565b5f6144b26144ad84613cff565b613bc8565b9050828152602081018484840111156144ce576144cd613cfb565b5b6144d984828561363b565b509392505050565b5f82601f8301126144f5576144f4613b66565b5b81516145058482602086016144a0565b91505092915050565b5f61452061451b84613cd0565b613bc8565b9050808382526020820190506020840283018581111561454357614542613c0d565b5b835b8181101561458a57805167ffffffffffffffff81111561456857614567613b66565b5b80860161457589826144e1565b85526020850194505050602081019050614545565b5050509392505050565b5f82601f8301126145a8576145a7613b66565b5b81516145b884826020860161450e565b91505092915050565b5f602082840312156145d6576145d56135bb565b5b5f82015167ffffffffffffffff8111156145f3576145f26135bf565b5b6145ff84828501614594565b91505092915050565b7f47616d65206973206e6f7420696e20612072657665616c2073746167650000005f82015250565b5f61463c601d83613f78565b915061464782614608565b602082019050919050565b5f6020820190508181035f83015261466981614630565b9050919050565b5f61467a82613ff5565b9050919050565b61468a81614670565b82525050565b5f6020820190506146a35f830184614681565b92915050565b7f4e6f7420796f7572207475726e20746f206465637279707400000000000000005f82015250565b5f6146dd601883613f78565b91506146e8826146a9565b602082019050919050565b5f6020820190508181035f83015261470a816146d1565b9050919050565b7f4d69736d6174636820696e2063617264496e646578657320616e6420646563725f8201527f797074696f6e56616c756573206c656e67746873000000000000000000000000602082015250565b5f61476b603483613f78565b915061477682614711565b604082019050919050565b5f6020820190508181035f8301526147988161475f565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6147d38383613881565b60208301905092915050565b5f602082019050919050565b5f6147f58261479f565b6147ff81856147a9565b935061480a836147b9565b805f5b8381101561483a57815161482188826147c8565b975061482c836147df565b92505060018101905061480d565b5085935050505092915050565b5f6040820190508181035f83015261485f81856147eb565b90508181036020830152614873818461376f565b90509392505050565b5f82825260208201905092915050565b5f61489682613621565b6148a0818561487c565b93506148b081856020860161363b565b6148b981613649565b840191505092915050565b6148cd81613691565b82525050565b5f6040820190508181035f8301526148eb818561488c565b90506148fa60208301846148c4565b9392505050565b5f5ffd5b5f5ffd5b5f6060828403121561491e5761491d614901565b5b6149286060613bc8565b90505f82015167ffffffffffffffff81111561494757614946614905565b5b614953848285016144e1565b5f83015250602061496684828501614461565b602083015250604061497a8482850161437f565b60408301525092915050565b5f6020828403121561499b5761499a6135bb565b5b5f82015167ffffffffffffffff8111156149b8576149b76135bf565b5b6149c484828501614909565b91505092915050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302614a297fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826149ee565b614a3386836149ee565b95508019841693508086168417925050509392505050565b5f614a65614a60614a5b846135c3565b614014565b6135c3565b9050919050565b5f819050919050565b614a7e83614a4b565b614a92614a8a82614a6c565b8484546149fa565b825550505050565b5f5f905090565b614aa9614a9a565b614ab4818484614a75565b505050565b5b81811015614ad757614acc5f82614aa1565b600181019050614aba565b5050565b601f821115614b1c57614aed816149cd565b614af6846149df565b81016020851015614b05578190505b614b19614b11856149df565b830182614ab9565b50505b505050565b5f82821c905092915050565b5f614b3c5f1984600802614b21565b1980831691505092915050565b5f614b548383614b2d565b9150826002028217905092915050565b614b6d82613621565b67ffffffffffffffff811115614b8657614b85613b6a565b5b614b9082546142fa565b614b9b828285614adb565b5f60209050601f831160018114614bcc575f8415614bba578287015190505b614bc48582614b49565b865550614c2b565b601f198416614bda866149cd565b5f5b82811015614c0157848901518255600182019150602085019450602081019050614bdc565b86831015614c1e5784890151614c1a601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b5f602082019050614c465f830184614226565b92915050565b5f8154614c58816142fa565b614c62818661362b565b9450600182165f8114614c7c5760018114614c9257614cc4565b60ff198316865281151560200286019350614cc4565b614c9b856149cd565b5f5b83811015614cbc57815481890152600182019150602081019050614c9d565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f614cf6614cf183614ccd565b614cd8565b9050919050565b5f819050919050565b5f614d18614d1383614ccd565b614cfd565b9050919050565b5f606083015f5f84018583035f870152614d398382614c4c565b92505060018401549050614d4c81614ce4565b614d59602087018261369c565b5060028401549050614d6a81614d06565b614d7760408701826136ab565b50819250505092915050565b5f6020820190508181035f830152614d9b8184614d1f565b905092915050565b5f67ffffffffffffffff821115614dbd57614dbc613b6a565b5b614dc682613649565b9050602081019050919050565b5f614de5614de084614da3565b613bc8565b905082815260208101848484011115614e0157614e00613cfb565b5b614e0c84828561363b565b509392505050565b5f82601f830112614e2857614e27613b66565b5b8151614e38848260208601614dd3565b91505092915050565b5f60208284031215614e5657614e556135bb565b5b5f82015167ffffffffffffffff811115614e7357614e726135bf565b5b614e7f84828501614e14565b91505092915050565b5f6060820190508181035f830152614ea08186613f88565b90508181036020830152614eb48185613f88565b90508181036040830152614ec88184613f88565b9050949350505050565b5f819050815f5260205f209050919050565b601f821115614f2557614ef681614ed2565b614eff846149df565b81016020851015614f0e578190505b614f22614f1a856149df565b830182614ab9565b50505b505050565b614f33826138ad565b67ffffffffffffffff811115614f4c57614f4b613b6a565b5b614f5682546142fa565b614f61828285614ee4565b5f60209050601f831160018114614f92575f8415614f80578287015190505b614f8a8582614b49565b865550614ff1565b601f198416614fa086614ed2565b5f5b82811015614fc757848901518255600182019150602085019450602081019050614fa2565b86831015614fe45784890151614fe0601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b61500281613875565b82525050565b5f60208201905061501b5f830184614ff9565b92915050565b61502a81614670565b8114615034575f5ffd5b50565b5f8151905061504581615021565b92915050565b5f67ffffffffffffffff82111561506557615064613b6a565b5b602082029050919050565b5f61508261507d8461504b565b613bc8565b9050806020840283018581111561509c5761509b613c0d565b5b835b818110156150e357805167ffffffffffffffff8111156150c1576150c0613b66565b5b8086016150ce8982614e14565b8552602085019450505060208101905061509e565b5050509392505050565b5f82601f83011261510157615100613b66565b5b600261510e848285615070565b91505092915050565b5f610180828403121561512d5761512c614901565b5b615138610180613bc8565b90505f61514784828501615037565b5f83015250602061515a8482850161437f565b602083015250604061516e8482850161437f565b60408301525060606151828482850161437f565b606083015250608061519684828501614461565b60808301525060a06151aa84828501614461565b60a08301525060c06151be84828501614461565b60c08301525060e082015167ffffffffffffffff8111156151e2576151e1614905565b5b6151ee848285016150ed565b60e0830152506101006152038482850161440c565b610100830152506101206152198482850161437f565b6101208301525061014061522f84828501614461565b6101408301525061016061524584828501614461565b6101608301525092915050565b5f60208284031215615267576152666135bb565b5b5f82015167ffffffffffffffff811115615284576152836135bf565b5b61529084828501615117565b91505092915050565b7f506c61796572206e6f74206a6f696e656420616674657220726f756e642073745f8201527f6172746564000000000000000000000000000000000000000000000000000000602082015250565b5f6152f3602583613f78565b91506152fe82615299565b604082019050919050565b5f6020820190508181035f830152615320816152e7565b9050919050565b7f506c6179657220666f6c646564000000000000000000000000000000000000005f82015250565b5f61535b600d83613f78565b915061536682615327565b602082019050919050565b5f6020820190508181035f8301526153888161534f565b9050919050565b50565b5f61539d5f83613f78565b91506153a88261538f565b5f82019050919050565b5f6040820190508181035f8301526153ca8184613f88565b905081810360208301526153dd81615392565b905092915050565b7f506c6179657220616c72656164792072657665616c65642063617264732028305f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61543f602f83613f78565b915061544a826153e5565b604082019050919050565b5f6020820190508181035f83015261546c81615433565b9050919050565b7f506c6179657220616c72656164792072657665616c65642063617264732028315f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f6154cd602f83613f78565b91506154d882615473565b604082019050919050565b5f6020820190508181035f8301526154fa816154c1565b9050919050565b5f6060820190508181035f83015261551981866141ee565b9050818103602083015261552d81856141ee565b9050818103604083015261554181846141ee565b9050949350505050565b5f67ffffffffffffffff82111561556557615564613b6a565b5b602082029050919050565b5f61558261557d8461554b565b613bc8565b9050806020840283018581111561559c5761559b613c0d565b5b835b818110156155c557806155b1888261440c565b84526020840193505060208101905061559e565b5050509392505050565b5f82601f8301126155e3576155e2613b66565b5b60026155f0848285615570565b91505092915050565b5f6040828403121561560e5761560d6135bb565b5b5f61561b848285016155cf565b91505092915050565b5f819050919050565b5f61564761564261563d84615624565b614014565b6135c3565b9050919050565b6156578161562d565b82525050565b5f6060820190508181035f830152615675818661488c565b905061568460208301856148c4565b615691604083018461564e565b949350505050565b5f606083015f8301518482035f8601526156b38282613659565b91505060208301516156c8602086018261369c565b5060408301516156db60408601826136ab565b508091505092915050565b5f604083015f8301518482035f8601526157008282615699565b9150506020830151848203602086015261571a8282615699565b9150508091505092915050565b5f6060820190508181035f83015261573f81866156e6565b9050818103602083015261575381856136ba565b9050818103604083015261576781846136ba565b9050949350505050565b5f60079050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b5f6157a482615771565b6157ae818561577b565b9350836020820285016157c085615785565b805f5b858110156157fb57848403895281516157dc85826138ff565b94506157e78361578e565b925060208a019950506001810190506157c3565b50829750879550505050505092915050565b5f6020820190508181035f830152615825818461579a565b905092915050565b600a8110615839575f5ffd5b50565b5f8151905061584a8161582d565b92915050565b5f67ffffffffffffffff82111561586a57615869613b6a565b5b602082029050919050565b5f6040828403121561588a57615889614901565b5b6158946040613bc8565b90505f6158a38482850161440c565b5f8301525060206158b68482850161440c565b60208301525092915050565b5f6158d46158cf84615850565b613bc8565b905080604084028301858111156158ee576158ed613c0d565b5b835b8181101561591757806159038882615875565b8452602084019350506040810190506158f0565b5050509392505050565b5f82601f83011261593557615934613b66565b5b60056159428482856158c2565b91505092915050565b5f610180828403121561596157615960614901565b5b61596b6060613bc8565b90505f61597a8482850161583c565b5f83015250602061598d8482850161437f565b60208301525060406159a184828501615921565b60408301525092915050565b5f61018082840312156159c3576159c26135bb565b5b5f6159d08482850161594b565b91505092915050565b5f6040820190506159ec5f830185614ff9565b6159f96020830184614235565b9392505050565b600a8110615a1157615a10613802565b5b50565b5f819050615a2182615a00565b919050565b5f615a3082615a14565b9050919050565b615a4081615a26565b82525050565b5f6080820190508181035f830152615a5e8187613f88565b90508181036020830152615a728186613f88565b9050615a816040830185615a37565b615a8e6060830184614235565b95945050505050565b7f4f6e6c792074686520726f6f6d20636f6e74726163742063616e2072657365745f8201527f20746865206465636b0000000000000000000000000000000000000000000000602082015250565b5f615af1602983613f78565b9150615afc82615a97565b604082019050919050565b5f6020820190508181035f830152615b1e81615ae5565b9050919050565b7f4d7573742070726f766964652065786163746c792035322063617264730000005f82015250565b5f615b59601d83613f78565b9150615b6482615b25565b602082019050919050565b5f6020820190508181035f830152615b8681615b4d565b9050919050565b7f57726f6e672073746167650000000000000000000000000000000000000000005f82015250565b5f615bc1600b83613f78565b9150615bcc82615b8d565b602082019050919050565b5f6020820190508181035f830152615bee81615bb5565b9050919050565b7f4e6f7420796f7572207475726e20746f2073687566666c6500000000000000005f82015250565b5f615c29601883613f78565b9150615c3482615bf5565b602082019050919050565b5f6020820190508181035f830152615c5681615c1d565b905091905056fea264697066735822122067650a6f6653c75c3c95a6305287e031cf5af14f2246b23333e2b61683cab17d64736f6c634300081d0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000000b377624bd9bfdeb5fa6d0c4621edbd9b2e7c1f90000000000000000000000009b6336c9b008e774c95ae1958e541cf791a54698000000000000000000000000af542de5c211a84330b17a3e25fd073373f593c9
-----Decoded View---------------
Arg [0] : _texasHoldemRoom (address): 0x0B377624bd9BFDeB5fA6d0C4621EdBD9B2E7C1F9
Arg [1] : _cryptoUtils (address): 0x9B6336C9B008E774c95AE1958e541Cf791A54698
Arg [2] : _handEvaluator (address): 0xAF542De5c211a84330b17A3E25fD073373F593C9
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000000b377624bd9bfdeb5fa6d0c4621edbd9b2e7c1f9
Arg [1] : 0000000000000000000000009b6336c9b008e774c95ae1958e541cf791a54698
Arg [2] : 000000000000000000000000af542de5c211a84330b17a3e25fd073373f593c9
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
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.