Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
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 Source Code Verified (Exact Match)
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, 256);
}
texasHoldemRoom.progressGame();
}
// TODO(high): verify that the card indicies are valid for the number of players and stage
function submitDecryptionValues(uint8[] memory cardIndexes, bytes[] memory decryptionValues)
external
{
TexasHoldemRoom.GameStage stage = texasHoldemRoom.stage();
require(
stage == TexasHoldemRoom.GameStage.RevealDeal
|| stage == TexasHoldemRoom.GameStage.RevealFlop
|| stage == TexasHoldemRoom.GameStage.RevealTurn
|| stage == TexasHoldemRoom.GameStage.RevealRiver,
"Game is not in a reveal stage"
);
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
require(currentPlayerIndex == playerIndex, "Not your turn to decrypt");
require(
cardIndexes.length == decryptionValues.length,
"Mismatch in cardIndexes and decryptionValues lengths"
);
// TODO: verify decryption values?
// TODO: verify decryption indexes?
emit DecryptionValuesSubmitted(msg.sender, cardIndexes, decryptionValues);
for (uint8 i = 0; i < cardIndexes.length; i++) {
encryptedDeck[cardIndexes[i]] = BigNumbers.init(decryptionValues[i], false, 256);
}
// The dealer always starts decrypting, so when we are back at the dealer,
// we know the last player has decrypted community cards, so emit/set the community cards
uint8 nextRevealPlayer = texasHoldemRoom.getNextActivePlayer(true);
if (nextRevealPlayer == texasHoldemRoom.dealerPosition()) {
if (stage == TexasHoldemRoom.GameStage.RevealFlop) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
string memory card2 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[1]]);
string memory card3 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[2]]);
emit FlopRevealed(card1, card2, card3);
communityCards[0] = card1;
communityCards[1] = card2;
communityCards[2] = card3;
} else if (stage == TexasHoldemRoom.GameStage.RevealTurn) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
emit TurnRevealed(card1);
communityCards[3] = card1;
} else if (stage == TexasHoldemRoom.GameStage.RevealRiver) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
emit RiverRevealed(card1);
communityCards[4] = card1;
}
}
texasHoldemRoom.progressGame();
}
/**
* @dev Reveals the player's cards.
* @dev Many todos: validate the encrypted cards, the card indexes for the player are valid (getCardIndexForPlayer in js),
* @dev and that it is appropriate (showdown/all-in) to reveal cards.
* @param c1 The player's encryption key c1 (derived from the private key)
* @param privateKey The player's private key
* @param c1InversePowPrivateKey The inverse of the player's c1*privateKey modulo inverse for decryption
*/
function revealMyCards(
bytes memory c1,
bytes memory privateKey,
bytes memory c1InversePowPrivateKey
) external returns (string memory card1, string memory card2) {
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
TexasHoldemRoom.Player memory player = texasHoldemRoom.getPlayer(playerIndex);
// todo: uncomment this
require(!player.joinedAndWaitingForNextRound, "Player not joined after round started");
require(!player.hasFolded, "Player folded");
require(
cryptoUtils.strEq(player.cards[0], ""),
"Player already revealed cards (0) in this round"
);
require(
cryptoUtils.strEq(player.cards[1], ""),
"Player already revealed cards (1) in this round"
);
emit PlayerRevealingCards(msg.sender, c1, privateKey, c1InversePowPrivateKey);
// scope block to reduce number of variables in memory (evm stack depth limited to 16 variables)
{
uint8[2] memory playerCardIndexes = texasHoldemRoom.getPlayersCardIndexes(playerIndex);
// BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 2048);
// BigNumber memory privateKeyBN = BigNumber({ val: privateKey, neg: false, bitlen: 256 });
// BigNumber memory c1BN = BigNumber({ val: c1, neg: false, bitlen: 256 });
BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 256);
BigNumber memory c1BN = BigNumbers.init(c1, false, 256);
BigNumber memory encryptedCard1BN = encryptedDeck[playerCardIndexes[0]];
BigNumber memory encryptedCard2BN = encryptedDeck[playerCardIndexes[1]];
CryptoUtils.EncryptedCard memory encryptedCard1Struct =
CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard1BN });
CryptoUtils.EncryptedCard memory encryptedCard2Struct =
CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard2BN });
BigNumber memory c1InversePowPrivateKeyBN =
BigNumbers.init(c1InversePowPrivateKey, false, 256);
BigNumber memory decryptedCard1 = cryptoUtils.verifyDecryptCard(
encryptedCard1Struct, privateKeyBN, c1InversePowPrivateKeyBN
);
BigNumber memory decryptedCard2 = cryptoUtils.verifyDecryptCard(
encryptedCard2Struct, privateKeyBN, c1InversePowPrivateKeyBN
);
// convert the decrypted cards to a string
card1 = cryptoUtils.decodeBigintMessage(decryptedCard1);
card2 = cryptoUtils.decodeBigintMessage(decryptedCard2);
}
player.cards[0] = card1;
player.cards[1] = card2;
// Get the player's hand score (using player's cards and community cards) from HandEvaluator
// Combine player cards and community cards into a single array
string[7] memory allCards;
allCards[0] = card1;
allCards[1] = card2;
allCards[2] = communityCards[0];
allCards[3] = communityCards[1];
allCards[4] = communityCards[2];
allCards[5] = communityCards[3];
allCards[6] = communityCards[4];
PokerHandEvaluatorv2.Hand memory playerHand = handEvaluator.findBestHandExternal(allCards);
uint256 playerHandScore = playerHand.score;
texasHoldemRoom.setPlayerHandScore(playerIndex, playerHandScore);
emit PlayerCardsRevealed(
address(msg.sender), card1, card2, playerHand.rank, playerHandScore
);
// emit THP_Log("emit PlayerCardsRevealed() in revealMyCards()");
// return true if the encrypted cards match the decrypted cards from the deck?
texasHoldemRoom.progressGame();
// emit THP_Log("after texasHoldemRoom.progressGame()");
return (card1, card2);
}
function getEncryptedDeck() external view returns (bytes[] memory) {
bytes[] memory deckBytes = new bytes[](encryptedDeck.length);
for (uint8 i = 0; i < encryptedDeck.length; i++) {
deckBytes[i] = encryptedDeck[i].val;
}
return deckBytes;
}
function getEncrypedCard(uint256 cardIndex) external view returns (BigNumber memory) {
return encryptedDeck[cardIndex];
}
function getCommunityCards() external view returns (string[5] memory) {
return communityCards;
}
/**
* @dev Returns all simple public variables of the TexasHoldemRoom contract and the encrypted deck
* To reduce the size of the TexasHoldemRoom contract, this function is put here.
*/
struct BulkRoomData {
uint256 roundNumber;
TexasHoldemRoom.GameStage stage;
uint256 smallBlind;
uint256 bigBlind;
uint8 dealerPosition;
uint8 currentPlayerIndex;
uint8 lastRaiseIndex;
uint256 pot;
uint256 currentStageBet;
uint8 numPlayers;
bool isPrivate;
string[5] communityCards;
bytes[] encryptedDeck;
uint256 lastActionTimestamp;
}
function getBulkRoomData() external view returns (BulkRoomData memory) {
return BulkRoomData({
roundNumber: texasHoldemRoom.roundNumber(),
stage: texasHoldemRoom.stage(),
smallBlind: texasHoldemRoom.smallBlind(),
bigBlind: texasHoldemRoom.bigBlind(),
dealerPosition: texasHoldemRoom.dealerPosition(),
currentPlayerIndex: texasHoldemRoom.currentPlayerIndex(),
lastRaiseIndex: texasHoldemRoom.lastRaiseIndex(),
pot: texasHoldemRoom.pot(),
currentStageBet: texasHoldemRoom.currentStageBet(),
numPlayers: texasHoldemRoom.numPlayers(),
isPrivate: texasHoldemRoom.isPrivate(),
encryptedDeck: this.getEncryptedDeck(),
communityCards: communityCards,
lastActionTimestamp: texasHoldemRoom.lastActionTimestamp()
});
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
// 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
// todo: FIX! the currentPlayerIndex is not changed in showdown. kick the first active player that hasn't
// revealed their cards?
uint8 playerIndexToKick = EMPTY_SEAT;
if (stage == GameStage.Showdown) {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].joinedAndWaitingForNextRound
&& !players[i].hasFolded && players[i].handScore == 0
) {
playerIndexToKick = i;
break;
}
}
require(playerIndexToKick != EMPTY_SEAT, "All players have revealed their cards");
} else {
playerIndexToKick = currentPlayerIndex;
}
emit IdlePlayerKicked(msg.sender, players[playerIndexToKick].addr, timeElapsed);
// todo: split the kicked player's chips between the other active players
players[playerIndexToKick].leavingAfterRoundEnds = true;
// same logic as: this.resetRound();
// returns chips to players and starts a new round
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
/**
* @dev This function should be called after EVERY valid player action. It contains
* @dev logic to update common state like lastActionTimestamp, currentPlayerIndex, and stage.
* @dev If in a reveal stage, all players need to submit their decryption values
* @dev If in a betting stage, only the players who are not all-in
* @dev and not folded need to submit their actions
* @notice Emits a NewStage event and new current player index event
*/
function _progressGame() internal {
// if showdown and countRevealed < countActive, do NOT update timestamp
if (stage == GameStage.Showdown && countOfHandsRevealed() < countActivePlayers()) {
// do not update lastActionTimestamp
// all players have 30 seconds to reveal their cards in showdown
} else {
lastActionTimestamp = block.timestamp;
}
if (stage == GameStage.Idle) {
// mark all players waiting for the next round as not joined
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].joinedAndWaitingForNextRound) {
players[i].joinedAndWaitingForNextRound = false;
}
}
return _moveToNextStage();
}
// shuffle or reveal stage
if (
stage == GameStage.Shuffle || stage == GameStage.RevealDeal
|| stage == GameStage.RevealFlop || stage == GameStage.RevealTurn
|| stage == GameStage.RevealRiver
) {
// emit THP_Log("_progressGame() if Reveal stage true");
// if the next reveal player is back at the dealer, move to the next stage
// since the dealer starts all reveal stages
bool requireActive = false;
uint8 nextRevealPlayer = getNextActivePlayer(requireActive);
if (nextRevealPlayer == dealerPosition) {
// emit THP_Log("_progressGame() if nextRevealPlayer == dealerPosition");
// after a reveal stage, we enter a betting stage
// always the first active player LEFT of the dealer starts all betting stages
// After shuffle, we are still in a reveal stage
if (stage == GameStage.Shuffle) {
currentPlayerIndex = dealerPosition;
} else {
bool requireActiveForBetting = true;
currentPlayerIndex = dealerPosition;
uint8 nextActivePlayer = getNextActivePlayer(requireActiveForBetting);
currentPlayerIndex = nextActivePlayer;
// at the start of a betting stage, the last raise index is the first active player by default
lastRaiseIndex = nextActivePlayer;
}
return _moveToNextStage();
} else {
// otherwise, the next player should submit their decryption values
currentPlayerIndex = nextRevealPlayer;
}
} else if (stage == GameStage.Showdown) {
if (countOfHandsRevealed() == countActivePlayers()) {
// find the winners and split the pot
determineWinners();
// Should start a new round
_startNewHand(); // moves game to idle/shuffling stage
}
// do nothing while the rest of the players reveal their cards
return;
} else {
// current in a betting stage
// if there are no more active players, the round ends and the last
// active player wins the pot
// emit THP_Log("_progressGame() running if _countActivePlayers() == 1");
if (countActivePlayers() == 1) {
// todo: split the pot
// emit THP_Log("_progressGame() in if _countActivePlayers() == 1");
determineWinners();
// emit THP_Log("_progressGame() in if _countActivePlayers() == 1 after det win");
_startNewHand();
// emit THP_Log(
// "_progressGame() in if _countActivePlayers() == 1 after start new hand"
// );
return;
}
// if the last raise index is the same as the next active player index,
// the betting stage is complete.
// Reset the round bet amounts and move to the next stage
bool requireActive = true;
uint8 nextPlayer = getNextActivePlayer(requireActive);
// Check if betting round is complete
if (nextPlayer == lastRaiseIndex) {
// emit THP_Log("_progressGame() if nextPlayer == lastRaiseIndex");
// Reset betting for a new betting stage
// do not reset players' total round bets here
currentStageBet = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].currentStageBet = 0;
players[i].hasChecked = false;
}
// if a betting stage ends, the next player should be the dealer
// to prepare for the next reveal stage
currentPlayerIndex = dealerPosition;
// For a reveal stage, this isn't used, however, it should be reset at the start
// of the next betting stage
// lastRaiseIndex = currentPlayerIndex;
return _moveToNextStage();
} else {
// in the middle of a betting stage with an active player left to act
currentPlayerIndex = nextPlayer;
}
// TODO: handle a new round of poker after showdown or only 1 active players
}
}
function _moveToNextStage() internal {
stage = GameStage(uint8(stage) + 1);
emit NewStage(stage);
}
// TODO: do we include all-in players in the count?
// TODO: don't count players who joined the game after the round started
function countActivePlayers() public view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].hasFolded
&& !players[i].joinedAndWaitingForNextRound
) {
count++;
}
}
return count;
}
function setPlayerHandScore(uint8 playerIndex, uint256 handScore) external {
require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
players[playerIndex].handScore = handScore;
}
function getPlayers() external view returns (Player[] memory) {
Player[] memory playersArray = new Player[](MAX_PLAYERS);
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
playersArray[i] = players[i];
}
return playersArray;
}
function getPlayer(uint8 playerIndex) external view returns (Player memory) {
return players[playerIndex];
}
function countPlayersAtRoundStart() external view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr != address(0) && players[i].joinedAndWaitingForNextRound == false) {
count++;
}
}
return count;
}
function getPlayersCardIndexes(uint8 playerIndex)
external
view
returns (uint8[2] memory playerCardIndexes)
{
// emit THP_Log("_progressGame() in getPlayersCardIndexes()");
uint8 countOfPlayersCounterClockwiseToDealer = 0;
uint8 playerSeatPosition = players[playerIndex].seatPosition; // 0
uint8 dealerSeatPosition = players[dealerPosition].seatPosition; // 1
while (playerSeatPosition != dealerSeatPosition) {
// 0 != 1
playerSeatPosition = (playerSeatPosition + (MAX_PLAYERS - 1)) % MAX_PLAYERS; // = (0 - 1 + 10) % 10 = 9
if (
seatPositionToPlayerIndex[playerSeatPosition] != EMPTY_SEAT
&& !players[seatPositionToPlayerIndex[playerSeatPosition]].joinedAndWaitingForNextRound
) {
countOfPlayersCounterClockwiseToDealer++;
}
// emit THP_Log("_progressGame() in getPlayersCardIndexes() in while loop");
}
// emit THP_Log("_progressGame() in getPlayersCardIndexes() after while loop");
uint8 playersAtRoundStart = this.countPlayersAtRoundStart();
playerCardIndexes[0] = countOfPlayersCounterClockwiseToDealer;
playerCardIndexes[1] = countOfPlayersCounterClockwiseToDealer + playersAtRoundStart;
return playerCardIndexes;
}
/**
* @dev Returns the number of hands revealed by the players this round by
* checking if the player's hand score is greater than 0
* @return The number of hands revealed
*/
function countOfHandsRevealed() public view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
// This is set to 0 after each round,
// so players with a score have just revealed their cards
// This should by default exclude null players and players who joined the game after the round started
if (players[i].handScore > 0) {
// emit THP_Log(
// "_progressGame() in countOfHandsRevealed() if players[i].handScore > 0"
// );
count++;
}
}
return count;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
contract PokerHandEvaluatorv2 {
event PHE_Log(string message);
enum HandRank {
HighCard, // 0
Pair, // 1
TwoPair, // 2
ThreeOfAKind, // 3
Straight, // 4
Flush, // 5
FullHouse, // 6
FourOfAKind, // 7
StraightFlush, // 8
RoyalFlush // 9
}
struct Card {
uint8 rank; // 2-14 (14 = AceHigh)
uint8 suit; // 0-3 (Hearts, Diamonds, Clubs, Spades)
}
struct Hand {
HandRank rank;
uint256 score;
Card[5] bestHand;
}
function uintToString(uint8 value) public pure returns (string memory) {
// Special case for 0
if (value == 0) {
return "0";
}
// Find length of number by counting digits
uint8 length = 0;
uint8 temp = value;
while (temp != 0) {
length++;
temp /= 10;
}
// Create bytes array of the right length
bytes memory buffer = new bytes(length);
// Fill buffer from right to left
uint8 i = length;
while (value != 0) {
buffer[--i] = bytes1(uint8(48 + value % 10));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a card string representation (0-51) to a Card struct
* @param cardStr The string representation of the card (0-51)
* @return A Card struct with the appropriate rank and suit
*
* Card mapping:
* - Hearts: 0-12 (2-A)
* - Diamonds: 13-25 (2-A)
* - Clubs: 26-38 (2-A)
* - Spades: 39-51 (2-A)
*/
function stringToCard(string memory cardStr) public pure returns (Card memory) {
require(
bytes(cardStr).length == 1 || bytes(cardStr).length == 2, "Invalid card string length"
);
uint8 cardNum = uint8(parseInt(cardStr));
require(cardNum < 52, "Invalid card number");
uint8 suit = cardNum / 13;
uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
return Card({ rank: rank, suit: suit });
}
/**
* @dev Converts a card string representation (0-51) to a string
* @param cardStr The string representation of the card (0-51)
* @return A string with the appropriate rank and suit
*
* Card mapping:
* - Hearts: 0-12 (2-A)
* - Diamonds: 13-25 (2-A)
* - Clubs: 26-38 (2-A)
* - Spades: 39-51 (2-A)
*/
function stringToHumanReadable(string memory cardStr) public pure returns (string memory) {
uint8 cardNum = uint8(parseInt(cardStr));
require(cardNum < 52, "Invalid card number");
uint8 suit = cardNum / 13;
uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
string memory suitStr;
if (suit <= 0) {
suitStr = "H";
} else if (suit <= 1) {
suitStr = "D";
} else if (suit <= 2) {
suitStr = "C";
} else {
suitStr = "S";
}
string memory rankStr;
if (rank <= 10) {
rankStr = uintToString(rank);
} else if (rank == 11) {
rankStr = "J";
} else if (rank == 12) {
rankStr = "Q";
} else if (rank == 13) {
rankStr = "K";
} else {
rankStr = "A";
}
return string.concat(rankStr, suitStr);
}
/**
* @dev Converts a human readable card string to a Card struct
* @dev Example: "2H" -> Card(2, 0)
* @dev Example: "AH" -> Card(14, 0)
* @dev Example: "10D" -> Card(21, 1)
* @param cardStr The human readable card string
* @return A Card struct with the appropriate rank and suit
*/
function humanReadableToCard(string memory cardStr) public pure returns (Card memory) {
bytes memory cardBytes = bytes(cardStr);
string memory rankStr;
string memory suitStr = new string(1);
if (cardBytes.length > 2 && cardBytes[0] == "1" && cardBytes[1] == "0") {
// Handle "10" as a special case
rankStr = "10";
bytes(suitStr)[0] = cardBytes[2];
} else {
// For all other cards, just take the first character
bytes(suitStr)[0] = cardBytes[cardBytes.length - 1];
rankStr = new string(1);
bytes(rankStr)[0] = cardBytes[0];
// if rank is J, Q, K, A
if (cardBytes[0] == "J") {
rankStr = "11";
} else if (cardBytes[0] == "Q") {
rankStr = "12";
} else if (cardBytes[0] == "K") {
rankStr = "13";
} else if (cardBytes[0] == "A") {
rankStr = "14";
}
}
uint8 rank = uint8(parseInt(rankStr));
uint8 suit = 0;
if (strEq(suitStr, "H")) {
suit = 0;
} else if (strEq(suitStr, "D")) {
suit = 1;
} else if (strEq(suitStr, "C")) {
suit = 2;
} else if (strEq(suitStr, "S")) {
suit = 3;
}
return Card({ rank: rank, suit: suit });
}
/**
* @dev Helper function to parse a string to an integer
* @param s The string to parse
* @return The parsed integer value
*/
function parseInt(string memory s) public pure returns (uint256) {
bytes memory b = bytes(s);
uint256 result = 0;
for (uint256 i = 0; i < b.length; i++) {
uint8 c = uint8(b[i]);
if (c >= 48 && c <= 57) {
result = result * 10 + (c - 48);
}
}
return result;
}
function evaluateHand(Card[2] memory holeCards, Card[5] memory communityCards)
public
pure
returns (Hand memory)
{
Card[7] memory allCards;
allCards[0] = holeCards[0];
allCards[1] = holeCards[1];
for (uint256 i = 0; i < 5; i++) {
allCards[i + 2] = communityCards[i];
}
return findBestHand(allCards);
}
function findBestHandExternal(string[7] memory cards) public pure returns (Hand memory) {
Card[7] memory cardArray;
for (uint256 i = 0; i < 7; i++) {
cardArray[i] = stringToCard(cards[i]);
}
return findBestHand(cardArray);
}
// For test cases to use human readable strings
function findBestHandExternal2(string[7] memory cards) public pure returns (Hand memory) {
Card[7] memory cardArray;
for (uint256 i = 0; i < 7; i++) {
cardArray[i] = humanReadableToCard(cards[i]);
}
return findBestHand(cardArray);
}
// TODO(medium): have this return the indicies for the best hand (5 card indicies)
function findBestHand(Card[7] memory cards) internal pure returns (Hand memory) {
// Sort cards by rank (ascending order)
// Example: [2♥, 3♠, 5♦, 8♣, 10♥, J♦, A♠]
// Cards are sorted from lowest to highest rank
for (uint256 i = 0; i < cards.length - 1; i++) {
for (uint256 j = 0; j < cards.length - i - 1; j++) {
if (cards[j].rank > cards[j + 1].rank) {
Card memory temp = cards[j];
cards[j] = cards[j + 1];
cards[j + 1] = temp;
}
}
}
// emit PHE_Log("after card creation loop ");
// Check for each hand type from highest to lowest
Hand memory bestHand;
uint256 score = 0;
uint8 rank = 0;
// Check Royal Flush
(rank, score) = hasRoyalFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.RoyalFlush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after royal flush ");
// Check Straight Flush
(rank, score) = hasStraightFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.StraightFlush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after straight flush ");
// Check Four of a Kind
(rank, score) = hasFourOfAKind(cards);
if (score > 0) {
bestHand.rank = HandRank.FourOfAKind;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after four of a kind ");
// Check Full House
(rank, score) = hasFullHouse(cards);
if (score > 0) {
bestHand.rank = HandRank.FullHouse;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after full house ");
// Check Flush
(rank, score) = hasFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.Flush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after flush ");
// Check Straight
(rank, score) = hasStraight(cards);
if (score > 0) {
bestHand.rank = HandRank.Straight;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after straight ");
// Check Three of a Kind
(rank, score) = hasThreeOfAKind(cards);
if (score > 0) {
bestHand.rank = HandRank.ThreeOfAKind;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after three of a kind ");
// Check Two Pair
(rank, score) = hasTwoPair(cards);
if (score > 0) {
bestHand.rank = HandRank.TwoPair;
bestHand.score = score;
// emit PHE_Log("has two pair! before return ");
return bestHand;
}
// emit PHE_Log("after two pair ");
// Check Pair
(rank, score) = hasPair(cards);
if (score > 0) {
bestHand.rank = HandRank.Pair;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after pair ");
// Default to high card if no other hand is found
(rank, score) = hasHighCard(cards);
bestHand.rank = HandRank.HighCard;
bestHand.score = score;
// emit PHE_Log("after high card ");
return bestHand;
}
function hasRoyalFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint8 suit = 0; suit < 4; suit++) {
bool hasAce = false;
bool hasKing = false;
bool hasQueen = false;
bool hasJack = false;
bool hasTen = false;
for (uint8 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
if (cards[i].rank == 14) hasAce = true;
if (cards[i].rank == 13) hasKing = true;
if (cards[i].rank == 12) hasQueen = true;
if (cards[i].rank == 11) hasJack = true;
if (cards[i].rank == 10) hasTen = true;
}
}
if (hasAce && hasKing && hasQueen && hasJack && hasTen) {
return (10, 10 * 10 ** 14);
}
}
return (0, 0);
}
function hasStraightFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint8 suit = 0; suit < 4; suit++) {
uint8[] memory suitCards = new uint8[](7);
uint8 suitCount = 0;
for (uint256 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
suitCards[suitCount] = cards[i].rank;
suitCount++;
}
}
if (suitCount >= 5) {
// Sort suit cards
for (uint8 i = 0; i < suitCount - 1; i++) {
for (uint8 j = 0; j < suitCount - i - 1; j++) {
if (suitCards[j] > suitCards[j + 1]) {
uint8 temp = suitCards[j];
suitCards[j] = suitCards[j + 1];
suitCards[j + 1] = temp;
}
}
}
// Check for straight in suited cards
for (uint8 i = 0; i <= suitCount - 5; i++) {
if (suitCards[i + 4] == suitCards[i] + 4) {
return (9, 9 * 10 ** 14 + suitCards[i + 4]);
}
}
}
}
return (0, 0);
}
function hasFourOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint256 i = 0; i < 7; i++) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count == 4) {
// Find highest kicker
uint8 kicker = 0;
for (uint256 k = 0; k < 7; k++) {
if (cards[k].rank != rank && cards[k].rank > kicker) {
kicker = cards[k].rank;
}
}
return (8, 8 * 10 ** 14 + uint256(rank) * 10 ** 2 + uint256(kicker));
}
}
return (0, 0);
}
function hasFullHouse(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8 threeOfAKindRank = 0;
uint8 pairRank = 0;
// Find three of a kind
for (uint256 i = 0; i < 7; i++) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count >= 3 && rank > threeOfAKindRank) {
threeOfAKindRank = rank;
}
}
if (threeOfAKindRank == 0) {
return (0, 0);
}
// Find pair (different from three of a kind)
for (uint256 i = 0; i < 7; i++) {
if (cards[i].rank != threeOfAKindRank) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count >= 2 && rank > pairRank) {
pairRank = rank;
}
}
}
if (pairRank > 0) {
return (7, 7 * 10 ** 14 + uint256(threeOfAKindRank) * 10 ** 2 + uint256(pairRank));
}
return (0, 0);
}
function hasFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[5] memory flushCards;
for (uint8 suit = 0; suit < 4; suit++) {
uint8[] memory suitCards = new uint8[](7);
uint8 suitCount = 0;
for (uint8 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
suitCards[suitCount] = cards[i].rank;
suitCount++;
}
}
if (suitCount >= 5) {
// Sort suit cards in descending order
for (uint8 i = 0; i < suitCount - 1; i++) {
for (uint8 j = 0; j < suitCount - i - 1; j++) {
if (suitCards[j] < suitCards[j + 1]) {
uint8 temp = suitCards[j];
suitCards[j] = suitCards[j + 1];
suitCards[j + 1] = temp;
}
}
}
// Take the highest 5 cards
for (uint8 i = 0; i < 5; i++) {
flushCards[i] = suitCards[i];
}
// Calculate score with proper ordering (highest to lowest)
uint256 score = 6 * 10 ** 14;
score += uint256(flushCards[0]) * 10 ** 8;
score += uint256(flushCards[1]) * 10 ** 6;
score += uint256(flushCards[2]) * 10 ** 4;
score += uint256(flushCards[3]) * 10 ** 2;
score += uint256(flushCards[4]);
return (6, score);
}
}
return (0, 0);
}
function hasStraight(Card[7] memory cards) internal pure returns (uint8, uint256) {
// Remove duplicates
uint8[] memory uniqueRanks = new uint8[](15); // Max 14 ranks + 1 for Ace as 1
uint8 uniqueCount = 0;
for (uint8 i = 0; i < 7; i++) {
bool isDuplicate = false;
for (uint8 j = 0; j < uniqueCount; j++) {
if (uniqueRanks[j] == cards[i].rank) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
uniqueRanks[uniqueCount] = cards[i].rank;
uniqueCount++;
}
}
// Add Ace as 1 if Ace exists
bool hasAce = false;
for (uint8 i = 0; i < uniqueCount; i++) {
if (uniqueRanks[i] == 14) {
hasAce = true;
break;
}
}
if (hasAce) {
uniqueRanks[uniqueCount] = 1;
uniqueCount++;
}
// Sort ranks
for (uint8 i = 0; i < uniqueCount - 1; i++) {
for (uint8 j = 0; j < uniqueCount - i - 1; j++) {
if (uniqueRanks[j] > uniqueRanks[j + 1]) {
uint8 temp = uniqueRanks[j];
uniqueRanks[j] = uniqueRanks[j + 1];
uniqueRanks[j + 1] = temp;
}
}
}
// Check for straight
if (uniqueCount >= 5) {
for (uint8 i = 0; i <= uniqueCount - 5; i++) {
if (uniqueRanks[i + 4] == uniqueRanks[i] + 4) {
return (5, 5 * 10 ** 14 + uint256(uniqueRanks[i + 4]));
}
}
}
return (0, 0);
}
function hasThreeOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[2] memory kickers;
for (uint256 i = 0; i <= 4; i++) {
uint8 count = 1;
uint8 rank = cards[i].rank;
for (uint256 j = i + 1; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count == 3) {
// Find two highest kickers
uint8 kickerCount = 0;
// Start from the highest card and work down
for (int256 j = 6; j >= 0 && kickerCount < 2; j--) {
if (cards[uint256(j)].rank != rank) {
kickers[kickerCount] = cards[uint256(j)].rank;
kickerCount++;
}
}
// Make sure we have enough kickers before using them
// Calculate score with safeguards against overflow
uint256 score = 4 * 10 ** 14; // Reduced exponent to prevent overflow
score += uint256(rank) * 10 ** 6;
if (kickerCount >= 2) {
score += uint256(kickers[0]) * 10 ** 4 + uint256(kickers[1]);
} else if (kickerCount == 1) {
score += uint256(kickers[0]) * 10 ** 2;
}
return (4, score);
}
}
return (0, 0);
}
function hasTwoPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8 highPairRank = 0;
uint8 lowPairRank = 0;
// emit PHE_Log(uintToString(cards[6].rank));
// emit PHE_Log(uintToString(cards[5].rank));
// emit PHE_Log(uintToString(cards[4].rank));
// emit PHE_Log(uintToString(cards[3].rank));
// emit PHE_Log(uintToString(cards[2].rank));
// emit PHE_Log(uintToString(cards[1].rank));
// emit PHE_Log(uintToString(cards[0].rank));
// Find pairs
for (uint256 i = 6; i > 0; i--) {
if (cards[i].rank == cards[i - 1].rank) {
if (highPairRank == 0) {
highPairRank = cards[i].rank;
// if (i == 1) {
// break;
// }
// underflow if we subtract 1 from i twice when i == 1
if (i > 1) {
i--; // Skip the second card of the pair
}
} else {
lowPairRank = cards[i].rank;
break;
}
}
}
if (highPairRank > 0 && lowPairRank > 0) {
// Find highest kicker
uint8 kicker = 0;
for (int256 i = 6; i >= 0; i--) {
if (cards[uint256(i)].rank != highPairRank && cards[uint256(i)].rank != lowPairRank)
{
kicker = cards[uint256(i)].rank;
break;
}
}
// Reduced exponent to prevent overflow
uint256 score = 3 * 10 ** 14;
score += uint256(highPairRank) * 10 ** 6;
score += uint256(lowPairRank) * 10 ** 4;
score += uint256(kicker);
return (3, score);
}
return (0, 0);
}
function hasPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[3] memory kickers;
uint8 pairRank = 0;
// Find pair
for (uint256 i = 6; i > 0; i--) {
if (cards[i].rank == cards[i - 1].rank) {
pairRank = cards[i].rank;
break;
}
}
if (pairRank > 0) {
// Find three highest kickers
uint8 kickerCount = 0;
for (int256 i = 6; i >= 0 && kickerCount < 3; i--) {
if (cards[uint256(i)].rank != pairRank) {
kickers[kickerCount] = cards[uint256(i)].rank;
kickerCount++;
}
}
return (
2,
2 * 10 ** 14 + uint256(pairRank) * 10 ** 6 + uint256(kickers[2]) * 10 ** 4
+ uint256(kickers[1]) * 10 ** 2 + uint256(kickers[0])
);
}
return (0, 0);
}
function hasHighCard(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint256 score = 1 * 10 ** 14;
// Cards are already sorted by rank, so we take the 5 highest cards
// Starting from the highest card (index 6) down to the 5th highest (index 2)
score += uint256(cards[6].rank) * 10 ** 6;
score += uint256(cards[5].rank) * 10 ** 4;
score += uint256(cards[4].rank) * 10 ** 3;
score += uint256(cards[3].rank) * 10 ** 2;
score += uint256(cards[2].rank);
return (1, score);
}
// string equality check
function strEq(string memory a, string memory b) public pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}{
"remappings": [
"forge-std/=lib/forge-std/src/"
],
"optimizer": {
"enabled": false,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false,
"libraries": {
"src/BigNumbers/BigNumbers.sol": {
"BigNumbers": "0xF2635f00300F16D9acA57F955091Cc24DD01F7d1"
}
}
}Contract ABI
API[{"inputs":[{"internalType":"address","name":"_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
608060405234801561000f575f5ffd5b506040516162033803806162038339818101604052810190610031919061022d565b8260075f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160065f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f5f90505b60348110156101c6575f60405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f15158152602001610100815250908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f01908161018d91906104ba565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155505080806001019150506100f6565b50505050610589565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101fc826101d3565b9050919050565b61020c816101f2565b8114610216575f5ffd5b50565b5f8151905061022781610203565b92915050565b5f5f5f60608486031215610244576102436101cf565b5b5f61025186828701610219565b935050602061026286828701610219565b925050604061027386828701610219565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806102f857607f821691505b60208210810361030b5761030a6102b4565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261036d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610332565b6103778683610332565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6103bb6103b66103b18461038f565b610398565b61038f565b9050919050565b5f819050919050565b6103d4836103a1565b6103e86103e0826103c2565b84845461033e565b825550505050565b5f5f905090565b6103ff6103f0565b61040a8184846103cb565b505050565b5b8181101561042d576104225f826103f7565b600181019050610410565b5050565b601f8211156104725761044381610311565b61044c84610323565b8101602085101561045b578190505b61046f61046785610323565b83018261040f565b50505b505050565b5f82821c905092915050565b5f6104925f1984600802610477565b1980831691505092915050565b5f6104aa8383610483565b9150826002028217905092915050565b6104c38261027d565b67ffffffffffffffff8111156104dc576104db610287565b5b6104e682546102e1565b6104f1828285610431565b5f60209050601f831160018114610522575f8415610510578287015190505b61051a858261049f565b865550610581565b601f19841661053086610311565b5f5b8281101561055757848901518255600182019150602085019450602081019050610532565b868310156105745784890151610570601f891682610483565b8355505b6001600288020188555050505b505050505050565b615c6d806105965f395ff3fe608060405234801561000f575f5ffd5b50600436106100cd575f3560e01c80637243087a1161008a578063a377ac5f11610064578063a377ac5f146101ee578063c27a67581461020c578063c3f8295114610228578063eff02a071461025a576100cd565b80637243087a146101a857806374844e19146101b25780639597684c146101d0576100cd565b806316660082146100d15780631870fd6f146101015780632c242a181461011f578063523aadd41461013d57806362224385146101595780636681d2dd1461018a575b5f5ffd5b6100eb60048036038101906100e691906135fe565b61028a565b6040516100f8919061370f565b60405180910390f35b610109610376565b60405161011691906137ea565b60405180910390f35b6101276104c8565b6040516101349190613b4e565b60405180910390f35b61015760048036038101906101529190613e66565b610d1a565b005b610173600480360381019061016e9190613edc565b6119d9565b604051610181929190613fc8565b60405180910390f35b610192612ada565b60405161019f9190614077565b60405180910390f35b6101b0612aff565b005b6101ba612ce3565b6040516101c7919061410d565b60405180910390f35b6101d8612dad565b6040516101e5919061414d565b60405180910390f35b6101f6612dd2565b6040516102039190614186565b60405180910390f35b6102266004803603810190610221919061419f565b612df7565b005b610242600480360381019061023d91906135fe565b613296565b6040516102519392919061424c565b60405180910390f35b610274600480360381019061026f91906135fe565b61335c565b6040516102819190614288565b60405180910390f35b6102926133fb565b5f82815481106102a5576102a46142a8565b5b905f5260205f2090600302016040518060600160405290815f820180546102cb90614302565b80601f01602080910402602001604051908101604052809291908181526020018280546102f790614302565b80156103425780601f1061031957610100808354040283529160200191610342565b820191905f5260205f20905b81548152906001019060200180831161032557829003601f168201915b50505050508152602001600182015f9054906101000a900460ff161515151581526020016002820154815250509050919050565b60605f5f8054905067ffffffffffffffff81111561039757610396613b72565b5b6040519080825280602002602001820160405280156103ca57816020015b60608152602001906001900390816103b55790505b5090505f5f90505b5f805490508160ff1610156104c0575f8160ff16815481106103f7576103f66142a8565b5b905f5260205f2090600302015f01805461041090614302565b80601f016020809104026020016040519081016040528092919081815260200182805461043c90614302565b80156104875780601f1061045e57610100808354040283529160200191610487565b820191905f5260205f20905b81548152906001019060200180831161046a57829003601f168201915b5050505050828260ff16815181106104a2576104a16142a8565b5b602002602001018190525080806104b89061435f565b9150506103d2565b508091505090565b6104d061341c565b604051806101c0016040528060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634e2786fb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610546573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056a919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105fd91906143e9565b600c81111561060f5761060e61380a565b5b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e25501566040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106a2919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370984e976040518163ffffffff1660e01b8152600401602060405180830381865afa158015610711573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610735919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c89190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085e9190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2349ff06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108f49190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634ba2363a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610966573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061098a919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663af5b37996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a1d919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166397b2f5566040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab09190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663faff660e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b46919061447d565b151581526020016001600580602002604051908101604052809291905f905b82821015610c06578382018054610b7b90614302565b80601f0160208091040260200160405190810160405280929190818152602001828054610ba790614302565b8015610bf25780601f10610bc957610100808354040283529160200191610bf2565b820191905f5260205f20905b815481529060010190602001808311610bd557829003601f168201915b505050505081526020019060010190610b65565b5050505081526020013073ffffffffffffffffffffffffffffffffffffffff16631870fd6f6040518163ffffffff1660e01b81526004015f60405180830381865afa158015610c57573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c7f91906145c9565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663eac697c96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cee573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d12919061439b565b815250905090565b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da991906143e9565b90506002600c811115610dbf57610dbe61380a565b5b81600c811115610dd257610dd161380a565b5b1480610e0257506004600c811115610ded57610dec61380a565b5b81600c811115610e0057610dff61380a565b5b145b80610e3157506006600c811115610e1c57610e1b61380a565b5b81600c811115610e2f57610e2e61380a565b5b145b80610e6057506008600c811115610e4b57610e4a61380a565b5b81600c811115610e5e57610e5d61380a565b5b145b610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e969061465a565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401610efa9190614698565b602060405180830381865afa158015610f15573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f399190614428565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fca9190614428565b90508160ff168160ff1614611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161100b906146fb565b60405180910390fd5b8351855114611058576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104f90614789565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f19c122811f9e4d381304169d982476aabc6977d2467b14da176c96fee621b30386866040516110a092919061484f565b60405180910390a25f5f90505b85518160ff1610156111e75773f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16868360ff16815181106110e9576110e86142a8565b5b60200260200101515f6101006040518463ffffffff1660e01b815260040161111393929190614914565b5f60405180830381865af415801561112d573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061115591906149d5565b5f878360ff168151811061116c5761116b6142a8565b5b602002602001015160ff1681548110611188576111876142a8565b5b905f5260205f2090600302015f820151815f0190816111a79190614bb3565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505080806111df9061435f565b9150506110ad565b505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663df5a065c60016040518263ffffffff1660e01b81526004016112449190614c82565b602060405180830381865afa15801561125f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112839190614428565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112ef573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113139190614428565b60ff168160ff1603611955576004600c8111156113335761133261380a565b5b84600c8111156113465761134561380a565b5b03611688575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f8151811061139d5761139c6142a8565b5b602002602001015160ff16815481106113b9576113b86142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016113e19190614dd2565b5f60405180830381865afa1580156113fb573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114239190614e90565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8a600181518110611478576114776142a8565b5b602002602001015160ff1681548110611494576114936142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016114bc9190614dd2565b5f60405180830381865afa1580156114d6573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114fe9190614e90565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8b600281518110611553576115526142a8565b5b602002602001015160ff168154811061156f5761156e6142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016115979190614dd2565b5f60405180830381865afa1580156115b1573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115d99190614e90565b90507f96e672e4b09efde01ec2235824d9eb4bf5003ba7bf63407a45bff0974415696f83838360405161160e93929190614ed7565b60405180910390a18260015f6005811061162b5761162a6142a8565b5b0190816116389190614f79565b50816001806005811061164e5761164d6142a8565b5b01908161165b9190614f79565b50806001600260058110611672576116716142a8565b5b01908161167f9190614f79565b50505050611954565b6006600c81111561169c5761169b61380a565b5b84600c8111156116af576116ae61380a565b5b036117ef575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611706576117056142a8565b5b602002602001015160ff1681548110611722576117216142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b815260040161174a9190614dd2565b5f60405180830381865afa158015611764573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061178c9190614e90565b90507ff503b0d30585a8adce94d89b991f26d6f675f78304e4b85e7c0d75a1d8cb0bd3816040516117bd9190614288565b60405180910390a18060016003600581106117db576117da6142a8565b5b0190816117e89190614f79565b5050611953565b6008600c8111156118035761180261380a565b5b84600c8111156118165761181561380a565b5b03611952575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f8151811061186d5761186c6142a8565b5b602002602001015160ff1681548110611889576118886142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016118b19190614dd2565b5f60405180830381865afa1580156118cb573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118f39190614e90565b90507f17415641f4bcccb6710699f9b6ddf69fac7f253bd194cc81dd9a2ee32c2db0a3816040516119249190614288565b60405180910390a1806001600460058110611942576119416142a8565b5b01908161194f9190614f79565b50505b5b5b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156119bb575f5ffd5b505af11580156119cd573d5f5f3e3d5ffd5b50505050505050505050565b6060805f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611a379190614698565b602060405180830381865afa158015611a52573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a769190614428565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339fddff6836040518263ffffffff1660e01b8152600401611ad39190615057565b5f60405180830381865afa158015611aed573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b1591906152a1565b905080610140015115611b5d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5490615358565b60405180910390fd5b806080015115611ba2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b99906153c0565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e001515f60028110611bf657611bf56142a8565b5b60200201516040518263ffffffff1660e01b8152600401611c179190615401565b602060405180830381865afa158015611c32573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c56919061447d565b611c95576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8c906154a4565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e00151600160028110611cea57611ce96142a8565b5b60200201516040518263ffffffff1660e01b8152600401611d0b9190615401565b602060405180830381865afa158015611d26573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d4a919061447d565b611d89576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d8090615532565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fcf675dbb98179665682abaede1b14f82aa2dde6d99e83b602058a320c435f29f888888604051611dd393929190615550565b60405180910390a25f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339d505fc846040518263ffffffff1660e01b8152600401611e369190615057565b6040805180830381865afa158015611e50573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e749190615648565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16895f6101006040518463ffffffff1660e01b8152600401611eb493929190614914565b5f60405180830381865af4158015611ece573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ef691906149d5565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168b5f6101006040518463ffffffff1660e01b8152600401611f3693929190614914565b5f60405180830381865af4158015611f50573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611f7891906149d5565b90505f5f845f60028110611f8f57611f8e6142a8565b5b602002015160ff1681548110611fa857611fa76142a8565b5b905f5260205f2090600302016040518060600160405290815f82018054611fce90614302565b80601f0160208091040260200160405190810160405280929190818152602001828054611ffa90614302565b80156120455780601f1061201c57610100808354040283529160200191612045565b820191905f5260205f20905b81548152906001019060200180831161202857829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f5f8560016002811061208a576120896142a8565b5b602002015160ff16815481106120a3576120a26142a8565b5b905f5260205f2090600302016040518060600160405290815f820180546120c990614302565b80601f01602080910402602001604051908101604052809291908181526020018280546120f590614302565b80156121405780601f1061211757610100808354040283529160200191612140565b820191905f5260205f20905b81548152906001019060200180831161212357829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f60405180604001604052808581526020018481525090505f60405180604001604052808681526020018481525090505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168e5f6101006040518463ffffffff1660e01b81526004016121dd93929190614914565b5f60405180830381865af41580156121f7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061221f91906149d5565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858a856040518463ffffffff1660e01b815260040161228093929190615701565b5f604051808303815f875af115801561229b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906122c391906149d5565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858b866040518463ffffffff1660e01b815260040161232493929190615701565b5f604051808303815f875af115801561233f573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061236791906149d5565b905060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9836040518263ffffffff1660e01b81526004016123c3919061370f565b5f60405180830381865afa1580156123dd573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124059190614e90565b9d5060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9826040518263ffffffff1660e01b8152600401612461919061370f565b5f60405180830381865afa15801561247b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124a39190614e90565b9c5050505050505050505050838160e001515f600281106124c7576124c66142a8565b5b6020020181905250828160e001516001600281106124e8576124e76142a8565b5b60200201819052506124f86134a5565b84815f6007811061250c5761250b6142a8565b5b60200201819052508381600160078110612529576125286142a8565b5b602002018190525060015f60058110612545576125446142a8565b5b01805461255190614302565b80601f016020809104026020016040519081016040528092919081815260200182805461257d90614302565b80156125c85780601f1061259f576101008083540402835291602001916125c8565b820191905f5260205f20905b8154815290600101906020018083116125ab57829003601f168201915b5050505050816002600781106125e1576125e06142a8565b5b6020020181905250600180600581106125fd576125fc6142a8565b5b01805461260990614302565b80601f016020809104026020016040519081016040528092919081815260200182805461263590614302565b80156126805780601f1061265757610100808354040283529160200191612680565b820191905f5260205f20905b81548152906001019060200180831161266357829003601f168201915b505050505081600360078110612699576126986142a8565b5b602002018190525060016002600581106126b6576126b56142a8565b5b0180546126c290614302565b80601f01602080910402602001604051908101604052809291908181526020018280546126ee90614302565b80156127395780601f1061271057610100808354040283529160200191612739565b820191905f5260205f20905b81548152906001019060200180831161271c57829003601f168201915b505050505081600460078110612752576127516142a8565b5b6020020181905250600160036005811061276f5761276e6142a8565b5b01805461277b90614302565b80601f01602080910402602001604051908101604052809291908181526020018280546127a790614302565b80156127f25780601f106127c9576101008083540402835291602001916127f2565b820191905f5260205f20905b8154815290600101906020018083116127d557829003601f168201915b50505050508160056007811061280b5761280a6142a8565b5b60200201819052506001600460058110612828576128276142a8565b5b01805461283490614302565b80601f016020809104026020016040519081016040528092919081815260200182805461286090614302565b80156128ab5780601f10612882576101008083540402835291602001916128ab565b820191905f5260205f20905b81548152906001019060200180831161288e57829003601f168201915b5050505050816006600781106128c4576128c36142a8565b5b60200201819052505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166369a684f6836040518263ffffffff1660e01b815260040161292791906157e7565b61018060405180830381865afa158015612943573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129679190615987565b90505f8160200151905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630f53fc4b86836040518363ffffffff1660e01b81526004016129cd9291906159b3565b5f604051808303815f87803b1580156129e4575f5ffd5b505af11580156129f6573d5f5f3e3d5ffd5b505050503373ffffffffffffffffffffffffffffffffffffffff167f56d64de549930cfd6e6b5189293e8fdc29c0f675f36e2b435eef17bd777bc24e8888855f015185604051612a499493929190615a20565b60405180910390a260075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015612ab7575f5ffd5b505af1158015612ac9573d5f5f3e3d5ffd5b505050505050505050935093915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612b8e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b8590615ae1565b60405180910390fd5b5f5f90505b60348160ff161015612c625760405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f151581526020016101008152505f8260ff1681548110612c0957612c086142a8565b5b905f5260205f2090600302015f820151815f019081612c289190614bb3565b506020820151816001015f6101000a81548160ff021916908315150217905550604082015181600201559050508080600101915050612b93565b506040518060a0016040528060405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f8152508152506001906005612ce09291906134cc565b50565b612ceb613518565b6001600580602002604051908101604052809291905f905b82821015612da4578382018054612d1990614302565b80601f0160208091040260200160405190810160405280929190818152602001828054612d4590614302565b8015612d905780601f10612d6757610100808354040283529160200191612d90565b820191905f5260205f20905b815481529060010190602001808311612d7357829003601f168201915b505050505081526020019060010190612d03565b50505050905090565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6034815114612e3b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e3290615b49565b60405180910390fd5b6001600c811115612e4f57612e4e61380a565b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015612eb9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612edd91906143e9565b600c811115612eef57612eee61380a565b5b14612f2f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f2690615bb1565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401612f8a9190614698565b602060405180830381865afa158015612fa5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fc99190614428565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613036573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061305a9190614428565b90508160ff168160ff16146130a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161309b90615c19565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f73928f31daa4d318dd150e598b3ba6a20b7809ac97177fd3871ccbd66314394d846040516130ea91906137ea565b60405180910390a25f5f90505b83518160ff1610156132145773f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16858360ff1681518110613133576131326142a8565b5b60200260200101515f6101006040518463ffffffff1660e01b815260040161315d93929190614914565b5f60405180830381865af4158015613177573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061319f91906149d5565b5f8260ff16815481106131b5576131b46142a8565b5b905f5260205f2090600302015f820151815f0190816131d49190614bb3565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050808061320c9061435f565b9150506130f7565b5060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b15801561327b575f5ffd5b505af115801561328d573d5f5f3e3d5ffd5b50505050505050565b5f81815481106132a4575f80fd5b905f5260205f2090600302015f91509050805f0180546132c390614302565b80601f01602080910402602001604051908101604052809291908181526020018280546132ef90614302565b801561333a5780601f106133115761010080835404028352916020019161333a565b820191905f5260205f20905b81548152906001019060200180831161331d57829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b6001816005811061336b575f80fd5b015f91509050805461337c90614302565b80601f01602080910402602001604051908101604052809291908181526020018280546133a890614302565b80156133f35780601f106133ca576101008083540402835291602001916133f3565b820191905f5260205f20905b8154815290600101906020018083116133d657829003601f168201915b505050505081565b6040518060600160405280606081526020015f151581526020015f81525090565b604051806101c001604052805f81526020015f600c8111156134415761344061380a565b5b81526020015f81526020015f81526020015f60ff1681526020015f60ff1681526020015f60ff1681526020015f81526020015f81526020015f60ff1681526020015f15158152602001613492613518565b8152602001606081526020015f81525090565b6040518060e001604052806007905b60608152602001906001900390816134b45790505090565b8260058101928215613507579160200282015b828111156135065782518290816134f69190614f79565b50916020019190600101906134df565b5b509050613514919061353f565b5090565b6040518060a001604052806005905b60608152602001906001900390816135275790505090565b5b8082111561355e575f81816135559190613562565b50600101613540565b5090565b50805461356e90614302565b5f825580601f1061357f575061359c565b601f0160209004905f5260205f209081019061359b919061359f565b5b50565b5b808211156135b6575f815f9055506001016135a0565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6135dd816135cb565b81146135e7575f5ffd5b50565b5f813590506135f8816135d4565b92915050565b5f60208284031215613613576136126135c3565b5b5f613620848285016135ea565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61366b82613629565b6136758185613633565b9350613685818560208601613643565b61368e81613651565b840191505092915050565b5f8115159050919050565b6136ad81613699565b82525050565b6136bc816135cb565b82525050565b5f606083015f8301518482035f8601526136dc8282613661565b91505060208301516136f160208601826136a4565b50604083015161370460408601826136b3565b508091505092915050565b5f6020820190508181035f83015261372781846136c2565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6137638383613661565b905092915050565b5f602082019050919050565b5f6137818261372f565b61378b8185613739565b93508360208202850161379d85613749565b805f5b858110156137d857848403895281516137b98582613758565b94506137c48361376b565b925060208a019950506001810190506137a0565b50829750879550505050505092915050565b5f6020820190508181035f8301526138028184613777565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d81106138485761384761380a565b5b50565b5f81905061385882613837565b919050565b5f6138678261384b565b9050919050565b6138778161385d565b82525050565b5f60ff82169050919050565b6138928161387d565b82525050565b5f60059050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b5f6138d9826138b5565b6138e381856138bf565b93506138f3818560208601613643565b6138fc81613651565b840191505092915050565b5f61391283836138cf565b905092915050565b5f602082019050919050565b5f61393082613898565b61393a81856138a2565b93508360208202850161394c856138ac565b805f5b8581101561398757848403895281516139688582613907565b94506139738361391a565b925060208a0199505060018101905061394f565b50829750879550505050505092915050565b5f82825260208201905092915050565b5f6139b38261372f565b6139bd8185613999565b9350836020820285016139cf85613749565b805f5b85811015613a0a57848403895281516139eb8582613758565b94506139f68361376b565b925060208a019950506001810190506139d2565b50829750879550505050505092915050565b5f6101c083015f830151613a325f8601826136b3565b506020830151613a45602086018261386e565b506040830151613a5860408601826136b3565b506060830151613a6b60608601826136b3565b506080830151613a7e6080860182613889565b5060a0830151613a9160a0860182613889565b5060c0830151613aa460c0860182613889565b5060e0830151613ab760e08601826136b3565b50610100830151613acc6101008601826136b3565b50610120830151613ae1610120860182613889565b50610140830151613af66101408601826136a4565b50610160830151848203610160860152613b108282613926565b915050610180830151848203610180860152613b2c82826139a9565b9150506101a0830151613b436101a08601826136b3565b508091505092915050565b5f6020820190508181035f830152613b668184613a1c565b905092915050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613ba882613651565b810181811067ffffffffffffffff82111715613bc757613bc6613b72565b5b80604052505050565b5f613bd96135ba565b9050613be58282613b9f565b919050565b5f67ffffffffffffffff821115613c0457613c03613b72565b5b602082029050602081019050919050565b5f5ffd5b613c228161387d565b8114613c2c575f5ffd5b50565b5f81359050613c3d81613c19565b92915050565b5f613c55613c5084613bea565b613bd0565b90508083825260208201905060208402830185811115613c7857613c77613c15565b5b835b81811015613ca15780613c8d8882613c2f565b845260208401935050602081019050613c7a565b5050509392505050565b5f82601f830112613cbf57613cbe613b6e565b5b8135613ccf848260208601613c43565b91505092915050565b5f67ffffffffffffffff821115613cf257613cf1613b72565b5b602082029050602081019050919050565b5f5ffd5b5f67ffffffffffffffff821115613d2157613d20613b72565b5b613d2a82613651565b9050602081019050919050565b828183375f83830152505050565b5f613d57613d5284613d07565b613bd0565b905082815260208101848484011115613d7357613d72613d03565b5b613d7e848285613d37565b509392505050565b5f82601f830112613d9a57613d99613b6e565b5b8135613daa848260208601613d45565b91505092915050565b5f613dc5613dc084613cd8565b613bd0565b90508083825260208201905060208402830185811115613de857613de7613c15565b5b835b81811015613e2f57803567ffffffffffffffff811115613e0d57613e0c613b6e565b5b808601613e1a8982613d86565b85526020850194505050602081019050613dea565b5050509392505050565b5f82601f830112613e4d57613e4c613b6e565b5b8135613e5d848260208601613db3565b91505092915050565b5f5f60408385031215613e7c57613e7b6135c3565b5b5f83013567ffffffffffffffff811115613e9957613e986135c7565b5b613ea585828601613cab565b925050602083013567ffffffffffffffff811115613ec657613ec56135c7565b5b613ed285828601613e39565b9150509250929050565b5f5f5f60608486031215613ef357613ef26135c3565b5b5f84013567ffffffffffffffff811115613f1057613f0f6135c7565b5b613f1c86828701613d86565b935050602084013567ffffffffffffffff811115613f3d57613f3c6135c7565b5b613f4986828701613d86565b925050604084013567ffffffffffffffff811115613f6a57613f696135c7565b5b613f7686828701613d86565b9150509250925092565b5f82825260208201905092915050565b5f613f9a826138b5565b613fa48185613f80565b9350613fb4818560208601613643565b613fbd81613651565b840191505092915050565b5f6040820190508181035f830152613fe08185613f90565b90508181036020830152613ff48184613f90565b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61403f61403a61403584613ffd565b61401c565b613ffd565b9050919050565b5f61405082614025565b9050919050565b5f61406182614046565b9050919050565b61407181614057565b82525050565b5f60208201905061408a5f830184614068565b92915050565b5f81905092915050565b5f6140a482613898565b6140ae8185614090565b9350836020820285016140c0856138ac565b805f5b858110156140fb57848403895281516140dc8582613907565b94506140e78361391a565b925060208a019950506001810190506140c3565b50829750879550505050505092915050565b5f6020820190508181035f830152614125818461409a565b905092915050565b5f61413782614046565b9050919050565b6141478161412d565b82525050565b5f6020820190506141605f83018461413e565b92915050565b5f61417082614046565b9050919050565b61418081614166565b82525050565b5f6020820190506141995f830184614177565b92915050565b5f602082840312156141b4576141b36135c3565b5b5f82013567ffffffffffffffff8111156141d1576141d06135c7565b5b6141dd84828501613e39565b91505092915050565b5f82825260208201905092915050565b5f61420082613629565b61420a81856141e6565b935061421a818560208601613643565b61422381613651565b840191505092915050565b61423781613699565b82525050565b614246816135cb565b82525050565b5f6060820190508181035f83015261426481866141f6565b9050614273602083018561422e565b614280604083018461423d565b949350505050565b5f6020820190508181035f8301526142a08184613f90565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061431957607f821691505b60208210810361432c5761432b6142d5565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6143698261387d565b915060ff820361437c5761437b614332565b5b600182019050919050565b5f81519050614395816135d4565b92915050565b5f602082840312156143b0576143af6135c3565b5b5f6143bd84828501614387565b91505092915050565b600d81106143d2575f5ffd5b50565b5f815190506143e3816143c6565b92915050565b5f602082840312156143fe576143fd6135c3565b5b5f61440b848285016143d5565b91505092915050565b5f8151905061442281613c19565b92915050565b5f6020828403121561443d5761443c6135c3565b5b5f61444a84828501614414565b91505092915050565b61445c81613699565b8114614466575f5ffd5b50565b5f8151905061447781614453565b92915050565b5f60208284031215614492576144916135c3565b5b5f61449f84828501614469565b91505092915050565b5f6144ba6144b584613d07565b613bd0565b9050828152602081018484840111156144d6576144d5613d03565b5b6144e1848285613643565b509392505050565b5f82601f8301126144fd576144fc613b6e565b5b815161450d8482602086016144a8565b91505092915050565b5f61452861452384613cd8565b613bd0565b9050808382526020820190506020840283018581111561454b5761454a613c15565b5b835b8181101561459257805167ffffffffffffffff8111156145705761456f613b6e565b5b80860161457d89826144e9565b8552602085019450505060208101905061454d565b5050509392505050565b5f82601f8301126145b0576145af613b6e565b5b81516145c0848260208601614516565b91505092915050565b5f602082840312156145de576145dd6135c3565b5b5f82015167ffffffffffffffff8111156145fb576145fa6135c7565b5b6146078482850161459c565b91505092915050565b7f47616d65206973206e6f7420696e20612072657665616c2073746167650000005f82015250565b5f614644601d83613f80565b915061464f82614610565b602082019050919050565b5f6020820190508181035f83015261467181614638565b9050919050565b5f61468282613ffd565b9050919050565b61469281614678565b82525050565b5f6020820190506146ab5f830184614689565b92915050565b7f4e6f7420796f7572207475726e20746f206465637279707400000000000000005f82015250565b5f6146e5601883613f80565b91506146f0826146b1565b602082019050919050565b5f6020820190508181035f830152614712816146d9565b9050919050565b7f4d69736d6174636820696e2063617264496e646578657320616e6420646563725f8201527f797074696f6e56616c756573206c656e67746873000000000000000000000000602082015250565b5f614773603483613f80565b915061477e82614719565b604082019050919050565b5f6020820190508181035f8301526147a081614767565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6147db8383613889565b60208301905092915050565b5f602082019050919050565b5f6147fd826147a7565b61480781856147b1565b9350614812836147c1565b805f5b8381101561484257815161482988826147d0565b9750614834836147e7565b925050600181019050614815565b5085935050505092915050565b5f6040820190508181035f83015261486781856147f3565b9050818103602083015261487b8184613777565b90509392505050565b5f82825260208201905092915050565b5f61489e82613629565b6148a88185614884565b93506148b8818560208601613643565b6148c181613651565b840191505092915050565b6148d581613699565b82525050565b5f819050919050565b5f6148fe6148f96148f4846148db565b61401c565b6135cb565b9050919050565b61490e816148e4565b82525050565b5f6060820190508181035f83015261492c8186614894565b905061493b60208301856148cc565b6149486040830184614905565b949350505050565b5f5ffd5b5f5ffd5b5f6060828403121561496d5761496c614950565b5b6149776060613bd0565b90505f82015167ffffffffffffffff81111561499657614995614954565b5b6149a2848285016144e9565b5f8301525060206149b584828501614469565b60208301525060406149c984828501614387565b60408301525092915050565b5f602082840312156149ea576149e96135c3565b5b5f82015167ffffffffffffffff811115614a0757614a066135c7565b5b614a1384828501614958565b91505092915050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302614a787fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82614a3d565b614a828683614a3d565b95508019841693508086168417925050509392505050565b5f614ab4614aaf614aaa846135cb565b61401c565b6135cb565b9050919050565b5f819050919050565b614acd83614a9a565b614ae1614ad982614abb565b848454614a49565b825550505050565b5f5f905090565b614af8614ae9565b614b03818484614ac4565b505050565b5b81811015614b2657614b1b5f82614af0565b600181019050614b09565b5050565b601f821115614b6b57614b3c81614a1c565b614b4584614a2e565b81016020851015614b54578190505b614b68614b6085614a2e565b830182614b08565b50505b505050565b5f82821c905092915050565b5f614b8b5f1984600802614b70565b1980831691505092915050565b5f614ba38383614b7c565b9150826002028217905092915050565b614bbc82613629565b67ffffffffffffffff811115614bd557614bd4613b72565b5b614bdf8254614302565b614bea828285614b2a565b5f60209050601f831160018114614c1b575f8415614c09578287015190505b614c138582614b98565b865550614c7a565b601f198416614c2986614a1c565b5f5b82811015614c5057848901518255600182019150602085019450602081019050614c2b565b86831015614c6d5784890151614c69601f891682614b7c565b8355505b6001600288020188555050505b505050505050565b5f602082019050614c955f83018461422e565b92915050565b5f8154614ca781614302565b614cb18186613633565b9450600182165f8114614ccb5760018114614ce157614d13565b60ff198316865281151560200286019350614d13565b614cea85614a1c565b5f5b83811015614d0b57815481890152600182019150602081019050614cec565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f614d45614d4083614d1c565b614d27565b9050919050565b5f819050919050565b5f614d67614d6283614d1c565b614d4c565b9050919050565b5f606083015f5f84018583035f870152614d888382614c9b565b92505060018401549050614d9b81614d33565b614da860208701826136a4565b5060028401549050614db981614d55565b614dc660408701826136b3565b50819250505092915050565b5f6020820190508181035f830152614dea8184614d6e565b905092915050565b5f67ffffffffffffffff821115614e0c57614e0b613b72565b5b614e1582613651565b9050602081019050919050565b5f614e34614e2f84614df2565b613bd0565b905082815260208101848484011115614e5057614e4f613d03565b5b614e5b848285613643565b509392505050565b5f82601f830112614e7757614e76613b6e565b5b8151614e87848260208601614e22565b91505092915050565b5f60208284031215614ea557614ea46135c3565b5b5f82015167ffffffffffffffff811115614ec257614ec16135c7565b5b614ece84828501614e63565b91505092915050565b5f6060820190508181035f830152614eef8186613f90565b90508181036020830152614f038185613f90565b90508181036040830152614f178184613f90565b9050949350505050565b5f819050815f5260205f209050919050565b601f821115614f7457614f4581614f21565b614f4e84614a2e565b81016020851015614f5d578190505b614f71614f6985614a2e565b830182614b08565b50505b505050565b614f82826138b5565b67ffffffffffffffff811115614f9b57614f9a613b72565b5b614fa58254614302565b614fb0828285614f33565b5f60209050601f831160018114614fe1575f8415614fcf578287015190505b614fd98582614b98565b865550615040565b601f198416614fef86614f21565b5f5b8281101561501657848901518255600182019150602085019450602081019050614ff1565b86831015615033578489015161502f601f891682614b7c565b8355505b6001600288020188555050505b505050505050565b6150518161387d565b82525050565b5f60208201905061506a5f830184615048565b92915050565b61507981614678565b8114615083575f5ffd5b50565b5f8151905061509481615070565b92915050565b5f67ffffffffffffffff8211156150b4576150b3613b72565b5b602082029050919050565b5f6150d16150cc8461509a565b613bd0565b905080602084028301858111156150eb576150ea613c15565b5b835b8181101561513257805167ffffffffffffffff8111156151105761510f613b6e565b5b80860161511d8982614e63565b855260208501945050506020810190506150ed565b5050509392505050565b5f82601f8301126151505761514f613b6e565b5b600261515d8482856150bf565b91505092915050565b5f610180828403121561517c5761517b614950565b5b615187610180613bd0565b90505f61519684828501615086565b5f8301525060206151a984828501614387565b60208301525060406151bd84828501614387565b60408301525060606151d184828501614387565b60608301525060806151e584828501614469565b60808301525060a06151f984828501614469565b60a08301525060c061520d84828501614469565b60c08301525060e082015167ffffffffffffffff81111561523157615230614954565b5b61523d8482850161513c565b60e08301525061010061525284828501614414565b6101008301525061012061526884828501614387565b6101208301525061014061527e84828501614469565b6101408301525061016061529484828501614469565b6101608301525092915050565b5f602082840312156152b6576152b56135c3565b5b5f82015167ffffffffffffffff8111156152d3576152d26135c7565b5b6152df84828501615166565b91505092915050565b7f506c61796572206e6f74206a6f696e656420616674657220726f756e642073745f8201527f6172746564000000000000000000000000000000000000000000000000000000602082015250565b5f615342602583613f80565b915061534d826152e8565b604082019050919050565b5f6020820190508181035f83015261536f81615336565b9050919050565b7f506c6179657220666f6c646564000000000000000000000000000000000000005f82015250565b5f6153aa600d83613f80565b91506153b582615376565b602082019050919050565b5f6020820190508181035f8301526153d78161539e565b9050919050565b50565b5f6153ec5f83613f80565b91506153f7826153de565b5f82019050919050565b5f6040820190508181035f8301526154198184613f90565b9050818103602083015261542c816153e1565b905092915050565b7f506c6179657220616c72656164792072657665616c65642063617264732028305f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61548e602f83613f80565b915061549982615434565b604082019050919050565b5f6020820190508181035f8301526154bb81615482565b9050919050565b7f506c6179657220616c72656164792072657665616c65642063617264732028315f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61551c602f83613f80565b9150615527826154c2565b604082019050919050565b5f6020820190508181035f83015261554981615510565b9050919050565b5f6060820190508181035f83015261556881866141f6565b9050818103602083015261557c81856141f6565b9050818103604083015261559081846141f6565b9050949350505050565b5f67ffffffffffffffff8211156155b4576155b3613b72565b5b602082029050919050565b5f6155d16155cc8461559a565b613bd0565b905080602084028301858111156155eb576155ea613c15565b5b835b8181101561561457806156008882614414565b8452602084019350506020810190506155ed565b5050509392505050565b5f82601f83011261563257615631613b6e565b5b600261563f8482856155bf565b91505092915050565b5f6040828403121561565d5761565c6135c3565b5b5f61566a8482850161561e565b91505092915050565b5f606083015f8301518482035f86015261568d8282613661565b91505060208301516156a260208601826136a4565b5060408301516156b560408601826136b3565b508091505092915050565b5f604083015f8301518482035f8601526156da8282615673565b915050602083015184820360208601526156f48282615673565b9150508091505092915050565b5f6060820190508181035f83015261571981866156c0565b9050818103602083015261572d81856136c2565b9050818103604083015261574181846136c2565b9050949350505050565b5f60079050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b5f61577e8261574b565b6157888185615755565b93508360208202850161579a8561575f565b805f5b858110156157d557848403895281516157b68582613907565b94506157c183615768565b925060208a0199505060018101905061579d565b50829750879550505050505092915050565b5f6020820190508181035f8301526157ff8184615774565b905092915050565b600a8110615813575f5ffd5b50565b5f8151905061582481615807565b92915050565b5f67ffffffffffffffff82111561584457615843613b72565b5b602082029050919050565b5f6040828403121561586457615863614950565b5b61586e6040613bd0565b90505f61587d84828501614414565b5f83015250602061589084828501614414565b60208301525092915050565b5f6158ae6158a98461582a565b613bd0565b905080604084028301858111156158c8576158c7613c15565b5b835b818110156158f157806158dd888261584f565b8452602084019350506040810190506158ca565b5050509392505050565b5f82601f83011261590f5761590e613b6e565b5b600561591c84828561589c565b91505092915050565b5f610180828403121561593b5761593a614950565b5b6159456060613bd0565b90505f61595484828501615816565b5f83015250602061596784828501614387565b602083015250604061597b848285016158fb565b60408301525092915050565b5f610180828403121561599d5761599c6135c3565b5b5f6159aa84828501615925565b91505092915050565b5f6040820190506159c65f830185615048565b6159d3602083018461423d565b9392505050565b600a81106159eb576159ea61380a565b5b50565b5f8190506159fb826159da565b919050565b5f615a0a826159ee565b9050919050565b615a1a81615a00565b82525050565b5f6080820190508181035f830152615a388187613f90565b90508181036020830152615a4c8186613f90565b9050615a5b6040830185615a11565b615a68606083018461423d565b95945050505050565b7f4f6e6c792074686520726f6f6d20636f6e74726163742063616e2072657365745f8201527f20746865206465636b0000000000000000000000000000000000000000000000602082015250565b5f615acb602983613f80565b9150615ad682615a71565b604082019050919050565b5f6020820190508181035f830152615af881615abf565b9050919050565b7f4d7573742070726f766964652065786163746c792035322063617264730000005f82015250565b5f615b33601d83613f80565b9150615b3e82615aff565b602082019050919050565b5f6020820190508181035f830152615b6081615b27565b9050919050565b7f57726f6e672073746167650000000000000000000000000000000000000000005f82015250565b5f615b9b600b83613f80565b9150615ba682615b67565b602082019050919050565b5f6020820190508181035f830152615bc881615b8f565b9050919050565b7f4e6f7420796f7572207475726e20746f2073687566666c6500000000000000005f82015250565b5f615c03601883613f80565b9150615c0e82615bcf565b602082019050919050565b5f6020820190508181035f830152615c3081615bf7565b905091905056fea2646970667358221220df213d0b108a091d0c80e0ef5885ad3a52e380e9cd705a4b0a6447849386c6cf64736f6c634300081d00330000000000000000000000003b6d3862901fe4b7fa6fb584f8851c0dc5ae036700000000000000000000000027748f13cb0ae5cffd6c6a29499a98eb04da839e000000000000000000000000da98e42845318303a7f58fc20f37577e033ae2fa
Deployed Bytecode
0x608060405234801561000f575f5ffd5b50600436106100cd575f3560e01c80637243087a1161008a578063a377ac5f11610064578063a377ac5f146101ee578063c27a67581461020c578063c3f8295114610228578063eff02a071461025a576100cd565b80637243087a146101a857806374844e19146101b25780639597684c146101d0576100cd565b806316660082146100d15780631870fd6f146101015780632c242a181461011f578063523aadd41461013d57806362224385146101595780636681d2dd1461018a575b5f5ffd5b6100eb60048036038101906100e691906135fe565b61028a565b6040516100f8919061370f565b60405180910390f35b610109610376565b60405161011691906137ea565b60405180910390f35b6101276104c8565b6040516101349190613b4e565b60405180910390f35b61015760048036038101906101529190613e66565b610d1a565b005b610173600480360381019061016e9190613edc565b6119d9565b604051610181929190613fc8565b60405180910390f35b610192612ada565b60405161019f9190614077565b60405180910390f35b6101b0612aff565b005b6101ba612ce3565b6040516101c7919061410d565b60405180910390f35b6101d8612dad565b6040516101e5919061414d565b60405180910390f35b6101f6612dd2565b6040516102039190614186565b60405180910390f35b6102266004803603810190610221919061419f565b612df7565b005b610242600480360381019061023d91906135fe565b613296565b6040516102519392919061424c565b60405180910390f35b610274600480360381019061026f91906135fe565b61335c565b6040516102819190614288565b60405180910390f35b6102926133fb565b5f82815481106102a5576102a46142a8565b5b905f5260205f2090600302016040518060600160405290815f820180546102cb90614302565b80601f01602080910402602001604051908101604052809291908181526020018280546102f790614302565b80156103425780601f1061031957610100808354040283529160200191610342565b820191905f5260205f20905b81548152906001019060200180831161032557829003601f168201915b50505050508152602001600182015f9054906101000a900460ff161515151581526020016002820154815250509050919050565b60605f5f8054905067ffffffffffffffff81111561039757610396613b72565b5b6040519080825280602002602001820160405280156103ca57816020015b60608152602001906001900390816103b55790505b5090505f5f90505b5f805490508160ff1610156104c0575f8160ff16815481106103f7576103f66142a8565b5b905f5260205f2090600302015f01805461041090614302565b80601f016020809104026020016040519081016040528092919081815260200182805461043c90614302565b80156104875780601f1061045e57610100808354040283529160200191610487565b820191905f5260205f20905b81548152906001019060200180831161046a57829003601f168201915b5050505050828260ff16815181106104a2576104a16142a8565b5b602002602001018190525080806104b89061435f565b9150506103d2565b508091505090565b6104d061341c565b604051806101c0016040528060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634e2786fb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610546573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056a919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105fd91906143e9565b600c81111561060f5761060e61380a565b5b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e25501566040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106a2919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370984e976040518163ffffffff1660e01b8152600401602060405180830381865afa158015610711573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610735919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c89190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085e9190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2349ff06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108f49190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634ba2363a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610966573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061098a919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663af5b37996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a1d919061439b565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166397b2f5566040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab09190614428565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663faff660e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b46919061447d565b151581526020016001600580602002604051908101604052809291905f905b82821015610c06578382018054610b7b90614302565b80601f0160208091040260200160405190810160405280929190818152602001828054610ba790614302565b8015610bf25780601f10610bc957610100808354040283529160200191610bf2565b820191905f5260205f20905b815481529060010190602001808311610bd557829003601f168201915b505050505081526020019060010190610b65565b5050505081526020013073ffffffffffffffffffffffffffffffffffffffff16631870fd6f6040518163ffffffff1660e01b81526004015f60405180830381865afa158015610c57573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c7f91906145c9565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663eac697c96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cee573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d12919061439b565b815250905090565b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da991906143e9565b90506002600c811115610dbf57610dbe61380a565b5b81600c811115610dd257610dd161380a565b5b1480610e0257506004600c811115610ded57610dec61380a565b5b81600c811115610e0057610dff61380a565b5b145b80610e3157506006600c811115610e1c57610e1b61380a565b5b81600c811115610e2f57610e2e61380a565b5b145b80610e6057506008600c811115610e4b57610e4a61380a565b5b81600c811115610e5e57610e5d61380a565b5b145b610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e969061465a565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401610efa9190614698565b602060405180830381865afa158015610f15573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f399190614428565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fca9190614428565b90508160ff168160ff1614611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161100b906146fb565b60405180910390fd5b8351855114611058576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104f90614789565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f19c122811f9e4d381304169d982476aabc6977d2467b14da176c96fee621b30386866040516110a092919061484f565b60405180910390a25f5f90505b85518160ff1610156111e75773f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16868360ff16815181106110e9576110e86142a8565b5b60200260200101515f6101006040518463ffffffff1660e01b815260040161111393929190614914565b5f60405180830381865af415801561112d573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061115591906149d5565b5f878360ff168151811061116c5761116b6142a8565b5b602002602001015160ff1681548110611188576111876142a8565b5b905f5260205f2090600302015f820151815f0190816111a79190614bb3565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505080806111df9061435f565b9150506110ad565b505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663df5a065c60016040518263ffffffff1660e01b81526004016112449190614c82565b602060405180830381865afa15801561125f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112839190614428565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112ef573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113139190614428565b60ff168160ff1603611955576004600c8111156113335761133261380a565b5b84600c8111156113465761134561380a565b5b03611688575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f8151811061139d5761139c6142a8565b5b602002602001015160ff16815481106113b9576113b86142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016113e19190614dd2565b5f60405180830381865afa1580156113fb573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114239190614e90565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8a600181518110611478576114776142a8565b5b602002602001015160ff1681548110611494576114936142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016114bc9190614dd2565b5f60405180830381865afa1580156114d6573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114fe9190614e90565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8b600281518110611553576115526142a8565b5b602002602001015160ff168154811061156f5761156e6142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016115979190614dd2565b5f60405180830381865afa1580156115b1573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115d99190614e90565b90507f96e672e4b09efde01ec2235824d9eb4bf5003ba7bf63407a45bff0974415696f83838360405161160e93929190614ed7565b60405180910390a18260015f6005811061162b5761162a6142a8565b5b0190816116389190614f79565b50816001806005811061164e5761164d6142a8565b5b01908161165b9190614f79565b50806001600260058110611672576116716142a8565b5b01908161167f9190614f79565b50505050611954565b6006600c81111561169c5761169b61380a565b5b84600c8111156116af576116ae61380a565b5b036117ef575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611706576117056142a8565b5b602002602001015160ff1681548110611722576117216142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b815260040161174a9190614dd2565b5f60405180830381865afa158015611764573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061178c9190614e90565b90507ff503b0d30585a8adce94d89b991f26d6f675f78304e4b85e7c0d75a1d8cb0bd3816040516117bd9190614288565b60405180910390a18060016003600581106117db576117da6142a8565b5b0190816117e89190614f79565b5050611953565b6008600c8111156118035761180261380a565b5b84600c8111156118165761181561380a565b5b03611952575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f8151811061186d5761186c6142a8565b5b602002602001015160ff1681548110611889576118886142a8565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016118b19190614dd2565b5f60405180830381865afa1580156118cb573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118f39190614e90565b90507f17415641f4bcccb6710699f9b6ddf69fac7f253bd194cc81dd9a2ee32c2db0a3816040516119249190614288565b60405180910390a1806001600460058110611942576119416142a8565b5b01908161194f9190614f79565b50505b5b5b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156119bb575f5ffd5b505af11580156119cd573d5f5f3e3d5ffd5b50505050505050505050565b6060805f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611a379190614698565b602060405180830381865afa158015611a52573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a769190614428565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339fddff6836040518263ffffffff1660e01b8152600401611ad39190615057565b5f60405180830381865afa158015611aed573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b1591906152a1565b905080610140015115611b5d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5490615358565b60405180910390fd5b806080015115611ba2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b99906153c0565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e001515f60028110611bf657611bf56142a8565b5b60200201516040518263ffffffff1660e01b8152600401611c179190615401565b602060405180830381865afa158015611c32573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c56919061447d565b611c95576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8c906154a4565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e00151600160028110611cea57611ce96142a8565b5b60200201516040518263ffffffff1660e01b8152600401611d0b9190615401565b602060405180830381865afa158015611d26573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d4a919061447d565b611d89576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d8090615532565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fcf675dbb98179665682abaede1b14f82aa2dde6d99e83b602058a320c435f29f888888604051611dd393929190615550565b60405180910390a25f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339d505fc846040518263ffffffff1660e01b8152600401611e369190615057565b6040805180830381865afa158015611e50573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e749190615648565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16895f6101006040518463ffffffff1660e01b8152600401611eb493929190614914565b5f60405180830381865af4158015611ece573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ef691906149d5565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168b5f6101006040518463ffffffff1660e01b8152600401611f3693929190614914565b5f60405180830381865af4158015611f50573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611f7891906149d5565b90505f5f845f60028110611f8f57611f8e6142a8565b5b602002015160ff1681548110611fa857611fa76142a8565b5b905f5260205f2090600302016040518060600160405290815f82018054611fce90614302565b80601f0160208091040260200160405190810160405280929190818152602001828054611ffa90614302565b80156120455780601f1061201c57610100808354040283529160200191612045565b820191905f5260205f20905b81548152906001019060200180831161202857829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f5f8560016002811061208a576120896142a8565b5b602002015160ff16815481106120a3576120a26142a8565b5b905f5260205f2090600302016040518060600160405290815f820180546120c990614302565b80601f01602080910402602001604051908101604052809291908181526020018280546120f590614302565b80156121405780601f1061211757610100808354040283529160200191612140565b820191905f5260205f20905b81548152906001019060200180831161212357829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f60405180604001604052808581526020018481525090505f60405180604001604052808681526020018481525090505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168e5f6101006040518463ffffffff1660e01b81526004016121dd93929190614914565b5f60405180830381865af41580156121f7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061221f91906149d5565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858a856040518463ffffffff1660e01b815260040161228093929190615701565b5f604051808303815f875af115801561229b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906122c391906149d5565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858b866040518463ffffffff1660e01b815260040161232493929190615701565b5f604051808303815f875af115801561233f573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061236791906149d5565b905060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9836040518263ffffffff1660e01b81526004016123c3919061370f565b5f60405180830381865afa1580156123dd573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124059190614e90565b9d5060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9826040518263ffffffff1660e01b8152600401612461919061370f565b5f60405180830381865afa15801561247b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124a39190614e90565b9c5050505050505050505050838160e001515f600281106124c7576124c66142a8565b5b6020020181905250828160e001516001600281106124e8576124e76142a8565b5b60200201819052506124f86134a5565b84815f6007811061250c5761250b6142a8565b5b60200201819052508381600160078110612529576125286142a8565b5b602002018190525060015f60058110612545576125446142a8565b5b01805461255190614302565b80601f016020809104026020016040519081016040528092919081815260200182805461257d90614302565b80156125c85780601f1061259f576101008083540402835291602001916125c8565b820191905f5260205f20905b8154815290600101906020018083116125ab57829003601f168201915b5050505050816002600781106125e1576125e06142a8565b5b6020020181905250600180600581106125fd576125fc6142a8565b5b01805461260990614302565b80601f016020809104026020016040519081016040528092919081815260200182805461263590614302565b80156126805780601f1061265757610100808354040283529160200191612680565b820191905f5260205f20905b81548152906001019060200180831161266357829003601f168201915b505050505081600360078110612699576126986142a8565b5b602002018190525060016002600581106126b6576126b56142a8565b5b0180546126c290614302565b80601f01602080910402602001604051908101604052809291908181526020018280546126ee90614302565b80156127395780601f1061271057610100808354040283529160200191612739565b820191905f5260205f20905b81548152906001019060200180831161271c57829003601f168201915b505050505081600460078110612752576127516142a8565b5b6020020181905250600160036005811061276f5761276e6142a8565b5b01805461277b90614302565b80601f01602080910402602001604051908101604052809291908181526020018280546127a790614302565b80156127f25780601f106127c9576101008083540402835291602001916127f2565b820191905f5260205f20905b8154815290600101906020018083116127d557829003601f168201915b50505050508160056007811061280b5761280a6142a8565b5b60200201819052506001600460058110612828576128276142a8565b5b01805461283490614302565b80601f016020809104026020016040519081016040528092919081815260200182805461286090614302565b80156128ab5780601f10612882576101008083540402835291602001916128ab565b820191905f5260205f20905b81548152906001019060200180831161288e57829003601f168201915b5050505050816006600781106128c4576128c36142a8565b5b60200201819052505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166369a684f6836040518263ffffffff1660e01b815260040161292791906157e7565b61018060405180830381865afa158015612943573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129679190615987565b90505f8160200151905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630f53fc4b86836040518363ffffffff1660e01b81526004016129cd9291906159b3565b5f604051808303815f87803b1580156129e4575f5ffd5b505af11580156129f6573d5f5f3e3d5ffd5b505050503373ffffffffffffffffffffffffffffffffffffffff167f56d64de549930cfd6e6b5189293e8fdc29c0f675f36e2b435eef17bd777bc24e8888855f015185604051612a499493929190615a20565b60405180910390a260075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015612ab7575f5ffd5b505af1158015612ac9573d5f5f3e3d5ffd5b505050505050505050935093915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612b8e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b8590615ae1565b60405180910390fd5b5f5f90505b60348160ff161015612c625760405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f151581526020016101008152505f8260ff1681548110612c0957612c086142a8565b5b905f5260205f2090600302015f820151815f019081612c289190614bb3565b506020820151816001015f6101000a81548160ff021916908315150217905550604082015181600201559050508080600101915050612b93565b506040518060a0016040528060405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f8152508152506001906005612ce09291906134cc565b50565b612ceb613518565b6001600580602002604051908101604052809291905f905b82821015612da4578382018054612d1990614302565b80601f0160208091040260200160405190810160405280929190818152602001828054612d4590614302565b8015612d905780601f10612d6757610100808354040283529160200191612d90565b820191905f5260205f20905b815481529060010190602001808311612d7357829003601f168201915b505050505081526020019060010190612d03565b50505050905090565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6034815114612e3b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e3290615b49565b60405180910390fd5b6001600c811115612e4f57612e4e61380a565b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015612eb9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612edd91906143e9565b600c811115612eef57612eee61380a565b5b14612f2f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f2690615bb1565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401612f8a9190614698565b602060405180830381865afa158015612fa5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fc99190614428565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613036573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061305a9190614428565b90508160ff168160ff16146130a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161309b90615c19565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f73928f31daa4d318dd150e598b3ba6a20b7809ac97177fd3871ccbd66314394d846040516130ea91906137ea565b60405180910390a25f5f90505b83518160ff1610156132145773f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16858360ff1681518110613133576131326142a8565b5b60200260200101515f6101006040518463ffffffff1660e01b815260040161315d93929190614914565b5f60405180830381865af4158015613177573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061319f91906149d5565b5f8260ff16815481106131b5576131b46142a8565b5b905f5260205f2090600302015f820151815f0190816131d49190614bb3565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050808061320c9061435f565b9150506130f7565b5060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b15801561327b575f5ffd5b505af115801561328d573d5f5f3e3d5ffd5b50505050505050565b5f81815481106132a4575f80fd5b905f5260205f2090600302015f91509050805f0180546132c390614302565b80601f01602080910402602001604051908101604052809291908181526020018280546132ef90614302565b801561333a5780601f106133115761010080835404028352916020019161333a565b820191905f5260205f20905b81548152906001019060200180831161331d57829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b6001816005811061336b575f80fd5b015f91509050805461337c90614302565b80601f01602080910402602001604051908101604052809291908181526020018280546133a890614302565b80156133f35780601f106133ca576101008083540402835291602001916133f3565b820191905f5260205f20905b8154815290600101906020018083116133d657829003601f168201915b505050505081565b6040518060600160405280606081526020015f151581526020015f81525090565b604051806101c001604052805f81526020015f600c8111156134415761344061380a565b5b81526020015f81526020015f81526020015f60ff1681526020015f60ff1681526020015f60ff1681526020015f81526020015f81526020015f60ff1681526020015f15158152602001613492613518565b8152602001606081526020015f81525090565b6040518060e001604052806007905b60608152602001906001900390816134b45790505090565b8260058101928215613507579160200282015b828111156135065782518290816134f69190614f79565b50916020019190600101906134df565b5b509050613514919061353f565b5090565b6040518060a001604052806005905b60608152602001906001900390816135275790505090565b5b8082111561355e575f81816135559190613562565b50600101613540565b5090565b50805461356e90614302565b5f825580601f1061357f575061359c565b601f0160209004905f5260205f209081019061359b919061359f565b5b50565b5b808211156135b6575f815f9055506001016135a0565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6135dd816135cb565b81146135e7575f5ffd5b50565b5f813590506135f8816135d4565b92915050565b5f60208284031215613613576136126135c3565b5b5f613620848285016135ea565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61366b82613629565b6136758185613633565b9350613685818560208601613643565b61368e81613651565b840191505092915050565b5f8115159050919050565b6136ad81613699565b82525050565b6136bc816135cb565b82525050565b5f606083015f8301518482035f8601526136dc8282613661565b91505060208301516136f160208601826136a4565b50604083015161370460408601826136b3565b508091505092915050565b5f6020820190508181035f83015261372781846136c2565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6137638383613661565b905092915050565b5f602082019050919050565b5f6137818261372f565b61378b8185613739565b93508360208202850161379d85613749565b805f5b858110156137d857848403895281516137b98582613758565b94506137c48361376b565b925060208a019950506001810190506137a0565b50829750879550505050505092915050565b5f6020820190508181035f8301526138028184613777565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d81106138485761384761380a565b5b50565b5f81905061385882613837565b919050565b5f6138678261384b565b9050919050565b6138778161385d565b82525050565b5f60ff82169050919050565b6138928161387d565b82525050565b5f60059050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b5f6138d9826138b5565b6138e381856138bf565b93506138f3818560208601613643565b6138fc81613651565b840191505092915050565b5f61391283836138cf565b905092915050565b5f602082019050919050565b5f61393082613898565b61393a81856138a2565b93508360208202850161394c856138ac565b805f5b8581101561398757848403895281516139688582613907565b94506139738361391a565b925060208a0199505060018101905061394f565b50829750879550505050505092915050565b5f82825260208201905092915050565b5f6139b38261372f565b6139bd8185613999565b9350836020820285016139cf85613749565b805f5b85811015613a0a57848403895281516139eb8582613758565b94506139f68361376b565b925060208a019950506001810190506139d2565b50829750879550505050505092915050565b5f6101c083015f830151613a325f8601826136b3565b506020830151613a45602086018261386e565b506040830151613a5860408601826136b3565b506060830151613a6b60608601826136b3565b506080830151613a7e6080860182613889565b5060a0830151613a9160a0860182613889565b5060c0830151613aa460c0860182613889565b5060e0830151613ab760e08601826136b3565b50610100830151613acc6101008601826136b3565b50610120830151613ae1610120860182613889565b50610140830151613af66101408601826136a4565b50610160830151848203610160860152613b108282613926565b915050610180830151848203610180860152613b2c82826139a9565b9150506101a0830151613b436101a08601826136b3565b508091505092915050565b5f6020820190508181035f830152613b668184613a1c565b905092915050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613ba882613651565b810181811067ffffffffffffffff82111715613bc757613bc6613b72565b5b80604052505050565b5f613bd96135ba565b9050613be58282613b9f565b919050565b5f67ffffffffffffffff821115613c0457613c03613b72565b5b602082029050602081019050919050565b5f5ffd5b613c228161387d565b8114613c2c575f5ffd5b50565b5f81359050613c3d81613c19565b92915050565b5f613c55613c5084613bea565b613bd0565b90508083825260208201905060208402830185811115613c7857613c77613c15565b5b835b81811015613ca15780613c8d8882613c2f565b845260208401935050602081019050613c7a565b5050509392505050565b5f82601f830112613cbf57613cbe613b6e565b5b8135613ccf848260208601613c43565b91505092915050565b5f67ffffffffffffffff821115613cf257613cf1613b72565b5b602082029050602081019050919050565b5f5ffd5b5f67ffffffffffffffff821115613d2157613d20613b72565b5b613d2a82613651565b9050602081019050919050565b828183375f83830152505050565b5f613d57613d5284613d07565b613bd0565b905082815260208101848484011115613d7357613d72613d03565b5b613d7e848285613d37565b509392505050565b5f82601f830112613d9a57613d99613b6e565b5b8135613daa848260208601613d45565b91505092915050565b5f613dc5613dc084613cd8565b613bd0565b90508083825260208201905060208402830185811115613de857613de7613c15565b5b835b81811015613e2f57803567ffffffffffffffff811115613e0d57613e0c613b6e565b5b808601613e1a8982613d86565b85526020850194505050602081019050613dea565b5050509392505050565b5f82601f830112613e4d57613e4c613b6e565b5b8135613e5d848260208601613db3565b91505092915050565b5f5f60408385031215613e7c57613e7b6135c3565b5b5f83013567ffffffffffffffff811115613e9957613e986135c7565b5b613ea585828601613cab565b925050602083013567ffffffffffffffff811115613ec657613ec56135c7565b5b613ed285828601613e39565b9150509250929050565b5f5f5f60608486031215613ef357613ef26135c3565b5b5f84013567ffffffffffffffff811115613f1057613f0f6135c7565b5b613f1c86828701613d86565b935050602084013567ffffffffffffffff811115613f3d57613f3c6135c7565b5b613f4986828701613d86565b925050604084013567ffffffffffffffff811115613f6a57613f696135c7565b5b613f7686828701613d86565b9150509250925092565b5f82825260208201905092915050565b5f613f9a826138b5565b613fa48185613f80565b9350613fb4818560208601613643565b613fbd81613651565b840191505092915050565b5f6040820190508181035f830152613fe08185613f90565b90508181036020830152613ff48184613f90565b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61403f61403a61403584613ffd565b61401c565b613ffd565b9050919050565b5f61405082614025565b9050919050565b5f61406182614046565b9050919050565b61407181614057565b82525050565b5f60208201905061408a5f830184614068565b92915050565b5f81905092915050565b5f6140a482613898565b6140ae8185614090565b9350836020820285016140c0856138ac565b805f5b858110156140fb57848403895281516140dc8582613907565b94506140e78361391a565b925060208a019950506001810190506140c3565b50829750879550505050505092915050565b5f6020820190508181035f830152614125818461409a565b905092915050565b5f61413782614046565b9050919050565b6141478161412d565b82525050565b5f6020820190506141605f83018461413e565b92915050565b5f61417082614046565b9050919050565b61418081614166565b82525050565b5f6020820190506141995f830184614177565b92915050565b5f602082840312156141b4576141b36135c3565b5b5f82013567ffffffffffffffff8111156141d1576141d06135c7565b5b6141dd84828501613e39565b91505092915050565b5f82825260208201905092915050565b5f61420082613629565b61420a81856141e6565b935061421a818560208601613643565b61422381613651565b840191505092915050565b61423781613699565b82525050565b614246816135cb565b82525050565b5f6060820190508181035f83015261426481866141f6565b9050614273602083018561422e565b614280604083018461423d565b949350505050565b5f6020820190508181035f8301526142a08184613f90565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061431957607f821691505b60208210810361432c5761432b6142d5565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6143698261387d565b915060ff820361437c5761437b614332565b5b600182019050919050565b5f81519050614395816135d4565b92915050565b5f602082840312156143b0576143af6135c3565b5b5f6143bd84828501614387565b91505092915050565b600d81106143d2575f5ffd5b50565b5f815190506143e3816143c6565b92915050565b5f602082840312156143fe576143fd6135c3565b5b5f61440b848285016143d5565b91505092915050565b5f8151905061442281613c19565b92915050565b5f6020828403121561443d5761443c6135c3565b5b5f61444a84828501614414565b91505092915050565b61445c81613699565b8114614466575f5ffd5b50565b5f8151905061447781614453565b92915050565b5f60208284031215614492576144916135c3565b5b5f61449f84828501614469565b91505092915050565b5f6144ba6144b584613d07565b613bd0565b9050828152602081018484840111156144d6576144d5613d03565b5b6144e1848285613643565b509392505050565b5f82601f8301126144fd576144fc613b6e565b5b815161450d8482602086016144a8565b91505092915050565b5f61452861452384613cd8565b613bd0565b9050808382526020820190506020840283018581111561454b5761454a613c15565b5b835b8181101561459257805167ffffffffffffffff8111156145705761456f613b6e565b5b80860161457d89826144e9565b8552602085019450505060208101905061454d565b5050509392505050565b5f82601f8301126145b0576145af613b6e565b5b81516145c0848260208601614516565b91505092915050565b5f602082840312156145de576145dd6135c3565b5b5f82015167ffffffffffffffff8111156145fb576145fa6135c7565b5b6146078482850161459c565b91505092915050565b7f47616d65206973206e6f7420696e20612072657665616c2073746167650000005f82015250565b5f614644601d83613f80565b915061464f82614610565b602082019050919050565b5f6020820190508181035f83015261467181614638565b9050919050565b5f61468282613ffd565b9050919050565b61469281614678565b82525050565b5f6020820190506146ab5f830184614689565b92915050565b7f4e6f7420796f7572207475726e20746f206465637279707400000000000000005f82015250565b5f6146e5601883613f80565b91506146f0826146b1565b602082019050919050565b5f6020820190508181035f830152614712816146d9565b9050919050565b7f4d69736d6174636820696e2063617264496e646578657320616e6420646563725f8201527f797074696f6e56616c756573206c656e67746873000000000000000000000000602082015250565b5f614773603483613f80565b915061477e82614719565b604082019050919050565b5f6020820190508181035f8301526147a081614767565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6147db8383613889565b60208301905092915050565b5f602082019050919050565b5f6147fd826147a7565b61480781856147b1565b9350614812836147c1565b805f5b8381101561484257815161482988826147d0565b9750614834836147e7565b925050600181019050614815565b5085935050505092915050565b5f6040820190508181035f83015261486781856147f3565b9050818103602083015261487b8184613777565b90509392505050565b5f82825260208201905092915050565b5f61489e82613629565b6148a88185614884565b93506148b8818560208601613643565b6148c181613651565b840191505092915050565b6148d581613699565b82525050565b5f819050919050565b5f6148fe6148f96148f4846148db565b61401c565b6135cb565b9050919050565b61490e816148e4565b82525050565b5f6060820190508181035f83015261492c8186614894565b905061493b60208301856148cc565b6149486040830184614905565b949350505050565b5f5ffd5b5f5ffd5b5f6060828403121561496d5761496c614950565b5b6149776060613bd0565b90505f82015167ffffffffffffffff81111561499657614995614954565b5b6149a2848285016144e9565b5f8301525060206149b584828501614469565b60208301525060406149c984828501614387565b60408301525092915050565b5f602082840312156149ea576149e96135c3565b5b5f82015167ffffffffffffffff811115614a0757614a066135c7565b5b614a1384828501614958565b91505092915050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302614a787fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82614a3d565b614a828683614a3d565b95508019841693508086168417925050509392505050565b5f614ab4614aaf614aaa846135cb565b61401c565b6135cb565b9050919050565b5f819050919050565b614acd83614a9a565b614ae1614ad982614abb565b848454614a49565b825550505050565b5f5f905090565b614af8614ae9565b614b03818484614ac4565b505050565b5b81811015614b2657614b1b5f82614af0565b600181019050614b09565b5050565b601f821115614b6b57614b3c81614a1c565b614b4584614a2e565b81016020851015614b54578190505b614b68614b6085614a2e565b830182614b08565b50505b505050565b5f82821c905092915050565b5f614b8b5f1984600802614b70565b1980831691505092915050565b5f614ba38383614b7c565b9150826002028217905092915050565b614bbc82613629565b67ffffffffffffffff811115614bd557614bd4613b72565b5b614bdf8254614302565b614bea828285614b2a565b5f60209050601f831160018114614c1b575f8415614c09578287015190505b614c138582614b98565b865550614c7a565b601f198416614c2986614a1c565b5f5b82811015614c5057848901518255600182019150602085019450602081019050614c2b565b86831015614c6d5784890151614c69601f891682614b7c565b8355505b6001600288020188555050505b505050505050565b5f602082019050614c955f83018461422e565b92915050565b5f8154614ca781614302565b614cb18186613633565b9450600182165f8114614ccb5760018114614ce157614d13565b60ff198316865281151560200286019350614d13565b614cea85614a1c565b5f5b83811015614d0b57815481890152600182019150602081019050614cec565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f614d45614d4083614d1c565b614d27565b9050919050565b5f819050919050565b5f614d67614d6283614d1c565b614d4c565b9050919050565b5f606083015f5f84018583035f870152614d888382614c9b565b92505060018401549050614d9b81614d33565b614da860208701826136a4565b5060028401549050614db981614d55565b614dc660408701826136b3565b50819250505092915050565b5f6020820190508181035f830152614dea8184614d6e565b905092915050565b5f67ffffffffffffffff821115614e0c57614e0b613b72565b5b614e1582613651565b9050602081019050919050565b5f614e34614e2f84614df2565b613bd0565b905082815260208101848484011115614e5057614e4f613d03565b5b614e5b848285613643565b509392505050565b5f82601f830112614e7757614e76613b6e565b5b8151614e87848260208601614e22565b91505092915050565b5f60208284031215614ea557614ea46135c3565b5b5f82015167ffffffffffffffff811115614ec257614ec16135c7565b5b614ece84828501614e63565b91505092915050565b5f6060820190508181035f830152614eef8186613f90565b90508181036020830152614f038185613f90565b90508181036040830152614f178184613f90565b9050949350505050565b5f819050815f5260205f209050919050565b601f821115614f7457614f4581614f21565b614f4e84614a2e565b81016020851015614f5d578190505b614f71614f6985614a2e565b830182614b08565b50505b505050565b614f82826138b5565b67ffffffffffffffff811115614f9b57614f9a613b72565b5b614fa58254614302565b614fb0828285614f33565b5f60209050601f831160018114614fe1575f8415614fcf578287015190505b614fd98582614b98565b865550615040565b601f198416614fef86614f21565b5f5b8281101561501657848901518255600182019150602085019450602081019050614ff1565b86831015615033578489015161502f601f891682614b7c565b8355505b6001600288020188555050505b505050505050565b6150518161387d565b82525050565b5f60208201905061506a5f830184615048565b92915050565b61507981614678565b8114615083575f5ffd5b50565b5f8151905061509481615070565b92915050565b5f67ffffffffffffffff8211156150b4576150b3613b72565b5b602082029050919050565b5f6150d16150cc8461509a565b613bd0565b905080602084028301858111156150eb576150ea613c15565b5b835b8181101561513257805167ffffffffffffffff8111156151105761510f613b6e565b5b80860161511d8982614e63565b855260208501945050506020810190506150ed565b5050509392505050565b5f82601f8301126151505761514f613b6e565b5b600261515d8482856150bf565b91505092915050565b5f610180828403121561517c5761517b614950565b5b615187610180613bd0565b90505f61519684828501615086565b5f8301525060206151a984828501614387565b60208301525060406151bd84828501614387565b60408301525060606151d184828501614387565b60608301525060806151e584828501614469565b60808301525060a06151f984828501614469565b60a08301525060c061520d84828501614469565b60c08301525060e082015167ffffffffffffffff81111561523157615230614954565b5b61523d8482850161513c565b60e08301525061010061525284828501614414565b6101008301525061012061526884828501614387565b6101208301525061014061527e84828501614469565b6101408301525061016061529484828501614469565b6101608301525092915050565b5f602082840312156152b6576152b56135c3565b5b5f82015167ffffffffffffffff8111156152d3576152d26135c7565b5b6152df84828501615166565b91505092915050565b7f506c61796572206e6f74206a6f696e656420616674657220726f756e642073745f8201527f6172746564000000000000000000000000000000000000000000000000000000602082015250565b5f615342602583613f80565b915061534d826152e8565b604082019050919050565b5f6020820190508181035f83015261536f81615336565b9050919050565b7f506c6179657220666f6c646564000000000000000000000000000000000000005f82015250565b5f6153aa600d83613f80565b91506153b582615376565b602082019050919050565b5f6020820190508181035f8301526153d78161539e565b9050919050565b50565b5f6153ec5f83613f80565b91506153f7826153de565b5f82019050919050565b5f6040820190508181035f8301526154198184613f90565b9050818103602083015261542c816153e1565b905092915050565b7f506c6179657220616c72656164792072657665616c65642063617264732028305f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61548e602f83613f80565b915061549982615434565b604082019050919050565b5f6020820190508181035f8301526154bb81615482565b9050919050565b7f506c6179657220616c72656164792072657665616c65642063617264732028315f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61551c602f83613f80565b9150615527826154c2565b604082019050919050565b5f6020820190508181035f83015261554981615510565b9050919050565b5f6060820190508181035f83015261556881866141f6565b9050818103602083015261557c81856141f6565b9050818103604083015261559081846141f6565b9050949350505050565b5f67ffffffffffffffff8211156155b4576155b3613b72565b5b602082029050919050565b5f6155d16155cc8461559a565b613bd0565b905080602084028301858111156155eb576155ea613c15565b5b835b8181101561561457806156008882614414565b8452602084019350506020810190506155ed565b5050509392505050565b5f82601f83011261563257615631613b6e565b5b600261563f8482856155bf565b91505092915050565b5f6040828403121561565d5761565c6135c3565b5b5f61566a8482850161561e565b91505092915050565b5f606083015f8301518482035f86015261568d8282613661565b91505060208301516156a260208601826136a4565b5060408301516156b560408601826136b3565b508091505092915050565b5f604083015f8301518482035f8601526156da8282615673565b915050602083015184820360208601526156f48282615673565b9150508091505092915050565b5f6060820190508181035f83015261571981866156c0565b9050818103602083015261572d81856136c2565b9050818103604083015261574181846136c2565b9050949350505050565b5f60079050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b5f61577e8261574b565b6157888185615755565b93508360208202850161579a8561575f565b805f5b858110156157d557848403895281516157b68582613907565b94506157c183615768565b925060208a0199505060018101905061579d565b50829750879550505050505092915050565b5f6020820190508181035f8301526157ff8184615774565b905092915050565b600a8110615813575f5ffd5b50565b5f8151905061582481615807565b92915050565b5f67ffffffffffffffff82111561584457615843613b72565b5b602082029050919050565b5f6040828403121561586457615863614950565b5b61586e6040613bd0565b90505f61587d84828501614414565b5f83015250602061589084828501614414565b60208301525092915050565b5f6158ae6158a98461582a565b613bd0565b905080604084028301858111156158c8576158c7613c15565b5b835b818110156158f157806158dd888261584f565b8452602084019350506040810190506158ca565b5050509392505050565b5f82601f83011261590f5761590e613b6e565b5b600561591c84828561589c565b91505092915050565b5f610180828403121561593b5761593a614950565b5b6159456060613bd0565b90505f61595484828501615816565b5f83015250602061596784828501614387565b602083015250604061597b848285016158fb565b60408301525092915050565b5f610180828403121561599d5761599c6135c3565b5b5f6159aa84828501615925565b91505092915050565b5f6040820190506159c65f830185615048565b6159d3602083018461423d565b9392505050565b600a81106159eb576159ea61380a565b5b50565b5f8190506159fb826159da565b919050565b5f615a0a826159ee565b9050919050565b615a1a81615a00565b82525050565b5f6080820190508181035f830152615a388187613f90565b90508181036020830152615a4c8186613f90565b9050615a5b6040830185615a11565b615a68606083018461423d565b95945050505050565b7f4f6e6c792074686520726f6f6d20636f6e74726163742063616e2072657365745f8201527f20746865206465636b0000000000000000000000000000000000000000000000602082015250565b5f615acb602983613f80565b9150615ad682615a71565b604082019050919050565b5f6020820190508181035f830152615af881615abf565b9050919050565b7f4d7573742070726f766964652065786163746c792035322063617264730000005f82015250565b5f615b33601d83613f80565b9150615b3e82615aff565b602082019050919050565b5f6020820190508181035f830152615b6081615b27565b9050919050565b7f57726f6e672073746167650000000000000000000000000000000000000000005f82015250565b5f615b9b600b83613f80565b9150615ba682615b67565b602082019050919050565b5f6020820190508181035f830152615bc881615b8f565b9050919050565b7f4e6f7420796f7572207475726e20746f2073687566666c6500000000000000005f82015250565b5f615c03601883613f80565b9150615c0e82615bcf565b602082019050919050565b5f6020820190508181035f830152615c3081615bf7565b905091905056fea2646970667358221220df213d0b108a091d0c80e0ef5885ad3a52e380e9cd705a4b0a6447849386c6cf64736f6c634300081d0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000003b6d3862901fe4b7fa6fb584f8851c0dc5ae036700000000000000000000000027748f13cb0ae5cffd6c6a29499a98eb04da839e000000000000000000000000da98e42845318303a7f58fc20f37577e033ae2fa
-----Decoded View---------------
Arg [0] : _texasHoldemRoom (address): 0x3B6d3862901Fe4b7FA6fB584F8851C0dC5ae0367
Arg [1] : _cryptoUtils (address): 0x27748f13cb0AE5CffD6c6a29499A98eB04da839E
Arg [2] : _handEvaluator (address): 0xDa98e42845318303a7F58Fc20f37577E033Ae2Fa
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000003b6d3862901fe4b7fa6fb584f8851c0dc5ae0367
Arg [1] : 00000000000000000000000027748f13cb0ae5cffd6c6a29499a98eb04da839e
Arg [2] : 000000000000000000000000da98e42845318303a7f58fc20f37577e033ae2fa
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.