ETH Price: $2,079.60 (+1.04%)
 

Overview

ETH Balance

0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Reveal My Cards300205502025-05-09 22:40:47280 days ago1746830447IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000650.00108668
Submit Decryptio...300205082025-05-09 22:39:23280 days ago1746830363IN
0xaa988C3d...Dd44FB7A6
0 ETH0.00000040.00209239
Submit Decryptio...300205002025-05-09 22:39:07280 days ago1746830347IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000150.00109118
Submit Decryptio...300204642025-05-09 22:37:55280 days ago1746830275IN
0xaa988C3d...Dd44FB7A6
0 ETH0.00000040.0020852
Submit Decryptio...300204542025-05-09 22:37:35280 days ago1746830255IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000140.00109302
Submit Decryptio...300204292025-05-09 22:36:45280 days ago1746830205IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000670.00209536
Submit Decryptio...300204232025-05-09 22:36:33280 days ago1746830193IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000210.00109735
Submit Decryptio...300203432025-05-09 22:33:53280 days ago1746830033IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000380.00209066
Submit Decryptio...300203222025-05-09 22:33:11280 days ago1746829991IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000180.00109668
Submit Encrypted...300202922025-05-09 22:32:11280 days ago1746829931IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000002810.00209129
Submit Encrypted...300189012025-05-09 21:45:49280 days ago1746827149IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000002940.00122782
Submit Decryptio...300157102025-05-09 19:59:27280 days ago1746820767IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000520.00122706
Submit Decryptio...300156962025-05-09 19:58:59280 days ago1746820739IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000390.00223308
Submit Decryptio...300156162025-05-09 19:56:19280 days ago1746820579IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000230.00122912
Submit Decryptio...300156102025-05-09 19:56:07280 days ago1746820567IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000340.00223063
Submit Encrypted...300155982025-05-09 19:55:43280 days ago1746820543IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000001730.00123105
Submit Encrypted...300155902025-05-09 19:55:27280 days ago1746820527IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000005270.0022308
Reveal My Cards300149062025-05-09 19:32:39280 days ago1746819159IN
0xaa988C3d...Dd44FB7A6
0 ETH0.00000130.00221955
Reveal My Cards300126132025-05-09 18:16:13280 days ago1746814573IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000001310.00222779
Reveal My Cards299774462025-05-08 22:43:59281 days ago1746744239IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000003960.00674735
Reveal My Cards299773052025-05-08 22:39:17281 days ago1746743957IN
0xaa988C3d...Dd44FB7A6
0 ETH0.00000420.00714292
Reveal My Cards299772762025-05-08 22:38:19281 days ago1746743899IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000004380.00722244
Submit Decryptio...299772562025-05-08 22:37:39281 days ago1746743859IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000003610.0180669
Submit Decryptio...299772512025-05-08 22:37:29281 days ago1746743849IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000000930.00732128
Submit Decryptio...299772172025-05-08 22:36:21281 days ago1746743781IN
0xaa988C3d...Dd44FB7A6
0 ETH0.000003640.01822911
View all transactions

Parent Transaction Hash Block From To
View All Internal Transactions

Cross-Chain Transactions
Loading...
Loading

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import "./BigNumbers/BigNumbers.sol";
import "./CryptoUtils.sol";
import "./TexasHoldemRoom.sol";
import "./PokerHandEvaluatorv2.sol";

contract DeckHandler {
    using BigNumbers for BigNumber;

    BigNumber[] public encryptedDeck;
    string[5] public communityCards;

    CryptoUtils public cryptoUtils;
    TexasHoldemRoom public texasHoldemRoom;
    PokerHandEvaluatorv2 public handEvaluator;

    event EncryptedShuffleSubmitted(address indexed player, bytes[] encryptedShuffle);
    event DecryptionValuesSubmitted(
        address indexed player, uint8[] cardIndexes, bytes[] decryptionValues
    );
    event FlopRevealed(string card1, string card2, string card3);
    event TurnRevealed(string card1);
    event RiverRevealed(string card1);
    event PlayerRevealingCards(
        address indexed player, bytes c1, bytes privateKey, bytes c1InversePowPrivateKey
    );
    event PlayerCardsRevealed(
        address indexed player,
        string card1,
        string card2,
        PokerHandEvaluatorv2.HandRank rank,
        uint256 handScore
    );
    // event THP_Log(string message);

    constructor(address _texasHoldemRoom, address _cryptoUtils, address _handEvaluator) {
        texasHoldemRoom = TexasHoldemRoom(_texasHoldemRoom);
        cryptoUtils = CryptoUtils(_cryptoUtils);
        handEvaluator = PokerHandEvaluatorv2(_handEvaluator);
        for (uint256 i = 0; i < 52; i++) {
            encryptedDeck.push(BigNumber({ val: "0", neg: false, bitlen: 256 }));
        }
    }

    // called when a new round starts, only callable by the room contract
    function resetDeck() external {
        require(msg.sender == address(texasHoldemRoom), "Only the room contract can reset the deck");
        for (uint8 i = 0; i < 52; i++) {
            encryptedDeck[i] = BigNumber({ val: "0", neg: false, bitlen: 256 });
        }
        communityCards = ["", "", "", "", ""];
    }

    function submitEncryptedShuffle(bytes[] memory encryptedShuffle) external {
        require(encryptedShuffle.length == 52, "Must provide exactly 52 cards");
        require(texasHoldemRoom.stage() == TexasHoldemRoom.GameStage.Shuffle, "Wrong stage");
        uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
        uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
        require(currentPlayerIndex == playerIndex, "Not your turn to shuffle");

        // Store shuffle as an action?
        emit EncryptedShuffleSubmitted(msg.sender, encryptedShuffle);

        // Copy each element individually since direct array assignment is not supported
        for (uint8 i = 0; i < encryptedShuffle.length; i++) {
            encryptedDeck[i] = BigNumbers.init(encryptedShuffle[i], false);
        }
        texasHoldemRoom.progressGame();
    }

    // TODO(high): verify that the card indicies are valid for the number of players and stage
    function submitDecryptionValues(uint8[] memory cardIndexes, bytes[] memory decryptionValues)
        external
    {
        TexasHoldemRoom.GameStage stage = texasHoldemRoom.stage();
        require(
            stage == TexasHoldemRoom.GameStage.RevealDeal
                || stage == TexasHoldemRoom.GameStage.RevealFlop
                || stage == TexasHoldemRoom.GameStage.RevealTurn
                || stage == TexasHoldemRoom.GameStage.RevealRiver,
            "Game is not in a reveal stage"
        );
        uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
        uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
        require(currentPlayerIndex == playerIndex, "Not your turn to decrypt");
        require(
            cardIndexes.length == decryptionValues.length,
            "Mismatch in cardIndexes and decryptionValues lengths"
        );
        // TODO: verify decryption values?
        // TODO: verify decryption indexes?
        emit DecryptionValuesSubmitted(msg.sender, cardIndexes, decryptionValues);

        for (uint8 i = 0; i < cardIndexes.length; i++) {
            encryptedDeck[cardIndexes[i]] = BigNumbers.init(decryptionValues[i], false);
        }

        // The dealer always starts decrypting, so when we are back at the dealer,
        // we know the last player has decrypted community cards, so emit/set the community cards
        uint8 nextRevealPlayer = texasHoldemRoom.getNextActivePlayer(true);
        if (nextRevealPlayer == texasHoldemRoom.dealerPosition()) {
            if (stage == TexasHoldemRoom.GameStage.RevealFlop) {
                // convert the decrypted cards to a string
                string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
                string memory card2 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[1]]);
                string memory card3 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[2]]);
                emit FlopRevealed(card1, card2, card3);
                communityCards[0] = card1;
                communityCards[1] = card2;
                communityCards[2] = card3;
            } else if (stage == TexasHoldemRoom.GameStage.RevealTurn) {
                // convert the decrypted cards to a string
                string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
                emit TurnRevealed(card1);
                communityCards[3] = card1;
            } else if (stage == TexasHoldemRoom.GameStage.RevealRiver) {
                // convert the decrypted cards to a string
                string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
                emit RiverRevealed(card1);
                communityCards[4] = card1;
            }
        }

        texasHoldemRoom.progressGame();
    }

    /**
     * @dev Reveals the player's cards.
     * @dev Many todos: validate the encrypted cards, the card indexes for the player are valid (getCardIndexForPlayer in js),
     * @dev and that it is appropriate (showdown/all-in) to reveal cards.
     * @param c1 The player's encryption key c1 (derived from the private key)
     * @param privateKey The player's private key
     * @param c1InversePowPrivateKey The inverse of the player's c1*privateKey modulo inverse for decryption
     */
    function revealMyCards(
        bytes memory c1,
        bytes memory privateKey,
        bytes memory c1InversePowPrivateKey
    ) external returns (string memory card1, string memory card2) {
        uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
        TexasHoldemRoom.Player memory player = texasHoldemRoom.getPlayer(playerIndex);
        // todo: uncomment this
        require(!player.joinedAndWaitingForNextRound, "Player not joined after round started");
        require(!player.hasFolded, "Player folded");
        require(
            cryptoUtils.strEq(player.cards[0], ""),
            "Player already revealed cards (0) in this round"
        );
        require(
            cryptoUtils.strEq(player.cards[1], ""),
            "Player already revealed cards (1) in this round"
        );
        emit PlayerRevealingCards(msg.sender, c1, privateKey, c1InversePowPrivateKey);
        // scope block to reduce number of variables in memory (evm stack depth limited to 16 variables)
        {
            uint8[2] memory playerCardIndexes = texasHoldemRoom.getPlayersCardIndexes(playerIndex);
            // BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 2048);
            // BigNumber memory privateKeyBN = BigNumber({ val: privateKey, neg: false, bitlen: 256 });
            // BigNumber memory c1BN = BigNumber({ val: c1, neg: false, bitlen: 256 });
            BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 256);
            BigNumber memory c1BN = BigNumbers.init(c1, false, 256);
            BigNumber memory encryptedCard1BN = encryptedDeck[playerCardIndexes[0]];
            BigNumber memory encryptedCard2BN = encryptedDeck[playerCardIndexes[1]];
            CryptoUtils.EncryptedCard memory encryptedCard1Struct =
                CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard1BN });
            CryptoUtils.EncryptedCard memory encryptedCard2Struct =
                CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard2BN });
            BigNumber memory c1InversePowPrivateKeyBN =
                BigNumbers.init(c1InversePowPrivateKey, false, 256);
            BigNumber memory decryptedCard1 = cryptoUtils.verifyDecryptCard(
                encryptedCard1Struct, privateKeyBN, c1InversePowPrivateKeyBN
            );
            BigNumber memory decryptedCard2 = cryptoUtils.verifyDecryptCard(
                encryptedCard2Struct, privateKeyBN, c1InversePowPrivateKeyBN
            );

            // convert the decrypted cards to a string
            card1 = cryptoUtils.decodeBigintMessage(decryptedCard1);
            card2 = cryptoUtils.decodeBigintMessage(decryptedCard2);
        }
        player.cards[0] = card1;
        player.cards[1] = card2;

        // Get the player's hand score (using player's cards and community cards) from HandEvaluator
        // Combine player cards and community cards into a single array
        string[7] memory allCards;
        allCards[0] = card1;
        allCards[1] = card2;
        allCards[2] = communityCards[0];
        allCards[3] = communityCards[1];
        allCards[4] = communityCards[2];
        allCards[5] = communityCards[3];
        allCards[6] = communityCards[4];
        PokerHandEvaluatorv2.Hand memory playerHand = handEvaluator.findBestHandExternal(allCards);
        uint256 playerHandScore = playerHand.score;
        texasHoldemRoom.setPlayerHandScore(playerIndex, playerHandScore);

        emit PlayerCardsRevealed(
            address(msg.sender), card1, card2, playerHand.rank, playerHandScore
        );
        // emit THP_Log("emit PlayerCardsRevealed() in revealMyCards()");
        // return true if the encrypted cards match the decrypted cards from the deck?
        texasHoldemRoom.progressGame();
        // emit THP_Log("after texasHoldemRoom.progressGame()");
        return (card1, card2);
    }

    function getEncryptedDeck() external view returns (bytes[] memory) {
        bytes[] memory deckBytes = new bytes[](encryptedDeck.length);
        for (uint8 i = 0; i < encryptedDeck.length; i++) {
            deckBytes[i] = encryptedDeck[i].val;
        }
        return deckBytes;
    }

    function getEncrypedCard(uint256 cardIndex) external view returns (BigNumber memory) {
        return encryptedDeck[cardIndex];
    }

    function getCommunityCards() external view returns (string[5] memory) {
        return communityCards;
    }

    /**
     * @dev Returns all simple public variables of the TexasHoldemRoom contract and the encrypted deck
     * To reduce the size of the TexasHoldemRoom contract, this function is put here.
     */
    struct BulkRoomData {
        uint256 roundNumber;
        TexasHoldemRoom.GameStage stage;
        uint256 smallBlind;
        uint256 bigBlind;
        uint8 dealerPosition;
        uint8 currentPlayerIndex;
        uint8 lastRaiseIndex;
        uint256 pot;
        uint256 currentStageBet;
        uint8 numPlayers;
        bool isPrivate;
        string[5] communityCards;
        bytes[] encryptedDeck;
        uint256 lastActionTimestamp;
    }

    function getBulkRoomData() external view returns (BulkRoomData memory) {
        return BulkRoomData({
            roundNumber: texasHoldemRoom.roundNumber(),
            stage: texasHoldemRoom.stage(),
            smallBlind: texasHoldemRoom.smallBlind(),
            bigBlind: texasHoldemRoom.bigBlind(),
            dealerPosition: texasHoldemRoom.dealerPosition(),
            currentPlayerIndex: texasHoldemRoom.currentPlayerIndex(),
            lastRaiseIndex: texasHoldemRoom.lastRaiseIndex(),
            pot: texasHoldemRoom.pot(),
            currentStageBet: texasHoldemRoom.currentStageBet(),
            numPlayers: texasHoldemRoom.numPlayers(),
            isPrivate: texasHoldemRoom.isPrivate(),
            encryptedDeck: this.getEncryptedDeck(),
            communityCards: communityCards,
            lastActionTimestamp: texasHoldemRoom.lastActionTimestamp()
        });
    }
}

File 2 of 5 : BigNumbers.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

// Definition here allows both the lib and inheriting contracts to use BigNumber directly.
struct BigNumber {
    bytes val;
    bool neg;
    uint256 bitlen;
}

/**
 * @notice BigNumbers library for Solidity.
 */
library BigNumbers {
    /// @notice the value for number 0 of a BigNumber instance.
    bytes constant ZERO = hex"0000000000000000000000000000000000000000000000000000000000000000";
    /// @notice the value for number 1 of a BigNumber instance.
    bytes constant ONE = hex"0000000000000000000000000000000000000000000000000000000000000001";
    /// @notice the value for number 2 of a BigNumber instance.
    bytes constant TWO = hex"0000000000000000000000000000000000000000000000000000000000000002";

    // ***************** BEGIN EXPOSED MANAGEMENT FUNCTIONS ******************
    /**
     * @notice verify a BN instance
     *  @dev checks if the BN is in the correct format. operations should only be carried out on
     *       verified BNs, so it is necessary to call this if your function takes an arbitrary BN
     *       as input.
     *
     *  @param bn BigNumber instance
     */
    function verify(BigNumber memory bn) public pure {
        uint256 msword;
        bytes memory val = bn.val;
        assembly {
            msword := mload(add(val, 0x20))
        } //get msword of result
        if (msword == 0) require(isZero(bn));
        else require((bn.val.length % 32 == 0) && (msword >> ((bn.bitlen - 1) % 256) == 1));
    }

    /**
     * @notice initialize a BN instance
     *  @dev wrapper function for _init. initializes from bytes value.
     *       Allows passing bitLength of value. This is NOT verified in the public function. Only use where bitlen is
     *       explicitly known; otherwise use the other init function.
     *
     *  @param val BN value. may be of any size.
     *  @param neg neg whether the BN is +/-
     *  @param bitlen bit length of output.
     *  @return BigNumber instance
     */
    function init(bytes memory val, bool neg, uint256 bitlen)
        public
        view
        returns (BigNumber memory)
    {
        return _init(val, neg, bitlen);
    }

    /**
     * @notice initialize a BN instance
     *  @dev wrapper function for _init. initializes from bytes value.
     *
     *  @param val BN value. may be of any size.
     *  @param neg neg whether the BN is +/-
     *  @return BigNumber instance
     */
    function init(bytes memory val, bool neg) public view returns (BigNumber memory) {
        return _init(val, neg, 0);
    }

    /**
     * @notice initialize a BN instance
     *  @dev wrapper function for _init. initializes from uint value (converts to bytes);
     *       tf. resulting BN is in the range -2^256-1 ... 2^256-1.
     *
     *  @param val uint value.
     *  @param neg neg whether the BN is +/-
     *  @return BigNumber instance
     */
    function init(uint256 val, bool neg) public view returns (BigNumber memory) {
        return _init(abi.encodePacked(val), neg, 0);
    }
    // ***************** END EXPOSED MANAGEMENT FUNCTIONS ******************

    // ***************** BEGIN EXPOSED CORE CALCULATION FUNCTIONS ******************
    /**
     * @notice BigNumber addition: a + b.
     * @dev add: Initially prepare BigNumbers for addition operation; internally calls actual addition/subtraction,
     *           depending on inputs.
     *           In order to do correct addition or subtraction we have to handle the sign.
     *           This function discovers the sign of the result based on the inputs, and calls the correct operation.
     *
     * @param a first BN
     * @param b second BN
     * @return r result  - addition of a and b.
     */
    function add(BigNumber memory a, BigNumber memory b) public pure returns (BigNumber memory r) {
        if (a.bitlen == 0 && b.bitlen == 0) return zero();
        if (a.bitlen == 0) return b;
        if (b.bitlen == 0) return a;
        bytes memory val;
        uint256 bitlen;
        int256 compare = cmp(a, b, false);

        if (a.neg || b.neg) {
            if (a.neg && b.neg) {
                if (compare >= 0) (val, bitlen) = _add(a.val, b.val, a.bitlen);
                else (val, bitlen) = _add(b.val, a.val, b.bitlen);
                r.neg = true;
            } else {
                if (compare == 1) {
                    (val, bitlen) = privSub(a.val, b.val);
                    r.neg = a.neg;
                } else if (compare == -1) {
                    (val, bitlen) = privSub(b.val, a.val);
                    r.neg = !a.neg;
                } else {
                    return zero();
                } //one pos and one neg, and same value.
            }
        } else {
            if (compare >= 0) {
                // a>=b
                (val, bitlen) = _add(a.val, b.val, a.bitlen);
            } else {
                (val, bitlen) = _add(b.val, a.val, b.bitlen);
            }
            r.neg = false;
        }

        r.val = val;
        r.bitlen = (bitlen);
    }

    /**
     * @notice BigNumber subtraction: a - b.
     * @dev sub: Initially prepare BigNumbers for subtraction operation; internally calls actual addition/subtraction,
     *               depending on inputs.
     *           In order to do correct addition or subtraction we have to handle the sign.
     *           This function discovers the sign of the result based on the inputs, and calls the correct operation.
     *
     * @param a first BN
     * @param b second BN
     * @return r result - subtraction of a and b.
     */
    function sub(BigNumber memory a, BigNumber memory b) public pure returns (BigNumber memory r) {
        if (a.bitlen == 0 && b.bitlen == 0) return zero();
        bytes memory val;
        int256 compare;
        uint256 bitlen;
        compare = cmp(a, b, false);
        if (a.neg || b.neg) {
            if (a.neg && b.neg) {
                if (compare == 1) {
                    (val, bitlen) = privSub(a.val, b.val);
                    r.neg = true;
                } else if (compare == -1) {
                    (val, bitlen) = privSub(b.val, a.val);
                    r.neg = false;
                } else {
                    return zero();
                }
            } else {
                if (compare >= 0) (val, bitlen) = _add(a.val, b.val, a.bitlen);
                else (val, bitlen) = _add(b.val, a.val, b.bitlen);

                r.neg = (a.neg) ? true : false;
            }
        } else {
            if (compare == 1) {
                (val, bitlen) = privSub(a.val, b.val);
                r.neg = false;
            } else if (compare == -1) {
                (val, bitlen) = privSub(b.val, a.val);
                r.neg = true;
            } else {
                return zero();
            }
        }

        r.val = val;
        r.bitlen = (bitlen);
    }

    /**
     * @notice BigNumber multiplication: a * b.
     * @dev mul: takes two BigNumbers and multiplys them. Order is irrelevant.
     *              multiplication achieved using modexp precompile:
     *                 (a * b) = ((a + b)**2 - (a - b)**2) / 4
     *
     * @param a first BN
     * @param b second BN
     * @return r result - multiplication of a and b.
     */
    function mul(BigNumber memory a, BigNumber memory b) public view returns (BigNumber memory r) {
        BigNumber memory lhs = add(a, b);
        BigNumber memory fst = modexp(lhs, two(), _powModulus(lhs, 2)); // (a+b)^2

        // no need to do subtraction part of the equation if a == b; if so, it has no effect on final result.
        if (!eq(a, b)) {
            BigNumber memory rhs = sub(a, b);
            BigNumber memory snd = modexp(rhs, two(), _powModulus(rhs, 2)); // (a-b)^2
            r = _shr(sub(fst, snd), 2); // (a * b) = (((a + b)**2 - (a - b)**2) / 4
        } else {
            r = _shr(fst, 2); // a==b ? (((a + b)**2 / 4
        }
    }

    /**
     * @notice This function is not optimized for large (2048 bit) numbers and will break gas limits.
     * @notice BigNumber division: a / b.
     * @dev div: takes two BigNumbers and divides them.
     *      This is an expensive operation and should be used with caution.
     *      For verification of division results, use divVerify instead.
     *
     * @param a dividend BigNumber
     * @param b divisor BigNumber
     * @return r result BigNumber
     */
    function div(BigNumber memory a, BigNumber memory b) public view returns (BigNumber memory r) {
        // Check for division by zero
        require(!isZero(b.val), "Division by zero");

        // If a < b, result is 0
        if (cmp(a, b, false) == -1) {
            return zero();
        }

        // Determine sign of result
        bool resultNegative = (a.neg && !b.neg) || (!a.neg && b.neg);

        // Work with positive values for the algorithm
        BigNumber memory dividend = BigNumber(a.val, false, a.bitlen);
        BigNumber memory divisor = BigNumber(b.val, false, b.bitlen);

        // Initialize result
        BigNumber memory quotient = zero();
        BigNumber memory one_bn = one();

        // Binary search approach for division
        BigNumber memory low = one_bn;
        BigNumber memory high = dividend;

        while (cmp(low, high, false) <= 0) {
            BigNumber memory mid = _shr(add(low, high), 1);
            BigNumber memory product = mul(divisor, mid);

            int256 compareResult = cmp(product, dividend, false);

            if (compareResult == 0) {
                // Exact match
                quotient = mid;
                break;
            } else if (compareResult < 0) {
                // product < dividend, try higher
                quotient = mid;
                low = add(mid, one_bn);
            } else {
                // product > dividend, try lower
                high = sub(mid, one_bn);
            }
        }

        // Set the sign
        quotient.neg = resultNegative;

        return quotient;
    }

    /**
     * @notice This function is not optimized for large (2048 bit) numbers and will break gas limits.
     * @notice Modular inverse: finds x such that (a * x) % n = 1
     * @dev modInverse: Computes the modular multiplicative inverse of a modulo n.
     *      Uses the Extended Euclidean Algorithm to find the inverse.
     *      Returns zero if the inverse doesn't exist (when a and n are not coprime).
     *
     * @param a BigNumber to find inverse for
     * @param n modulus BigNumber
     * @return r result BigNumber - the modular inverse
     */
    function modInverse(BigNumber memory a, BigNumber memory n)
        public
        view
        returns (BigNumber memory)
    {
        // Check inputs
        require(!n.neg && !isZero(n.val), "Modulus must be positive");
        require(!a.neg, "Base must be positive");

        // Ensure a is within the modulus range
        BigNumber memory base = mod(a, n);

        // Special case: if base is 0, no inverse exists
        if (isZero(base.val)) {
            return zero();
        }

        // Special case: if base is 1, the inverse is 1
        if (eq(base, one())) {
            return one();
        }

        // Initialize values for Extended Euclidean Algorithm
        BigNumber memory r1 = BigNumber(n.val, false, n.bitlen);
        BigNumber memory r2 = base;
        BigNumber memory t1 = zero();
        BigNumber memory t2 = one();

        // Extended Euclidean Algorithm
        while (!isZero(r2.val)) {
            BigNumber memory quotient = div(r1, r2);

            // r1, r2 = r2, r1 - quotient * r2
            BigNumber memory temp_r = r2;
            r2 = sub(r1, mul(quotient, r2));
            r1 = temp_r;

            // t1, t2 = t2, t1 - quotient * t2
            BigNumber memory temp_t = t2;
            t2 = sub(t1, mul(quotient, t2));
            t1 = temp_t;
        }

        // Check if gcd is 1 (r1 should be 1 if inverse exists)
        if (!eq(r1, one())) {
            return zero(); // No inverse exists
        }

        // Make sure result is positive
        if (t1.neg) {
            t1 = add(t1, n);
        }

        return t1;
    }

    /**
     * @notice BigNumber division verification: a * b.
     * @dev div: takes three BigNumbers (a,b and result), and verifies that a/b == result.
     * Performing BigNumber division on-chain is a significantly expensive operation. As a result,
     * we expose the ability to verify the result of a division operation, which is a constant time operation.
     *              (a/b = result) == (a = b * result)
     *              Integer division only; therefore:
     *                verify ((b*result) + (a % (b*result))) == a.
     *              eg. 17/7 == 2:
     *                verify  (7*2) + (17 % (7*2)) == 17.
     * The function returns a bool on successful verification. The require statements will ensure that false can never
     *  be returned, however inheriting contracts may also want to put this function inside a require statement.
     *
     * @param a first BigNumber
     * @param b second BigNumber
     * @param r result BigNumber
     * @return bool whether or not the operation was verified
     */
    function divVerify(BigNumber memory a, BigNumber memory b, BigNumber memory r)
        public
        view
        returns (bool)
    {
        // first do zero check.
        // if a<b (always zero) and r==zero (input check), return true.
        if (cmp(a, b, false) == -1) {
            require(cmp(zero(), r, false) == 0);
            return true;
        }

        // Following zero check:
        //if both negative: result positive
        //if one negative: result negative
        //if neither negative: result positive
        bool positiveResult = (a.neg && b.neg) || (!a.neg && !b.neg);
        require(positiveResult ? !r.neg : r.neg);

        // require denominator to not be zero.
        require(!(cmp(b, zero(), true) == 0));

        // division result check assumes inputs are positive.
        // we have already checked for result sign so this is safe.
        bool[3] memory negs = [a.neg, b.neg, r.neg];
        a.neg = false;
        b.neg = false;
        r.neg = false;

        // do multiplication (b * r)
        BigNumber memory fst = mul(b, r);
        // check if we already have 'a' (ie. no remainder after division). if so, no mod necessary, and return true.
        if (cmp(fst, a, true) == 0) return true;
        //a mod (b*r)
        BigNumber memory snd = modexp(a, one(), fst);
        // ((b*r) + a % (b*r)) == a
        require(cmp(add(fst, snd), a, true) == 0);

        a.neg = negs[0];
        b.neg = negs[1];
        r.neg = negs[2];

        return true;
    }

    /**
     * @notice BigNumber exponentiation: a ^ b.
     * @dev pow: takes a BigNumber and a uint (a,e), and calculates a^e.
     * modexp precompile is used to achieve a^e; for this is work, we need to work out the minimum modulus value
     * such that the modulus passed to modexp is not used. the result of a^e can never be more than size bitlen(a) * e.
     *
     * @param a BigNumber
     * @param e exponent
     * @return r result BigNumber
     */
    function pow(BigNumber memory a, uint256 e) public view returns (BigNumber memory) {
        return modexp(a, init(e, false), _powModulus(a, e));
    }

    /**
     * @notice BigNumber modulus: a % n.
     * @dev mod: takes a BigNumber and modulus BigNumber (a,n), and calculates a % n.
     * modexp precompile is used to achieve a % n; an exponent of value '1' is passed.
     * @param a BigNumber
     * @param n modulus BigNumber
     * @return r result BigNumber
     */
    function mod(BigNumber memory a, BigNumber memory n) public view returns (BigNumber memory) {
        return modexp(a, one(), n);
    }

    /**
     * @notice BigNumber modular exponentiation: a^e mod n.
     * @dev modexp: takes base, exponent, and modulus, internally computes base^exponent % modulus using the precompile at address 0x5, and creates new BigNumber.
     *              this function is overloaded: it assumes the exponent is positive. if not, the other method is used, whereby the inverse of the base is also passed.
     *
     * @param a base BigNumber
     * @param e exponent BigNumber
     * @param n modulus BigNumber
     * @return result BigNumber
     */
    function modexp(BigNumber memory a, BigNumber memory e, BigNumber memory n)
        public
        view
        returns (BigNumber memory)
    {
        //if exponent is negative, other method with this same name should be used.
        //if modulus is negative or zero, we cannot perform the operation.
        require(e.neg == false && n.neg == false && !isZero(n.val));

        bytes memory _result = _modexp(a.val, e.val, n.val);
        //get bitlen of result (TODO: optimise. we know bitlen is in the same byte as the modulus bitlen byte)
        uint256 bitlen = bitLength(_result);

        // if result is 0, immediately return.
        if (bitlen == 0) return zero();
        // if base is negative AND exponent is odd, base^exp is negative, and tf. result is negative;
        // in that case we make the result positive by adding the modulus.
        if (a.neg && isOdd(e)) return add(BigNumber(_result, true, bitlen), n);
        // in any other case we return the positive result.
        return BigNumber(_result, false, bitlen);
    }

    /**
     * @notice BigNumber modular exponentiation with negative base: inv(a)==a_inv && a_inv^e mod n.
     * /** @dev modexp: takes base, base inverse, exponent, and modulus, asserts inverse(base)==base inverse,
     *              internally computes base_inverse^exponent % modulus and creates new BigNumber.
     *              this function is overloaded: it assumes the exponent is negative.
     *              if not, the other method is used, where the inverse of the base is not passed.
     *
     * @param a base BigNumber
     * @param ai base inverse BigNumber
     * @param e exponent BigNumber
     * @param a modulus
     * @return BigNumber memory result.
     */
    function modexp(BigNumber memory a, BigNumber memory ai, BigNumber memory e, BigNumber memory n)
        public
        view
        returns (BigNumber memory)
    {
        // base^-exp = (base^-1)^exp
        require(!a.neg && e.neg);

        //if modulus is negative or zero, we cannot perform the operation.
        require(!n.neg && !isZero(n.val));

        //base_inverse == inverse(base, modulus)
        require(modinvVerify(a, n, ai));

        bytes memory _result = _modexp(ai.val, e.val, n.val);
        //get bitlen of result (TODO: optimise. we know bitlen is in the same byte as the modulus bitlen byte)
        uint256 bitlen = bitLength(_result);

        // if result is 0, immediately return.
        if (bitlen == 0) return zero();
        // if base_inverse is negative AND exponent is odd, base_inverse^exp is negative, and tf. result is negative;
        // in that case we make the result positive by adding the modulus.
        if (ai.neg && isOdd(e)) return add(BigNumber(_result, true, bitlen), n);
        // in any other case we return the positive result.
        return BigNumber(_result, false, bitlen);
    }

    /**
     * @notice modular multiplication: (a*b) % n.
     * @dev modmul: Takes BigNumbers for a, b, and modulus, and computes (a*b) % modulus
     *              We call mul for the two input values, before calling modexp, passing exponent as 1.
     *              Sign is taken care of in sub-functions.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @param n Modulus BigNumber
     * @return result BigNumber
     */
    function modmul(BigNumber memory a, BigNumber memory b, BigNumber memory n)
        public
        view
        returns (BigNumber memory)
    {
        return mod(mul(a, b), n);
    }

    /**
     * @notice modular inverse verification: Verifies that (a*r) % n == 1.
     * @dev modinvVerify: Takes BigNumbers for base, modulus, and result, verifies (base*result)%modulus==1, and returns result.
     *              Similar to division, it's far cheaper to verify an inverse operation on-chain than it is to calculate it, so we allow the user to pass their own result.
     *
     * @param a base BigNumber
     * @param n modulus BigNumber
     * @param r result BigNumber
     * @return boolean result
     */
    function modinvVerify(BigNumber memory a, BigNumber memory n, BigNumber memory r)
        public
        view
        returns (bool)
    {
        require(!a.neg && !n.neg); //assert positivity of inputs.
        /*
         * the following proves:
         * - user result passed is correct for values base and modulus
         * - modular inverse exists for values base and modulus.
         * otherwise it fails.
         */
        require(
            cmp(modmul(a, r, n), one(), true) == 0,
            "BigNumbers.modinvVerify(): Invalid modular inverse"
        );

        return true;
    }
    // ***************** END EXPOSED CORE CALCULATION FUNCTIONS ******************

    // ***************** START EXPOSED HELPER FUNCTIONS ******************
    /**
     * @notice BigNumber odd number check
     * @dev isOdd: returns 1 if BigNumber value is an odd number and 0 otherwise.
     *
     * @param a BigNumber
     * @return r Boolean result
     */
    function isOdd(BigNumber memory a) public pure returns (bool r) {
        assembly {
            let a_ptr := add(mload(a), mload(mload(a))) // go to least significant word
            r := mod(mload(a_ptr), 2) // mod it with 2 (returns 0 or 1)
        }
    }

    /**
     * @notice BigNumber comparison
     * @dev cmp: Compares BigNumbers a and b. 'signed' parameter indiciates whether to consider the sign of the inputs.
     *           'trigger' is used to decide this -
     *              if both negative, invert the result;
     *              if both positive (or signed==false), trigger has no effect;
     *              if differing signs, we return immediately based on input.
     *           returns -1 on a<b, 0 on a==b, 1 on a>b.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @param signed whether to consider sign of inputs
     * @return int result
     */
    function cmp(BigNumber memory a, BigNumber memory b, bool signed)
        public
        pure
        returns (int256)
    {
        int256 trigger = 1;
        if (signed) {
            if (a.neg && b.neg) trigger = -1;
            else if (a.neg == false && b.neg == true) return 1;
            else if (a.neg == true && b.neg == false) return -1;
        }

        if (a.bitlen > b.bitlen) return trigger; // 1*trigger
        if (b.bitlen > a.bitlen) return -1 * trigger;

        uint256 a_ptr;
        uint256 b_ptr;
        uint256 a_word;
        uint256 b_word;

        uint256 len = a.val.length; //bitlen is same so no need to check length.

        assembly {
            a_ptr := add(mload(a), 0x20)
            b_ptr := add(mload(b), 0x20)
        }

        for (uint256 i = 0; i < len; i += 32) {
            assembly {
                a_word := mload(add(a_ptr, i))
                b_word := mload(add(b_ptr, i))
            }

            if (a_word > b_word) return trigger; // 1*trigger
            if (b_word > a_word) return -1 * trigger;
        }

        return 0; //same value.
    }

    /**
     * @notice BigNumber equality
     * @dev eq: returns true if a==b. sign always considered.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @return boolean result
     */
    function eq(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
        int256 result = cmp(a, b, true);
        return (result == 0) ? true : false;
    }

    /**
     * @notice BigNumber greater than
     * @dev eq: returns true if a>b. sign always considered.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @return boolean result
     */
    function gt(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
        int256 result = cmp(a, b, true);
        return (result == 1) ? true : false;
    }

    /**
     * @notice BigNumber greater than or equal to
     * @dev eq: returns true if a>=b. sign always considered.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @return boolean result
     */
    function gte(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
        int256 result = cmp(a, b, true);
        return (result == 1 || result == 0) ? true : false;
    }

    /**
     * @notice BigNumber less than
     * @dev eq: returns true if a<b. sign always considered.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @return boolean result
     */
    function lt(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
        int256 result = cmp(a, b, true);
        return (result == -1) ? true : false;
    }

    /**
     * @notice BigNumber less than or equal o
     * @dev eq: returns true if a<=b. sign always considered.
     *
     * @param a BigNumber
     * @param b BigNumber
     * @return boolean result
     */
    function lte(BigNumber memory a, BigNumber memory b) public pure returns (bool) {
        int256 result = cmp(a, b, true);
        return (result == -1 || result == 0) ? true : false;
    }

    /**
     * @notice right shift BigNumber value
     * @dev shr: right shift BigNumber a by 'bits' bits.
     *          copies input value to new memory location before shift and calls _shr function after.
     * @param a BigNumber value to shift
     * @param bits amount of bits to shift by
     * @return result BigNumber
     */
    function pubShr(BigNumber memory a, uint256 bits) public view returns (BigNumber memory) {
        require(!a.neg);
        return _shr(a, bits);
    }

    /**
     * @notice right shift BigNumber memory 'dividend' by 'bits' bits.
     * @dev _shr: Shifts input value in-place, ie. does not create new memory. shr function does this.
     * right shift does not necessarily have to copy into a new memory location. where the user wishes the modify
     * the existing value they have in place, they can use this.
     * @param bn value to shift
     * @param bits amount of bits to shift by
     * @return r result
     */
    function _shr(BigNumber memory bn, uint256 bits) public view returns (BigNumber memory) {
        uint256 length;
        assembly {
            length := mload(mload(bn))
        }

        // if bits is >= the bitlength of the value the result is always 0
        if (bits >= bn.bitlen) return BigNumber(ZERO, false, 0);

        // set bitlen initially as we will be potentially modifying 'bits'
        bn.bitlen = bn.bitlen - (bits);

        // handle shifts greater than 256:
        // if bits is greater than 256 we can simply remove any trailing words, by altering the BN length.
        // we also update 'bits' so that it is now in the range 0..256.
        assembly {
            if or(gt(bits, 0x100), eq(bits, 0x100)) {
                length := sub(length, mul(div(bits, 0x100), 0x20))
                mstore(mload(bn), length)
                bits := mod(bits, 0x100)
            }

            // if bits is multiple of 8 (byte size), we can simply use identity precompile for cheap memcopy.
            // otherwise we shift each word, starting at the least signifcant word, one-by-one using the mask technique.
            // TODO it is possible to do this without the last two operations, see SHL identity copy.
            let bn_val_ptr := mload(bn)
            switch eq(mod(bits, 8), 0)
            case 1 {
                let bytes_shift := div(bits, 8)
                let in := mload(bn)
                let inlength := mload(in)
                let insize := add(inlength, 0x20)
                let out := add(in, bytes_shift)
                let outsize := sub(insize, bytes_shift)
                let success := staticcall(450, 0x4, in, insize, out, insize)
                mstore8(add(out, 0x1f), 0) // maintain our BN layout following identity call:
                mstore(in, inlength) // set current length byte to 0, and reset old length.
            }
            default {
                let mask
                let lsw
                let mask_shift := sub(0x100, bits)
                let lsw_ptr := add(bn_val_ptr, length)
                for { let i := length } eq(eq(i, 0), 0) { i := sub(i, 0x20) } {
                    // for(int i=max_length; i!=0; i-=32)
                    switch eq(i, 0x20)
                    // if i==32:
                    case 1 { mask := 0 }
                    //    - handles lsword: no mask needed.
                    default { mask := mload(sub(lsw_ptr, 0x20)) } //    - else get mask (previous word)
                    lsw := shr(bits, mload(lsw_ptr)) // right shift current by bits
                    mask := shl(mask_shift, mask) // left shift next significant word by mask_shift
                    mstore(lsw_ptr, or(lsw, mask)) // store OR'd mask and shifted bits in-place
                    lsw_ptr := sub(lsw_ptr, 0x20) // point to next bits.
                }
            }

            // The following removes the leading word containing all zeroes in the result should it exist,
            // as well as updating lengths and pointers as necessary.
            let msw_ptr := add(bn_val_ptr, 0x20)
            switch eq(mload(msw_ptr), 0)
            case 1 {
                mstore(msw_ptr, sub(mload(bn_val_ptr), 0x20)) // store new length in new position
                mstore(bn, msw_ptr) // update pointer from bn
            }
            default { }
        }

        return bn;
    }

    /**
     * @notice left shift BigNumber value
     * @dev shr: left shift BigNumber a by 'bits' bits.
     *               ensures the value is not negative before calling the private function.
     * @param a BigNumber value to shift
     * @param bits amount of bits to shift by
     * @return result BigNumber
     */
    function shl(BigNumber memory a, uint256 bits) public view returns (BigNumber memory) {
        require(!a.neg);
        return _shl(a, bits);
    }

    /**
     * @notice sha3 hash a BigNumber.
     * @dev hash: takes a BigNumber and performs sha3 hash on it.
     *            we hash each BigNumber WITHOUT it's first word - first word is a pointer to the start of the bytes value,
     *            and so is different for each struct.
     *
     * @param a BigNumber
     * @return h bytes32 hash.
     */
    function hash(BigNumber memory a) public pure returns (bytes32 h) {
        //amount of words to hash = all words of the value and three extra words: neg, bitlen & value length.
        assembly {
            h := keccak256(add(a, 0x20), add(mload(mload(a)), 0x60))
        }
    }

    /**
     * @notice BigNumber full zero check
     * @dev isZero: checks if the BigNumber is in the default zero format for BNs (ie. the result from zero()).
     *
     * @param a BigNumber
     * @return boolean result.
     */
    function isZero(BigNumber memory a) public pure returns (bool) {
        return isZero(a.val) && a.val.length == 0x20 && !a.neg && a.bitlen == 0;
    }

    /**
     * @notice bytes zero check
     * @dev isZero: checks if input bytes value resolves to zero.
     *
     * @param a bytes value
     * @return boolean result.
     */
    function isZero(bytes memory a) public pure returns (bool) {
        uint256 msword;
        uint256 msword_ptr;
        assembly {
            msword_ptr := add(a, 0x20)
        }
        for (uint256 i = 0; i < a.length; i += 32) {
            assembly {
                msword := mload(msword_ptr)
            } // get msword of input
            if (msword > 0) return false;
            assembly {
                msword_ptr := add(msword_ptr, 0x20)
            }
        }
        return true;
    }

    /**
     * @notice BigNumber value bit length
     * @dev bitLength: returns BigNumber value bit length- ie. log2 (most significant bit of value)
     *
     * @param a BigNumber
     * @return uint bit length result.
     */
    function bitLength(BigNumber memory a) public pure returns (uint256) {
        return bitLength(a.val);
    }

    /**
     * @notice bytes bit length
     * @dev bitLength: returns bytes bit length- ie. log2 (most significant bit of value)
     *
     * @param a bytes value
     * @return r uint bit length result.
     */
    function bitLength(bytes memory a) public pure returns (uint256 r) {
        if (isZero(a)) return 0;
        uint256 msword;
        assembly {
            msword := mload(add(a, 0x20)) // get msword of input
        }
        r = bitLength(msword); // get bitlen of msword, add to size of remaining words.
        assembly {
            r := add(r, mul(sub(mload(a), 0x20), 8)) // res += (val.length-32)*8;
        }
    }

    /**
     * @notice uint bit length
     *     @dev bitLength: get the bit length of a uint input - ie. log2 (most significant bit of 256 bit value (one EVM word))
     *                       credit: Tjaden Hess @ ethereum.stackexchange
     * @param a uint value
     * @return r uint bit length result.
     */
    function bitLength(uint256 a) public pure returns (uint256 r) {
        assembly {
            switch eq(a, 0)
            case 1 { r := 0 }
            default {
                let arg := a
                a := sub(a, 1)
                a := or(a, div(a, 0x02))
                a := or(a, div(a, 0x04))
                a := or(a, div(a, 0x10))
                a := or(a, div(a, 0x100))
                a := or(a, div(a, 0x10000))
                a := or(a, div(a, 0x100000000))
                a := or(a, div(a, 0x10000000000000000))
                a := or(a, div(a, 0x100000000000000000000000000000000))
                a := add(a, 1)
                let m := mload(0x40)
                mstore(m, 0xf8f9cbfae6cc78fbefe7cdc3a1793dfcf4f0e8bbd8cec470b6a28a7a5a3e1efd)
                mstore(
                    add(m, 0x20), 0xf5ecf1b3e9debc68e1d9cfabc5997135bfb7a7a3938b7b606b5b4b3f2f1f0ffe
                )
                mstore(
                    add(m, 0x40), 0xf6e4ed9ff2d6b458eadcdf97bd91692de2d4da8fd2d0ac50c6ae9a8272523616
                )
                mstore(
                    add(m, 0x60), 0xc8c0b887b0a8a4489c948c7f847c6125746c645c544c444038302820181008ff
                )
                mstore(
                    add(m, 0x80), 0xf7cae577eec2a03cf3bad76fb589591debb2dd67e0aa9834bea6925f6a4a2e0e
                )
                mstore(
                    add(m, 0xa0), 0xe39ed557db96902cd38ed14fad815115c786af479b7e83247363534337271707
                )
                mstore(
                    add(m, 0xc0), 0xc976c13bb96e881cb166a933a55e490d9d56952b8d4e801485467d2362422606
                )
                mstore(
                    add(m, 0xe0), 0x753a6d1b65325d0c552a4d1345224105391a310b29122104190a110309020100
                )
                mstore(0x40, add(m, 0x100))
                let magic := 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff
                let shift := 0x100000000000000000000000000000000000000000000000000000000000000
                let _a := div(mul(a, magic), shift)
                r := div(mload(add(m, sub(255, _a))), shift)
                r :=
                    add(
                        r,
                        mul(
                            256,
                            gt(arg, 0x8000000000000000000000000000000000000000000000000000000000000000)
                        )
                    )
                // where a is a power of two, result needs to be incremented. we use the power of two trick here: if(arg & arg-1 == 0) ++r;
                if eq(and(arg, sub(arg, 1)), 0) { r := add(r, 1) }
            }
        }
    }

    /**
     * @notice BigNumber zero value
     *     @dev zero: returns zero encoded as a BigNumber
     * @return zero encoded as BigNumber
     */
    function zero() public pure returns (BigNumber memory) {
        return BigNumber(ZERO, false, 0);
    }

    /**
     * @notice BigNumber one value
     *     @dev one: returns one encoded as a BigNumber
     * @return one encoded as BigNumber
     */
    function one() public pure returns (BigNumber memory) {
        return BigNumber(ONE, false, 1);
    }

    /**
     * @notice BigNumber two value
     *     @dev two: returns two encoded as a BigNumber
     * @return two encoded as BigNumber
     */
    function two() public pure returns (BigNumber memory) {
        return BigNumber(TWO, false, 2);
    }
    // ***************** END EXPOSED HELPER FUNCTIONS ******************

    // ***************** START PRIVATE MANAGEMENT FUNCTIONS ******************
    /**
     * @notice Create a new BigNumber.
     *     @dev init: overloading allows caller to obtionally pass bitlen where it is known - as it is cheaper to do off-chain and verify on-chain.
     *            we assert input is in data structure as defined above, and that bitlen, if passed, is correct.
     *            'copy' parameter indicates whether or not to copy the contents of val to a new location in memory (for example where you pass
     *            the contents of another variable's value in)
     * @param val bytes - bignum value.
     * @param neg bool - sign of value
     * @param bitlen uint - bit length of value
     * @return r BigNumber initialized value.
     */
    function _init(bytes memory val, bool neg, uint256 bitlen)
        private
        view
        returns (BigNumber memory r)
    {
        // use identity at location 0x4 for cheap memcpy.
        // grab contents of val, load starting from memory end, update memory end pointer.
        assembly {
            let data := add(val, 0x20)
            let length := mload(val)
            let out
            let freemem := msize()
            switch eq(mod(length, 0x20), 0)
            // if(val.length % 32 == 0)
            case 1 {
                out := add(freemem, 0x20) // freememory location + length word
                mstore(freemem, length) // set new length
            }
            default {
                let offset := sub(0x20, mod(length, 0x20)) // offset: 32 - (length % 32)
                out := add(add(freemem, offset), 0x20) // freememory location + offset + length word
                mstore(freemem, add(length, offset)) // set new length
            }
            pop(staticcall(450, 0x4, data, length, out, length)) // copy into 'out' memory location
            mstore(0x40, add(freemem, add(mload(freemem), 0x20))) // update the free memory pointer

            // handle leading zero words. assume freemem is pointer to bytes value
            let bn_length := mload(freemem)
            for { } eq(eq(bn_length, 0x20), 0) { } {
                // for(; length!=32; length-=32)
                switch eq(mload(add(freemem, 0x20)), 0)
                // if(msword==0):
                case 1 { freemem := add(freemem, 0x20) }
                //     update length pointer
                default { break } // else: loop termination. non-zero word found
                bn_length := sub(bn_length, 0x20)
            }
            mstore(freemem, bn_length)

            mstore(r, freemem) // store new bytes value in r
            mstore(add(r, 0x20), neg) // store neg value in r
        }

        r.bitlen = bitlen == 0 ? bitLength(r.val) : bitlen;
    }
    // ***************** END PRIVATE MANAGEMENT FUNCTIONS ******************

    // ***************** START PRIVATE CORE CALCULATION FUNCTIONS ******************
    /**
     * @notice takes two BigNumber memory values and the bitlen of the max value, and adds them.
     * @dev _add: This function is private and only callable from add: therefore the values may be of different sizes,
     *            in any order of size, and of different signs (handled in add).
     *            As values may be of different sizes, inputs are considered starting from the least significant
     *            words, working back.
     *            The function calculates the new bitlen (basically if bitlens are the same for max and min,
     *            max_bitlen++) and returns a new BigNumber memory value.
     *
     * @param max bytes -  biggest value  (determined from add)
     * @param min bytes -  smallest value (determined from add)
     * @param max_bitlen uint - bit length of max value.
     * @return bytes result - max + min.
     * @return uint - bit length of result.
     */
    function _add(bytes memory max, bytes memory min, uint256 max_bitlen)
        private
        pure
        returns (bytes memory, uint256)
    {
        bytes memory result;
        assembly {
            let result_start := msize() // Get the highest available block of memory
            let carry := 0
            let uint_max := sub(0, 1)

            let max_ptr := add(max, mload(max))
            let min_ptr := add(min, mload(min)) // point to last word of each byte array.

            let result_ptr := add(add(result_start, 0x20), mload(max)) // set result_ptr end.

            for { let i := mload(max) } eq(eq(i, 0), 0) { i := sub(i, 0x20) } {
                // for(int i=max_length; i!=0; i-=32)
                let max_val := mload(max_ptr) // get next word for 'max'
                switch gt(i, sub(mload(max), mload(min)))
                // if(i>(max_length-min_length)). while
                // 'min' words are still available.
                case 1 {
                    let min_val := mload(min_ptr) //      get next word for 'min'
                    mstore(result_ptr, add(add(max_val, min_val), carry)) //      result_word = max_word+min_word+carry
                    switch gt(max_val, sub(uint_max, sub(min_val, carry)))
                    //      this switch block finds whether or
                    //      not to set the carry bit for the
                    //      next iteration.
                    case 1 { carry := 1 }
                    default {
                        switch and(eq(max_val, uint_max), or(gt(carry, 0), gt(min_val, 0)))
                        case 1 { carry := 1 }
                        default { carry := 0 }
                    }

                    min_ptr := sub(min_ptr, 0x20) //       point to next 'min' word
                }
                default {
                    // else: remainder after 'min' words are complete.
                    mstore(result_ptr, add(max_val, carry)) //       result_word = max_word+carry

                    switch and(eq(uint_max, max_val), eq(carry, 1))
                    //       this switch block finds whether or
                    //       not to set the carry bit for the
                    //       next iteration.
                    case 1 { carry := 1 }
                    default { carry := 0 }
                }
                result_ptr := sub(result_ptr, 0x20) // point to next 'result' word
                max_ptr := sub(max_ptr, 0x20) // point to next 'max' word
            }

            switch eq(carry, 0)
            case 1 { result_start := add(result_start, 0x20) }
            // if carry is 0, increment result_start, ie.
            // length word for result is now one word
            // position ahead.
            default { mstore(result_ptr, 1) } // else if carry is 1, store 1; overflow has
                // occured, so length word remains in the
                // same position.

            result := result_start // point 'result' bytes value to the correct
                // address in memory.
            mstore(result, add(mload(max), mul(0x20, carry))) // store length of result. we are finished
                // with the byte array.

            mstore(0x40, add(result, add(mload(result), 0x20))) // Update freemem pointer to point to new
                // end of memory.

            // we now calculate the result's bit length.
            // with addition, if we assume that some a is at least equal to some b, then the resulting bit length will
            // be a's bit length or (a's bit length)+1, depending on carry bit.this is cheaper than calling bitLength.
            let msword := mload(add(result, 0x20)) // get most significant word of result
            // if(carry==1 || msword>>(max_bitlen % 256)==1):
            if or(eq(carry, 1), eq(shr(mod(max_bitlen, 256), msword), 1)) {
                max_bitlen := add(max_bitlen, 1)
            } // if msword's bit length is 1 greater
                // than max_bitlen, OR overflow occured,
                // new bitlen is max_bitlen+1.
        }

        return (result, max_bitlen);
    }

    /**
     * @notice takes two BigNumber memory values and subtracts them.
     * @dev privSub: This function is private and only callable from add: therefore the values may be of different sizes,
     *            in any order of size, and of different signs (handled in add).
     *            As values may be of different sizes, inputs are considered starting from the least significant words,
     *            working back.
     *            The function calculates the new bitlen (basically if bitlens are the same for max and min,
     *            max_bitlen++) and returns a new BigNumber memory value.
     *
     * @param max bytes -  biggest value  (determined from add)
     * @param min bytes -  smallest value (determined from add)
     * @return bytes result - max + min.
     * @return uint - bit length of result.
     */
    function privSub(bytes memory max, bytes memory min)
        public
        pure
        returns (bytes memory, uint256)
    {
        bytes memory result;
        uint256 carry = 0;
        uint256 uint_max = type(uint256).max;
        assembly {
            let result_start := msize() // Get the highest available block of
                // memory

            let max_len := mload(max)
            let min_len := mload(min) // load lengths of inputs

            let len_diff := sub(max_len, min_len) // get differences in lengths.

            let max_ptr := add(max, max_len)
            let min_ptr := add(min, min_len) // go to end of arrays
            let result_ptr := add(result_start, max_len) // point to least significant result
                // word.
            let memory_end := add(result_ptr, 0x20) // save memory_end to update free memory
                // pointer at the end.

            for { let i := max_len } eq(eq(i, 0), 0) { i := sub(i, 0x20) } {
                // for(int i=max_length; i!=0; i-=32)
                let max_val := mload(max_ptr) // get next word for 'max'
                switch gt(i, len_diff)
                // if(i>(max_length-min_length)). while
                // 'min' words are still available.
                case 1 {
                    let min_val := mload(min_ptr) //  get next word for 'min'

                    mstore(result_ptr, sub(sub(max_val, min_val), carry)) //  result_word = (max_word-min_word)-carry

                    switch or(
                        lt(max_val, add(min_val, carry)), and(eq(min_val, uint_max), eq(carry, 1))
                    )
                    //  this switch block finds whether or
                    //  not to set the carry bit for the next iteration.
                    case 1 { carry := 1 }
                    default { carry := 0 }

                    min_ptr := sub(min_ptr, 0x20) //  point to next 'result' word
                }
                default {
                    // else: remainder after 'min' words are complete.
                    mstore(result_ptr, sub(max_val, carry)) //      result_word = max_word-carry

                    switch and(eq(max_val, 0), eq(carry, 1))
                    //      this switch block finds whether or
                    //      not to set the carry bit for the
                    //      next iteration.
                    case 1 { carry := 1 }
                    default { carry := 0 }
                }
                result_ptr := sub(result_ptr, 0x20) // point to next 'result' word
                max_ptr := sub(max_ptr, 0x20) // point to next 'max' word
            }

            //the following code removes any leading words containing all zeroes in the result.
            result_ptr := add(result_ptr, 0x20)

            // for(result_ptr+=32;; result==0; result_ptr+=32)
            for { } eq(mload(result_ptr), 0) { result_ptr := add(result_ptr, 0x20) } {
                result_start := add(result_start, 0x20) // push up the start pointer for the result
                max_len := sub(max_len, 0x20) // subtract a word (32 bytes) from the
                    // result length.
            }

            result := result_start // point 'result' bytes value to
                // the correct address in memory

            mstore(result, max_len) // store length of result. we
                // are finished with the byte array.

            mstore(0x40, memory_end) // Update freemem pointer.
        }

        uint256 new_bitlen = bitLength(result); // calculate the result's
            // bit length.

        return (result, new_bitlen);
    }

    /**
     * @notice gets the modulus value necessary for calculating exponetiation.
     * @dev _powModulus: we must pass the minimum modulus value which would return JUST the a^b part of the calculation
     *       in modexp. the rationale here is:
     *       if 'a' has n bits, then a^e has at most n*e bits.
     *       using this modulus in exponetiation will result in simply a^e.
     *       therefore the value may be many words long.
     *       This is done by:
     *         - storing total modulus byte length
     *         - storing first word of modulus with correct bit set
     *         - updating the free memory pointer to come after total length.
     *
     * @param a BigNumber base
     * @param e uint exponent
     * @return BigNumber modulus result
     */
    function _powModulus(BigNumber memory a, uint256 e) private pure returns (BigNumber memory) {
        bytes memory _modulus = ZERO;
        uint256 mod_index;

        assembly {
            mod_index := mul(mload(add(a, 0x40)), e) // a.bitlen * e is the max bitlength of result
            let first_word_modulus := shl(mod(mod_index, 256), 1) // set bit in first modulus word.
            mstore(_modulus, mul(add(div(mod_index, 256), 1), 0x20)) // store length of modulus
            mstore(add(_modulus, 0x20), first_word_modulus) // set first modulus word
            mstore(0x40, add(_modulus, add(mload(_modulus), 0x20))) // update freemem pointer to be modulus index
                // + length
        }

        //create modulus BigNumber memory for modexp function
        return BigNumber(_modulus, false, mod_index);
    }

    /**
     * @notice Modular Exponentiation: Takes bytes values for base, exp, mod and calls precompile for (base^exp)%^mod
     * @dev modexp: Wrapper for built-in modexp (contract 0x5) as described here:
     *              https://github.com/ethereum/EIPs/pull/198
     *
     * @param _b bytes base
     * @param _e bytes base_inverse
     * @param _m bytes exponent
     * @param r bytes result.
     */
    function _modexp(bytes memory _b, bytes memory _e, bytes memory _m)
        private
        view
        returns (bytes memory r)
    {
        assembly {
            let bl := mload(_b)
            let el := mload(_e)
            let ml := mload(_m)

            let freemem := mload(0x40) // Free memory pointer is always stored at 0x40

            mstore(freemem, bl) // arg[0] = base.length @ +0

            mstore(add(freemem, 32), el) // arg[1] = exp.length @ +32

            mstore(add(freemem, 64), ml) // arg[2] = mod.length @ +64

            // arg[3] = base.bits @ + 96
            // Use identity built-in (contract 0x4) as a cheap memcpy
            let success := staticcall(450, 0x4, add(_b, 32), bl, add(freemem, 96), bl)

            // arg[4] = exp.bits @ +96+base.length
            let size := add(96, bl)
            success := staticcall(450, 0x4, add(_e, 32), el, add(freemem, size), el)

            // arg[5] = mod.bits @ +96+base.length+exp.length
            size := add(size, el)
            success := staticcall(450, 0x4, add(_m, 32), ml, add(freemem, size), ml)

            switch success
            case 0 { invalid() } //fail where we haven't enough gas to make the call

            // Total size of input = 96+base.length+exp.length+mod.length
            size := add(size, ml)
            // Invoke contract 0x5, put return value right after mod.length, @ +96
            success := staticcall(sub(gas(), 1350), 0x5, freemem, size, add(freemem, 0x60), ml)

            switch success
            case 0 { invalid() } //fail where we haven't enough gas to make the call

            let length := ml
            let msword_ptr := add(freemem, 0x60)

            ///the following code removes any leading words containing all zeroes in the result.
            for { } eq(eq(length, 0x20), 0) { } {
                // for(; length!=32; length-=32)
                switch eq(mload(msword_ptr), 0)
                // if(msword==0):
                case 1 { msword_ptr := add(msword_ptr, 0x20) }
                //     update length pointer
                default { break } // else: loop termination. non-zero word found
                length := sub(length, 0x20)
            }
            r := sub(msword_ptr, 0x20)
            mstore(r, length)

            // point to the location of the return value (length, bits)
            //assuming mod length is multiple of 32, return value is already in the right format.
            mstore(0x40, add(add(96, freemem), ml)) //deallocate freemem pointer
        }
    }
    // ***************** END PRIVATE CORE CALCULATION FUNCTIONS ******************

    // ***************** START PRIVATE HELPER FUNCTIONS ******************
    /**
     * @notice left shift BigNumber memory 'dividend' by 'value' bits.
     * @param bn value to shift
     * @param bits amount of bits to shift by
     * @return r result
     */
    function _shl(BigNumber memory bn, uint256 bits) private view returns (BigNumber memory r) {
        if (bits == 0 || bn.bitlen == 0) return bn;

        // we start by creating an empty bytes array of the size of the output, based on 'bits'.
        // for that we must get the amount of extra words needed for the output.
        uint256 length = bn.val.length;
        // position of bitlen in most significnat word
        uint256 bit_position = ((bn.bitlen - 1) % 256) + 1;
        // total extra words. we check if the bits remainder will add one more word.
        uint256 extra_words = (bits / 256) + ((bits % 256) >= (256 - bit_position) ? 1 : 0);
        // length of output
        uint256 total_length = length + (extra_words * 0x20);

        r.bitlen = bn.bitlen + (bits);
        r.neg = bn.neg;
        bits %= 256;

        bytes memory bn_shift;
        uint256 bn_shift_ptr;
        // the following efficiently creates an empty byte array of size 'total_length'
        assembly {
            let freemem_ptr := mload(0x40) // get pointer to free memory
            mstore(freemem_ptr, total_length) // store bytes length
            let mem_end := add(freemem_ptr, total_length) // end of memory
            mstore(mem_end, 0) // store 0 at memory end
            bn_shift := freemem_ptr // set pointer to bytes
            bn_shift_ptr := add(bn_shift, 0x20) // get bn_shift pointer
            mstore(0x40, add(mem_end, 0x20)) // update freemem pointer
        }

        // use identity for cheap copy if bits is multiple of 8.
        if (bits % 8 == 0) {
            // calculate the position of the first byte in the result.
            uint256 bytes_pos = ((256 - (((bn.bitlen - 1) + bits) % 256)) - 1) / 8;
            uint256 insize = (bn.bitlen / 8) + ((bn.bitlen % 8 != 0) ? 1 : 0);
            assembly {
                let in := add(add(mload(bn), 0x20), div(sub(256, bit_position), 8))
                let out := add(bn_shift_ptr, bytes_pos)
                let success := staticcall(450, 0x4, in, insize, out, length)
            }
            r.val = bn_shift;
            return r;
        }

        uint256 mask;
        uint256 mask_shift = 0x100 - bits;
        uint256 msw;
        uint256 msw_ptr;

        assembly {
            msw_ptr := add(mload(bn), 0x20)
        }

        // handle first word before loop if the shift adds any extra words.
        // the loop would handle it if the bit shift doesn't wrap into the next word,
        // so we check only for that condition.
        if ((bit_position + bits) > 256) {
            assembly {
                msw := mload(msw_ptr)
                mstore(bn_shift_ptr, shr(mask_shift, msw))
                bn_shift_ptr := add(bn_shift_ptr, 0x20)
            }
        }

        // as a result of creating the empty array we just have to operate on the words in the original bn.
        for (uint256 i = bn.val.length; i != 0; i -= 0x20) {
            // for each word:
            assembly {
                msw := mload(msw_ptr) // get most significant word
                switch eq(i, 0x20)
                // if i==32:
                case 1 { mask := 0 }
                // handles msword: no mask needed.
                default { mask := mload(add(msw_ptr, 0x20)) } // else get mask (next word)
                msw := shl(bits, msw) // left shift current msw by 'bits'
                mask := shr(mask_shift, mask) // right shift next significant word by mask_shift
                mstore(bn_shift_ptr, or(msw, mask)) // store OR'd mask and shifted bits in-place
                msw_ptr := add(msw_ptr, 0x20)
                bn_shift_ptr := add(bn_shift_ptr, 0x20)
            }
        }

        r.val = bn_shift;
    }
    // ***************** END PRIVATE HELPER FUNCTIONS ******************
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import "./BigNumbers/BigNumbers.sol";
import "./TexasHoldemRoom.sol";
/**
 * @title CryptoUtils
 * @dev Implements cryptographic utilities for mental poker
 */

contract CryptoUtils {
    using BigNumbers for BigNumber;

    // 2048-bit prime number
    BigNumber private P_2048;
    uint256 constant G_2048 = 2;
    uint256 public constant MAX_PLAYERS = 10;
    uint8 public constant EMPTY_SEAT = 255;

    event CULog(string message);

    constructor() {
        // Initialize P_2048
        // bytes memory p2048Bytes =
        //     hex"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718399549CCEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
        bytes memory p2048Bytes =
            hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1";
        P_2048 = BigNumbers.init(p2048Bytes, false, 256);
    }

    struct EncryptedCard {
        BigNumber c1; // 2048-bit number
        BigNumber c2; // 2048-bit number
    }

    // May no longer be accurate
    // /**
    //  * @dev Decrypts a card using ElGamal decryption
    //  * @param encryptedCard The encrypted card (c1, c2)
    //  * @param privateKey The private key of the player decrypting the card
    //  * @return The decrypted card
    //  */
    // function decryptCard(EncryptedCard memory encryptedCard, uint256 privateKey)
    //     public
    //     view
    //     returns (BigNumber memory)
    // {
    //     BigNumber memory c1PowPrivateKey =
    //         BigNumbers.modexp(encryptedCard.c1, BigNumbers.init(privateKey, false), P_2048);
    //     BigNumber memory c1Inverse = modInverse(c1PowPrivateKey, P_2048);
    //     return BigNumbers.modmul(encryptedCard.c2, c1Inverse, P_2048);
    // }

    // TODO: modify this function to just verify the modular inverse and have the user supply the result (c1Inverse)
    /**
     * @dev Decrypts a card using ElGamal decryption
     * @param encryptedCard The encrypted card (c1, c2)
     * @param privateKey The private key of the player decrypting the card
     * @return The decrypted card
     */
    function verifyDecryptCard(
        EncryptedCard memory encryptedCard,
        BigNumber memory privateKey,
        BigNumber memory c1InversePowPrivateKey
    ) public returns (BigNumber memory) {
        BigNumber memory c1PowPrivateKey = BigNumbers.modexp(encryptedCard.c1, privateKey, P_2048);
        emit CULog("c1PowPrivateKey");
        bool verifyResult = BigNumbers.modinvVerify(c1PowPrivateKey, P_2048, c1InversePowPrivateKey);
        emit CULog("verifyResult");
        require(verifyResult, "Invalid modular inverse");
        emit CULog("modmul");
        return BigNumbers.modmul(encryptedCard.c2, c1InversePowPrivateKey, P_2048);
    }

    // This is on chain in case of a dispute and we want to verify that a user correctly encrypted each card
    /**
     * @dev Encrypts a message using ElGamal encryption
     * @param message The message to encrypt (2048-bit)
     * @param publicKey The public key to encrypt with
     * @param r Optional random value (if not provided, will be generated)
     * @return The encrypted message (c1, c2)
     */
    function encryptMessageBigint(BigNumber memory message, uint256 publicKey, uint256 r)
        public
        view
        returns (EncryptedCard memory)
    {
        BigNumber memory rToUse;
        if (r == 0) {
            // Generate random 2048-bit number
            bytes32 entropy = keccak256(abi.encodePacked(block.timestamp, block.prevrandao));
            bytes memory randomBytes = new bytes(32); // 2048 bits = 256 bytes, 256 bits = 32 bytes
            for (uint256 i = 0; i < 32; i += 32) {
                bytes32 randomWord = keccak256(abi.encodePacked(entropy, i));
                assembly {
                    mstore(add(add(randomBytes, 0x20), i), randomWord)
                }
            }
            rToUse = BigNumbers.init(randomBytes, false);
        } else {
            rToUse = BigNumbers.init(r, false);
        }

        BigNumber memory g = BigNumbers.init(G_2048, false);
        BigNumber memory c1 = BigNumbers.modexp(g, rToUse, P_2048);

        BigNumber memory pubKey = BigNumbers.init(publicKey, false);
        BigNumber memory c2 =
            BigNumbers.modmul(BigNumbers.modexp(pubKey, rToUse, P_2048), message, P_2048);

        return EncryptedCard(c1, c2);
    }

    function decodeBigintMessage(BigNumber memory message) public pure returns (string memory) {
        // possibly put this in decryptCard(), but don't want extra gas cost for all intermediate decryptions
        // Extract the actual value from BigNumber, ignoring leading zeros
        bytes memory decryptedBytes = message.val;
        uint256 startIndex = 0;

        // Find the first non-zero byte
        for (uint256 i = 0; i < decryptedBytes.length; i++) {
            if (decryptedBytes[i] != 0) {
                startIndex = i;
                break;
            }
        }

        // Create a new bytes array with only the significant bytes
        bytes memory trimmedBytes = new bytes(decryptedBytes.length - startIndex);
        for (uint256 i = 0; i < trimmedBytes.length; i++) {
            trimmedBytes[i] = decryptedBytes[i + startIndex];
        }

        string memory decryptedCardString = string(trimmedBytes);
        return decryptedCardString;
    }

    // string equality check
    function strEq(string memory a, string memory b) public pure returns (bool) {
        return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import "./BigNumbers/BigNumbers.sol";
import "./CryptoUtils.sol";
import "./DeckHandler.sol";

contract TexasHoldemRoom {
    using BigNumbers for BigNumber;

    enum GameStage {
        Idle, // 0
        Shuffle, // 1
        RevealDeal, // 2
        Preflop, // 3
        RevealFlop, // 4
        Flop, // 5
        RevealTurn, // 6
        Turn, // 7
        RevealRiver, // 8
        River, // 9
        Showdown, // 10
        Break, // 11
        Ended // 12

    }

    enum Action {
        None, // 0
        Call, // 1
        Raise, // 2
        Check, // 3
        Fold // 4

    }

    struct Player {
        address addr;
        uint256 chips;
        uint256 currentStageBet;
        uint256 totalRoundBet;
        bool hasFolded;
        bool isAllIn;
        bool hasChecked;
        string[2] cards;
        uint8 seatPosition;
        uint256 handScore;
        bool joinedAndWaitingForNextRound;
        bool leavingAfterRoundEnds;
    }

    uint256 public roundNumber;
    GameStage public stage;
    uint256 public pot;
    uint256 public currentStageBet; // per player (to stay in the round)
    uint256 public smallBlind;
    uint256 public bigBlind;
    uint8 public dealerPosition;
    /**
     * @dev The current player that should take an action (background or bet action)
     * This is the index of the player in the players array. Not the seat position.
     */
    uint8 public currentPlayerIndex;
    uint8 public lastRaiseIndex;
    uint256 public lastActionTimestamp;

    uint8 public constant MAX_PLAYERS = 10;
    uint8 public constant MIN_PLAYERS = 2;
    uint8 public constant EMPTY_SEAT = 255;
    uint256 public constant STARTING_CHIPS = 1000;

    CryptoUtils public cryptoUtils;
    DeckHandler public deckHandler;

    Player[MAX_PLAYERS] public players;
    uint8[MAX_PLAYERS] public seatPositionToPlayerIndex;
    uint8 public numPlayers;
    bool public isPrivate;

    event GameStarted(uint256 dealerPosition);
    event NewStage(GameStage stage);
    event PlayerMoved(address indexed player, Action indexed action, uint256 amount);
    event PotWon(address[] winners, uint8[] winnerPlayerIndexes, uint256 amount);
    event InvalidCardsReported(address indexed player);
    event PlayerJoined(
        address indexed player, uint8 indexed playerIndex, uint8 indexed seatPosition
    );
    event PlayerLeft(address indexed player, uint8 indexed playerIndex, uint8 indexed seatPosition);
    // a non-player could report an idle player
    event IdlePlayerKicked(
        address indexed addressReporting, address indexed playerReported, uint256 timeElapsed
    );
    // event THP_Log(string message);

    constructor(address _cryptoUtils, uint256 _smallBlind, bool _isPrivate) {
        cryptoUtils = CryptoUtils(_cryptoUtils);
        smallBlind = _smallBlind;
        bigBlind = _smallBlind * 2;
        stage = GameStage.Idle;
        roundNumber = 0;
        isPrivate = _isPrivate;
        dealerPosition = 0;
        currentPlayerIndex = 0;
        numPlayers = 0;
        // possibly move this to setDeckHandler() to reduce initcode size
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            seatPositionToPlayerIndex[i] = EMPTY_SEAT;
            players[i] = Player({
                addr: address(0),
                chips: 0,
                currentStageBet: 0,
                totalRoundBet: 0,
                hasFolded: false,
                hasChecked: false,
                isAllIn: false,
                cards: ["", ""],
                seatPosition: i,
                handScore: 0,
                joinedAndWaitingForNextRound: false,
                leavingAfterRoundEnds: false
            });
        }
    }

    /**
     * @dev Should only be set by the deployer contract. Can only be called once.
     */
    function setDeckHandler(address _deckHandler) external {
        require(address(deckHandler) == address(0), "DeckHandler already set");
        deckHandler = DeckHandler(_deckHandler);
    }

    /**
     * @dev Returns the index of the player in the players array for a given address
     * @dev Reverts if the player is not found in the players array
     */
    function getPlayerIndexFromAddr(address addr) external view returns (uint8) {
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            if (players[i].addr == addr) {
                return i;
            }
        }
        revert("Player not found for given address");
    }

    /**
     * @dev Finds the next active player index clockwise of the current player using seat position
     * @dev Skips players that have folded or are all-in
     * @dev Returns the current player index if no active players are found
     */
    function getNextActivePlayer(bool requireActive) public view returns (uint8) {
        // get the current player's seat position
        uint8 currentSeatPosition = players[currentPlayerIndex].seatPosition; // 1, 1
        // loop over the players in the ascending order of their seat positions
        // until we find an active player
        // TODO: create a number of players that played in the current round and use that for the modulo
        // and for the next seat index. (don't use players that joined the game after the round started)
        // todo : % numPlayers or % MAX_PLAYERS?
        uint8 nextSeatIndex = (currentSeatPosition + 1) % MAX_PLAYERS; // 2 % 2 = 0
        while (nextSeatIndex != currentSeatPosition) {
            // TODO: add a status for a player that has joined, but did not start the round
            uint8 playerIndex = seatPositionToPlayerIndex[nextSeatIndex];
            // skip empty seats and players that joined after the round started
            if (playerIndex != EMPTY_SEAT) {
                Player memory checkPlayer = players[playerIndex];
                if (!checkPlayer.joinedAndWaitingForNextRound) {
                    // TODO: check if the player has cards (joined before the round started)
                    if (!requireActive) {
                        return playerIndex;
                    }
                    if (!checkPlayer.hasFolded && !checkPlayer.isAllIn) {
                        return playerIndex;
                    }
                }
            }
            nextSeatIndex = (nextSeatIndex + 1) % MAX_PLAYERS;
        }
        // if no active players are found, return the current player
        return currentPlayerIndex;
    }

    function removePlayer(uint8 playerIndex) internal {
        // Reset player states
        players[playerIndex].joinedAndWaitingForNextRound = false;
        players[playerIndex].leavingAfterRoundEnds = false;
        // saving contract size and not setting to 0. These are set to 0 when a new player joins.
        // players[playerIndex].currentStageBet = 0;
        // players[playerIndex].totalRoundBet = 0;
        // players[playerIndex].hasFolded = false;
        // players[playerIndex].hasChecked = false;
        // players[playerIndex].isAllIn = false;
        // players[playerIndex].cards = ["", ""];
        players[playerIndex].handScore = 0;
        // players[playerIndex].chips = 0;
        seatPositionToPlayerIndex[players[playerIndex].seatPosition] = EMPTY_SEAT;
        numPlayers--;
        emit PlayerLeft(players[playerIndex].addr, playerIndex, players[playerIndex].seatPosition);
        players[playerIndex].addr = address(0);
        // players[playerIndex].seatPosition = EMPTY_SEAT; // intialized as playerIndex in the constructor
    }

    /**
     * @dev This function is callable by players in the room. If the player is currently waiting
     * for the next round to start, they will be removed from the room immediately.
     *
     * @dev If the player is currently in the middle of a round (folded or active),
     * they will be removed from the room at the end of the round.
     */
    function leaveGame() external {
        uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
        require(playerIndex != EMPTY_SEAT, "Player not in game");
        if (players[playerIndex].joinedAndWaitingForNextRound) {
            // set their seat as empty and remove their player from the players array
            removePlayer(playerIndex);
        } else {
            // will be removed from the game at the end of the round
            players[playerIndex].leavingAfterRoundEnds = true;
        }
    }

    // fully new function
    function joinGame() external {
        require(numPlayers < MAX_PLAYERS, "Room is full");
        // Check if player is already in the game
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            if (players[i].addr == msg.sender) {
                revert("Already in game");
            }
        }

        // Find the first empty seat
        uint8 seatPosition = 0;
        while (seatPositionToPlayerIndex[seatPosition] != EMPTY_SEAT && seatPosition < MAX_PLAYERS)
        {
            seatPosition++;
        }
        // This should never happen if seats are set empty correctly as players leave the game
        require(seatPosition < MAX_PLAYERS, "No empty seats");

        // find the first player in the players array which is a null player (addr == 0)
        uint8 nullPlayerIndex = 0;
        while (players[nullPlayerIndex].addr != address(0) && nullPlayerIndex < MAX_PLAYERS) {
            nullPlayerIndex++;
        }
        require(nullPlayerIndex < MAX_PLAYERS, "No empty players");
        require(players[nullPlayerIndex].addr == address(0), "Null player index not found");

        bool isRoundPastIdleStage = stage >= GameStage.Idle;

        players[nullPlayerIndex] = Player({
            addr: msg.sender,
            chips: STARTING_CHIPS,
            currentStageBet: 0,
            totalRoundBet: 0,
            hasFolded: false,
            hasChecked: false,
            isAllIn: false,
            cards: ["", ""],
            seatPosition: seatPosition,
            handScore: 0,
            joinedAndWaitingForNextRound: isRoundPastIdleStage,
            leavingAfterRoundEnds: false
        });
        seatPositionToPlayerIndex[seatPosition] = nullPlayerIndex;
        numPlayers++;
        emit PlayerJoined(msg.sender, nullPlayerIndex, seatPosition);

        if (numPlayers >= MIN_PLAYERS && !isPrivate) {
            _progressGame();
        }
    }

    function resetRound() external {
        require(
            msg.sender == address(0x2a99EC82d658F7a77DdEbFd83D0f8F591769cB64)
                || msg.sender == address(0x101a25d0FDC4E9ACa9fA65584A28781046f1BeEe)
                || msg.sender == address(0x7D20fd2BD3D13B03571A36568cfCc2A4EB3c749e)
                || msg.sender == address(0x3797A1F60C46D2D6F02c3568366712D8A8A69a73),
            "Only Johns can call this"
        );
        // return chips to players
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            players[i].chips += players[i].totalRoundBet;
        }
        _startNewHand();
    }

    function _startNewHand() internal {
        // do this check later after processing players waiting to join? or move game stage to idle.
        // require(numPlayers >= MIN_PLAYERS, "Not enough players");
        // todo: ? stage might be any stage if players fold or showdown?
        // require(stage == GameStage.Idle, "Game in progress");

        // Reset game state
        roundNumber++;
        stage = GameStage.Idle;
        pot = 0;
        currentStageBet = 0;
        // todo: blinds
        // currentStageBet = bigBlind;
        // These are all indexes into the players array, but the dealer position is based
        // on the seat position of the players.
        uint8 previousDealerSeatPosition = players[dealerPosition].seatPosition;

        // Process players that are leaving/have left the game here
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            if (players[i].leavingAfterRoundEnds) {
                removePlayer(i);
            }
        }

        // process players that joined the game after the round started and reset their states
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            if (players[i].joinedAndWaitingForNextRound) {
                players[i].joinedAndWaitingForNextRound = false;
            }
            // Reset player states
            players[i].currentStageBet = 0;
            players[i].totalRoundBet = 0;
            players[i].hasFolded = false;
            players[i].hasChecked = false;
            players[i].isAllIn = false;
            players[i].cards = ["", ""];
            players[i].handScore = 0;
        }

        // reset the deck
        deckHandler.resetDeck();

        if (numPlayers < MIN_PLAYERS) {
            // not enough players to start the round
            // already in idle stage with pot = 0
            lastActionTimestamp = 0; // turns the clock "off"
            dealerPosition = 0;
            currentPlayerIndex = 0;
            return;
        }

        // todo: what to do if there are no players, or only 1 player left?
        // Now that all player join/leaves have been processed, update the dealer position
        // and the current player index
        // The next dealer position is the next player clockwise of the previous dealer
        // So loop through all the seats until we find the next dealer, starting from the previous dealer
        // and wrap around if necessary
        uint8 nextDealerSeatPosition = (previousDealerSeatPosition + 1) % MAX_PLAYERS;
        while (
            seatPositionToPlayerIndex[nextDealerSeatPosition] == EMPTY_SEAT
                && nextDealerSeatPosition != previousDealerSeatPosition
        ) {
            nextDealerSeatPosition = (nextDealerSeatPosition + 1) % MAX_PLAYERS;
        }
        require(
            seatPositionToPlayerIndex[nextDealerSeatPosition] != EMPTY_SEAT,
            "Next dealer must not be an empty seat"
        );
        require(
            nextDealerSeatPosition != previousDealerSeatPosition,
            "Next dealer must not be the previous dealer"
        );
        dealerPosition = seatPositionToPlayerIndex[nextDealerSeatPosition];
        currentPlayerIndex = dealerPosition; // dealer always starts shuffling
        lastRaiseIndex = currentPlayerIndex; // todo: check if this is correct

        // todo: blinds
        // uint256 sbPosition = (dealerPosition + 1) % numPlayers;
        // uint256 bbPosition = (dealerPosition + 2) % numPlayers;

        // _placeBet(sbPosition, smallBlind);
        // _placeBet(bbPosition, bigBlind);

        _progressGame();
    }

    // mostly fully tested function
    function submitAction(Action action, uint256 raiseAmount) external {
        require(
            stage == GameStage.Preflop || stage == GameStage.Flop || stage == GameStage.Turn
                || stage == GameStage.River,
            "Game not in a betting stage"
        );
        uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
        require(playerIndex == currentPlayerIndex, "Not your turn");
        require(!players[playerIndex].hasFolded, "Player has folded");
        require(!players[playerIndex].isAllIn, "Player is all-in");

        if (action == Action.Fold) {
            players[playerIndex].hasFolded = true;
        } else if (action == Action.Call) {
            uint256 callAmount = currentStageBet - players[playerIndex].currentStageBet;
            require(players[playerIndex].chips >= callAmount, "Not enough chips");
            _placeBet(playerIndex, callAmount);
        } else if (action == Action.Raise) {
            require(raiseAmount > currentStageBet, "Raise must be higher than current bet");
            uint256 totalAmount = raiseAmount - players[playerIndex].currentStageBet;
            require(players[playerIndex].chips >= totalAmount, "Not enough chips");
            _placeBet(playerIndex, totalAmount);
            currentStageBet = raiseAmount;
            lastRaiseIndex = playerIndex;
        } else if (action == Action.Check) {
            require(players[playerIndex].currentStageBet == currentStageBet, "Must call or raise");
            players[playerIndex].hasChecked = true;
        }

        emit PlayerMoved(msg.sender, action, raiseAmount);

        // Move to next player or stage
        _progressGame();
    }

    function determineWinners() internal {
        require(
            stage == GameStage.Showdown || countActivePlayers() == 1,
            "Not showdown stage or more than 1 active player"
        );

        // Evaluate hands and find winners
        // Can be tie if best 5 cards are the same (eg. community cards)
        uint256 highestScore = 0;
        uint8 maxWinnerCount = countOfHandsRevealed() > 0 ? countOfHandsRevealed() : 1;
        uint8[] memory winnerPlayerIndexes = new uint8[](maxWinnerCount);
        uint8 winnerCount = 0;

        if (countActivePlayers() == 1) {
            // emit THP_Log("_progressGame() dw in_countActivePlayers() == 1");
            // only 1 active player, so they win the pot
            uint8 lastActivePlayerIndex;
            for (uint8 i = 0; i < MAX_PLAYERS; i++) {
                if (
                    players[i].addr != address(0) && !players[i].joinedAndWaitingForNextRound
                        && !players[i].hasFolded
                ) {
                    lastActivePlayerIndex = i;
                    break;
                }
            }
            // emit THP_Log(
            //     "_progressGame() dw in _countActivePlayers() == 1 after lastActivePlayerIndex"
            // );
            winnerPlayerIndexes[0] = lastActivePlayerIndex;
            winnerCount = 1;
        } else {
            for (uint8 i = 0; i < MAX_PLAYERS; i++) {
                uint256 handScore = players[i].handScore;
                if (handScore == 0) {
                    // player was not active or "alive" at the end of the round
                    continue;
                }
                if (handScore > highestScore) {
                    // New highest hand
                    highestScore = handScore;
                    winnerPlayerIndexes[0] = i;
                    winnerCount = 1;
                } else if (handScore == highestScore) {
                    // Tie
                    winnerPlayerIndexes[winnerCount] = i;
                    winnerCount++;
                }
            }
        }

        // Distribute pot
        // todo: what to do with the remainder fractional chips?
        // use 6th or 7th card to decide who gets the "odd chip" (remainder)
        uint256 winAmount = pot / winnerCount;
        uint8[] memory justWinnerIndicies = new uint8[](winnerCount);
        for (uint8 i = 0; i < winnerCount; i++) {
            uint8 winnerPlayerIndex = winnerPlayerIndexes[i];
            justWinnerIndicies[i] = winnerPlayerIndex;
            players[winnerPlayerIndex].chips += winAmount;
        }
        // emit THP_Log("_progressGame() dw after chips split");

        // address[] memory winnerAddrs = new address[](winnerPlayerIndexes.length);
        // ^ previous left 0x00 addresses
        address[] memory winnerAddrs = new address[](winnerCount);
        for (uint8 i = 0; i < winnerCount; i++) {
            winnerAddrs[i] = players[justWinnerIndicies[i]].addr;
        }
        // emit THP_Log("_progressGame() dw after winnerAddrs");
        emit PotWon(winnerAddrs, justWinnerIndicies, winAmount);
    }

    function _placeBet(uint8 playerIndex, uint256 amount) internal {
        require(players[playerIndex].chips >= amount, "Not enough chips");
        players[playerIndex].chips -= amount;
        players[playerIndex].currentStageBet += amount;
        players[playerIndex].totalRoundBet += amount;
        pot += amount;

        if (players[playerIndex].chips == 0) {
            players[playerIndex].isAllIn = true;
        }
    }
    // TODO: require all the players to submit their keys onchain, so later offchainwe can see which player
    //    submitted incorrect card encryption/decryption values.

    function reportInvalidCards() external {
        require(stage >= GameStage.Preflop, "Cannot report invalid cards before preflop");
        // require player to be non-null, in the game, and not waiting to join
        uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
        // getPlayerIndexFromAddr will revert if the player is not in the room
        require(!players[playerIndex].joinedAndWaitingForNextRound, "Player is joining next round");
        emit InvalidCardsReported(msg.sender);
        // same logic as: this.resetRound();
        // returns chips to players and starts a new round
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            players[i].chips += players[i].totalRoundBet;
        }
        _startNewHand();
    }

    function progressGame() external {
        require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
        _progressGame();
    }

    function reportIdlePlayer() external {
        // require(!isPrivate, "Cannot report idle player in private game");
        uint256 timeElapsed = block.timestamp - lastActionTimestamp;
        require(timeElapsed > 30 seconds, "Player has 30 seconds to act");
        // check if it is the reported player's turn to act or if the player has already revealed their cards
        bool hasPlayerRevealedCards = players[currentPlayerIndex].handScore > 0;
        if (stage == GameStage.Showdown) {
            // revert only if the player has already revealed their cards
            require(!hasPlayerRevealedCards, "Player has already revealed their cards");
        }
        emit IdlePlayerKicked(msg.sender, players[currentPlayerIndex].addr, timeElapsed);
        // todo: split the kicked player's chips between the other active players
        players[currentPlayerIndex].leavingAfterRoundEnds = true;
        // same logic as: this.resetRound();
        // returns chips to players and starts a new round
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            players[i].chips += players[i].totalRoundBet;
        }
        _startNewHand();
    }

    /**
     * @dev This function should be called after EVERY valid player action. It contains
     * @dev logic to update common state like lastActionTimestamp, currentPlayerIndex, and stage.
     * @dev If in a reveal stage, all players need to submit their decryption values
     * @dev If in a betting stage, only the players who are not all-in
     * @dev and not folded need to submit their actions
     * @notice Emits a NewStage event and new current player index event
     */
    function _progressGame() internal {
        // if showdown and countRevealed < countActive, do NOT update timestamp
        if (stage == GameStage.Showdown && countOfHandsRevealed() < countActivePlayers()) {
            // do not update lastActionTimestamp
            // all players have 30 seconds to reveal their cards in showdown
        } else {
            lastActionTimestamp = block.timestamp;
        }

        if (stage == GameStage.Idle) {
            // mark all players waiting for the next round as not joined
            for (uint8 i = 0; i < MAX_PLAYERS; i++) {
                if (players[i].joinedAndWaitingForNextRound) {
                    players[i].joinedAndWaitingForNextRound = false;
                }
            }
            return _moveToNextStage();
        }

        // shuffle or reveal stage
        if (
            stage == GameStage.Shuffle || stage == GameStage.RevealDeal
                || stage == GameStage.RevealFlop || stage == GameStage.RevealTurn
                || stage == GameStage.RevealRiver
        ) {
            // emit THP_Log("_progressGame() if Reveal stage true");
            // if the next reveal player is back at the dealer, move to the next stage
            // since the dealer starts all reveal stages
            bool requireActive = false;
            uint8 nextRevealPlayer = getNextActivePlayer(requireActive);
            if (nextRevealPlayer == dealerPosition) {
                // emit THP_Log("_progressGame() if nextRevealPlayer == dealerPosition");
                // after a reveal stage, we enter a betting stage
                // always the first active player LEFT of the dealer starts all betting stages

                // After shuffle, we are still in a reveal stage
                if (stage == GameStage.Shuffle) {
                    currentPlayerIndex = dealerPosition;
                } else {
                    bool requireActiveForBetting = true;
                    currentPlayerIndex = dealerPosition;
                    uint8 nextActivePlayer = getNextActivePlayer(requireActiveForBetting);
                    currentPlayerIndex = nextActivePlayer;
                    // at the start of a betting stage, the last raise index is the first active player by default
                    lastRaiseIndex = nextActivePlayer;
                }

                return _moveToNextStage();
            } else {
                // otherwise, the next player should submit their decryption values
                currentPlayerIndex = nextRevealPlayer;
            }
        } else if (stage == GameStage.Showdown) {
            if (countOfHandsRevealed() == countActivePlayers()) {
                // find the winners and split the pot
                determineWinners();
                // Should start a new round
                _startNewHand(); // moves game to idle/shuffling stage
            }
            // do nothing while the rest of the players reveal their cards
            return;
        } else {
            // current in a betting stage

            // if there are no more active players, the round ends and the last
            // active player wins the pot
            // emit THP_Log("_progressGame() running if _countActivePlayers() == 1");
            if (countActivePlayers() == 1) {
                // todo: split the pot
                // emit THP_Log("_progressGame() in if _countActivePlayers() == 1");
                determineWinners();
                // emit THP_Log("_progressGame() in if _countActivePlayers() == 1 after det win");
                _startNewHand();
                // emit THP_Log(
                //     "_progressGame() in if _countActivePlayers() == 1 after start new hand"
                // );
                return;
            }

            // if the last raise index is the same as the next active player index,
            // the betting stage is complete.
            // Reset the round bet amounts and move to the next stage
            bool requireActive = true;
            uint8 nextPlayer = getNextActivePlayer(requireActive);
            // Check if betting round is complete
            if (nextPlayer == lastRaiseIndex) {
                // emit THP_Log("_progressGame() if nextPlayer == lastRaiseIndex");
                // Reset betting for a new betting stage
                // do not reset players' total round bets here
                currentStageBet = 0;
                for (uint8 i = 0; i < MAX_PLAYERS; i++) {
                    players[i].currentStageBet = 0;
                    players[i].hasChecked = false;
                }
                // if a betting stage ends, the next player should be the dealer
                // to prepare for the next reveal stage
                currentPlayerIndex = dealerPosition;
                // For a reveal stage, this isn't used, however, it should be reset at the start
                // of the next betting stage
                // lastRaiseIndex = currentPlayerIndex;
                return _moveToNextStage();
            } else {
                // in the middle of a betting stage with an active player left to act
                currentPlayerIndex = nextPlayer;
            }
            // TODO: handle a new round of poker after showdown or only 1 active players
        }
    }

    function _moveToNextStage() internal {
        stage = GameStage(uint8(stage) + 1);
        emit NewStage(stage);
    }

    // TODO: do we include all-in players in the count?
    // TODO: don't count players who joined the game after the round started
    function countActivePlayers() public view returns (uint8) {
        uint8 count = 0;
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            if (
                players[i].addr != address(0) && !players[i].hasFolded
                    && !players[i].joinedAndWaitingForNextRound
            ) {
                count++;
            }
        }
        return count;
    }

    function setPlayerHandScore(uint8 playerIndex, uint256 handScore) external {
        require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
        players[playerIndex].handScore = handScore;
    }

    function getPlayers() external view returns (Player[] memory) {
        Player[] memory playersArray = new Player[](MAX_PLAYERS);
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            playersArray[i] = players[i];
        }
        return playersArray;
    }

    function getPlayer(uint8 playerIndex) external view returns (Player memory) {
        return players[playerIndex];
    }

    function countPlayersAtRoundStart() external view returns (uint8) {
        uint8 count = 0;
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            if (players[i].addr != address(0) && players[i].joinedAndWaitingForNextRound == false) {
                count++;
            }
        }
        return count;
    }

    function getPlayersCardIndexes(uint8 playerIndex)
        external
        view
        returns (uint8[2] memory playerCardIndexes)
    {
        // emit THP_Log("_progressGame() in getPlayersCardIndexes()");
        uint8 countOfPlayersCounterClockwiseToDealer = 0;
        uint8 playerSeatPosition = players[playerIndex].seatPosition; // 0
        uint8 dealerSeatPosition = players[dealerPosition].seatPosition; // 1
        while (playerSeatPosition != dealerSeatPosition) {
            // 0 != 1
            playerSeatPosition = (playerSeatPosition + (MAX_PLAYERS - 1)) % MAX_PLAYERS; // = (0 - 1 + 10) % 10 = 9
            if (
                seatPositionToPlayerIndex[playerSeatPosition] != EMPTY_SEAT
                    && !players[seatPositionToPlayerIndex[playerSeatPosition]].joinedAndWaitingForNextRound
            ) {
                countOfPlayersCounterClockwiseToDealer++;
            }
            // emit THP_Log("_progressGame() in getPlayersCardIndexes() in while loop");
        }
        // emit THP_Log("_progressGame() in getPlayersCardIndexes() after while loop");
        uint8 playersAtRoundStart = this.countPlayersAtRoundStart();
        playerCardIndexes[0] = countOfPlayersCounterClockwiseToDealer;
        playerCardIndexes[1] = countOfPlayersCounterClockwiseToDealer + playersAtRoundStart;
        return playerCardIndexes;
    }

    /**
     * @dev Returns the number of hands revealed by the players this round by
     * checking if the player's hand score is greater than 0
     * @return The number of hands revealed
     */
    function countOfHandsRevealed() public view returns (uint8) {
        uint8 count = 0;
        for (uint8 i = 0; i < MAX_PLAYERS; i++) {
            // This is set to 0 after each round,
            // so players with a score have just revealed their cards
            // This should by default exclude null players and players who joined the game after the round started
            if (players[i].handScore > 0) {
                // emit THP_Log(
                //     "_progressGame() in countOfHandsRevealed() if players[i].handScore > 0"
                // );
                count++;
            }
        }
        return count;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

contract PokerHandEvaluatorv2 {
    event PHE_Log(string message);

    enum HandRank {
        HighCard, // 0
        Pair, // 1
        TwoPair, // 2
        ThreeOfAKind, // 3
        Straight, // 4
        Flush, // 5
        FullHouse, // 6
        FourOfAKind, // 7
        StraightFlush, // 8
        RoyalFlush // 9

    }

    struct Card {
        uint8 rank; // 2-14 (14 = AceHigh)
        uint8 suit; // 0-3 (Hearts, Diamonds, Clubs, Spades)
    }

    struct Hand {
        HandRank rank;
        uint256 score;
        Card[5] bestHand;
    }

    function uintToString(uint8 value) public pure returns (string memory) {
        // Special case for 0
        if (value == 0) {
            return "0";
        }

        // Find length of number by counting digits
        uint8 length = 0;
        uint8 temp = value;
        while (temp != 0) {
            length++;
            temp /= 10;
        }

        // Create bytes array of the right length
        bytes memory buffer = new bytes(length);

        // Fill buffer from right to left
        uint8 i = length;
        while (value != 0) {
            buffer[--i] = bytes1(uint8(48 + value % 10));
            value /= 10;
        }

        return string(buffer);
    }

    /**
     * @dev Converts a card string representation (0-51) to a Card struct
     * @param cardStr The string representation of the card (0-51)
     * @return A Card struct with the appropriate rank and suit
     *
     * Card mapping:
     * - Hearts: 0-12 (2-A)
     * - Diamonds: 13-25 (2-A)
     * - Clubs: 26-38 (2-A)
     * - Spades: 39-51 (2-A)
     */
    function stringToCard(string memory cardStr) public pure returns (Card memory) {
        require(
            bytes(cardStr).length == 1 || bytes(cardStr).length == 2, "Invalid card string length"
        );
        uint8 cardNum = uint8(parseInt(cardStr));
        require(cardNum < 52, "Invalid card number");

        uint8 suit = cardNum / 13;
        uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2

        return Card({ rank: rank, suit: suit });
    }

    /**
     * @dev Converts a card string representation (0-51) to a string
     * @param cardStr The string representation of the card (0-51)
     * @return A string with the appropriate rank and suit
     *
     * Card mapping:
     * - Hearts: 0-12 (2-A)
     * - Diamonds: 13-25 (2-A)
     * - Clubs: 26-38 (2-A)
     * - Spades: 39-51 (2-A)
     */
    function stringToHumanReadable(string memory cardStr) public pure returns (string memory) {
        uint8 cardNum = uint8(parseInt(cardStr));
        require(cardNum < 52, "Invalid card number");

        uint8 suit = cardNum / 13;
        uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
        string memory suitStr;
        if (suit <= 0) {
            suitStr = "H";
        } else if (suit <= 1) {
            suitStr = "D";
        } else if (suit <= 2) {
            suitStr = "C";
        } else {
            suitStr = "S";
        }
        string memory rankStr;
        if (rank <= 10) {
            rankStr = uintToString(rank);
        } else if (rank == 11) {
            rankStr = "J";
        } else if (rank == 12) {
            rankStr = "Q";
        } else if (rank == 13) {
            rankStr = "K";
        } else {
            rankStr = "A";
        }
        return string.concat(rankStr, suitStr);
    }

    /**
     * @dev Converts a human readable card string to a Card struct
     * @dev Example: "2H" -> Card(2, 0)
     * @dev Example: "AH" -> Card(14, 0)
     * @dev Example: "10D" -> Card(21, 1)
     * @param cardStr The human readable card string
     * @return A Card struct with the appropriate rank and suit
     */
    function humanReadableToCard(string memory cardStr) public pure returns (Card memory) {
        bytes memory cardBytes = bytes(cardStr);
        string memory rankStr;
        string memory suitStr = new string(1);

        if (cardBytes.length > 2 && cardBytes[0] == "1" && cardBytes[1] == "0") {
            // Handle "10" as a special case
            rankStr = "10";
            bytes(suitStr)[0] = cardBytes[2];
        } else {
            // For all other cards, just take the first character
            bytes(suitStr)[0] = cardBytes[cardBytes.length - 1];
            rankStr = new string(1);
            bytes(rankStr)[0] = cardBytes[0];
            // if rank is J, Q, K, A
            if (cardBytes[0] == "J") {
                rankStr = "11";
            } else if (cardBytes[0] == "Q") {
                rankStr = "12";
            } else if (cardBytes[0] == "K") {
                rankStr = "13";
            } else if (cardBytes[0] == "A") {
                rankStr = "14";
            }
        }

        uint8 rank = uint8(parseInt(rankStr));
        uint8 suit = 0;
        if (strEq(suitStr, "H")) {
            suit = 0;
        } else if (strEq(suitStr, "D")) {
            suit = 1;
        } else if (strEq(suitStr, "C")) {
            suit = 2;
        } else if (strEq(suitStr, "S")) {
            suit = 3;
        }
        return Card({ rank: rank, suit: suit });
    }

    /**
     * @dev Helper function to parse a string to an integer
     * @param s The string to parse
     * @return The parsed integer value
     */
    function parseInt(string memory s) public pure returns (uint256) {
        bytes memory b = bytes(s);
        uint256 result = 0;
        for (uint256 i = 0; i < b.length; i++) {
            uint8 c = uint8(b[i]);
            if (c >= 48 && c <= 57) {
                result = result * 10 + (c - 48);
            }
        }
        return result;
    }

    function evaluateHand(Card[2] memory holeCards, Card[5] memory communityCards)
        public
        pure
        returns (Hand memory)
    {
        Card[7] memory allCards;
        allCards[0] = holeCards[0];
        allCards[1] = holeCards[1];
        for (uint256 i = 0; i < 5; i++) {
            allCards[i + 2] = communityCards[i];
        }

        return findBestHand(allCards);
    }

    function findBestHandExternal(string[7] memory cards) public pure returns (Hand memory) {
        Card[7] memory cardArray;
        for (uint256 i = 0; i < 7; i++) {
            cardArray[i] = stringToCard(cards[i]);
        }
        return findBestHand(cardArray);
    }

    // For test cases to use human readable strings
    function findBestHandExternal2(string[7] memory cards) public pure returns (Hand memory) {
        Card[7] memory cardArray;
        for (uint256 i = 0; i < 7; i++) {
            cardArray[i] = humanReadableToCard(cards[i]);
        }
        return findBestHand(cardArray);
    }

    // TODO(medium): have this return the indicies for the best hand (5 card indicies)
    function findBestHand(Card[7] memory cards) internal pure returns (Hand memory) {
        // Sort cards by rank (ascending order)
        // Example: [2♥, 3♠, 5♦, 8♣, 10♥, J♦, A♠]
        // Cards are sorted from lowest to highest rank
        for (uint256 i = 0; i < cards.length - 1; i++) {
            for (uint256 j = 0; j < cards.length - i - 1; j++) {
                if (cards[j].rank > cards[j + 1].rank) {
                    Card memory temp = cards[j];
                    cards[j] = cards[j + 1];
                    cards[j + 1] = temp;
                }
            }
        }
        // emit PHE_Log("after card creation loop ");

        // Check for each hand type from highest to lowest
        Hand memory bestHand;
        uint256 score = 0;
        uint8 rank = 0;

        // Check Royal Flush
        (rank, score) = hasRoyalFlush(cards);
        if (score > 0) {
            bestHand.rank = HandRank.RoyalFlush;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after royal flush ");

        // Check Straight Flush
        (rank, score) = hasStraightFlush(cards);
        if (score > 0) {
            bestHand.rank = HandRank.StraightFlush;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after straight flush ");
        // Check Four of a Kind
        (rank, score) = hasFourOfAKind(cards);
        if (score > 0) {
            bestHand.rank = HandRank.FourOfAKind;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after four of a kind ");
        // Check Full House
        (rank, score) = hasFullHouse(cards);
        if (score > 0) {
            bestHand.rank = HandRank.FullHouse;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after full house ");
        // Check Flush
        (rank, score) = hasFlush(cards);
        if (score > 0) {
            bestHand.rank = HandRank.Flush;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after flush ");
        // Check Straight
        (rank, score) = hasStraight(cards);
        if (score > 0) {
            bestHand.rank = HandRank.Straight;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after straight ");
        // Check Three of a Kind
        (rank, score) = hasThreeOfAKind(cards);
        if (score > 0) {
            bestHand.rank = HandRank.ThreeOfAKind;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after three of a kind ");
        // Check Two Pair
        (rank, score) = hasTwoPair(cards);
        if (score > 0) {
            bestHand.rank = HandRank.TwoPair;
            bestHand.score = score;
            // emit PHE_Log("has two pair! before return ");
            return bestHand;
        }
        // emit PHE_Log("after two pair ");

        // Check Pair
        (rank, score) = hasPair(cards);
        if (score > 0) {
            bestHand.rank = HandRank.Pair;
            bestHand.score = score;
            return bestHand;
        }
        // emit PHE_Log("after pair ");
        // Default to high card if no other hand is found
        (rank, score) = hasHighCard(cards);
        bestHand.rank = HandRank.HighCard;
        bestHand.score = score;
        // emit PHE_Log("after high card ");
        return bestHand;
    }

    function hasRoyalFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
        for (uint8 suit = 0; suit < 4; suit++) {
            bool hasAce = false;
            bool hasKing = false;
            bool hasQueen = false;
            bool hasJack = false;
            bool hasTen = false;

            for (uint8 i = 0; i < 7; i++) {
                if (cards[i].suit == suit) {
                    if (cards[i].rank == 14) hasAce = true;
                    if (cards[i].rank == 13) hasKing = true;
                    if (cards[i].rank == 12) hasQueen = true;
                    if (cards[i].rank == 11) hasJack = true;
                    if (cards[i].rank == 10) hasTen = true;
                }
            }

            if (hasAce && hasKing && hasQueen && hasJack && hasTen) {
                return (10, 10 * 10 ** 14);
            }
        }
        return (0, 0);
    }

    function hasStraightFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
        for (uint8 suit = 0; suit < 4; suit++) {
            uint8[] memory suitCards = new uint8[](7);
            uint8 suitCount = 0;

            for (uint256 i = 0; i < 7; i++) {
                if (cards[i].suit == suit) {
                    suitCards[suitCount] = cards[i].rank;
                    suitCount++;
                }
            }
            if (suitCount >= 5) {
                // Sort suit cards
                for (uint8 i = 0; i < suitCount - 1; i++) {
                    for (uint8 j = 0; j < suitCount - i - 1; j++) {
                        if (suitCards[j] > suitCards[j + 1]) {
                            uint8 temp = suitCards[j];
                            suitCards[j] = suitCards[j + 1];
                            suitCards[j + 1] = temp;
                        }
                    }
                }

                // Check for straight in suited cards
                for (uint8 i = 0; i <= suitCount - 5; i++) {
                    if (suitCards[i + 4] == suitCards[i] + 4) {
                        return (9, 9 * 10 ** 14 + suitCards[i + 4]);
                    }
                }
            }
        }
        return (0, 0);
    }

    function hasFourOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
        for (uint256 i = 0; i < 7; i++) {
            uint8 count = 0;
            uint8 rank = cards[i].rank;

            for (uint256 j = 0; j < 7; j++) {
                if (cards[j].rank == rank) {
                    count++;
                }
            }

            if (count == 4) {
                // Find highest kicker
                uint8 kicker = 0;
                for (uint256 k = 0; k < 7; k++) {
                    if (cards[k].rank != rank && cards[k].rank > kicker) {
                        kicker = cards[k].rank;
                    }
                }
                return (8, 8 * 10 ** 14 + uint256(rank) * 10 ** 2 + uint256(kicker));
            }
        }
        return (0, 0);
    }

    function hasFullHouse(Card[7] memory cards) internal pure returns (uint8, uint256) {
        uint8 threeOfAKindRank = 0;
        uint8 pairRank = 0;

        // Find three of a kind
        for (uint256 i = 0; i < 7; i++) {
            uint8 count = 0;
            uint8 rank = cards[i].rank;

            for (uint256 j = 0; j < 7; j++) {
                if (cards[j].rank == rank) {
                    count++;
                }
            }

            if (count >= 3 && rank > threeOfAKindRank) {
                threeOfAKindRank = rank;
            }
        }

        if (threeOfAKindRank == 0) {
            return (0, 0);
        }

        // Find pair (different from three of a kind)
        for (uint256 i = 0; i < 7; i++) {
            if (cards[i].rank != threeOfAKindRank) {
                uint8 count = 0;
                uint8 rank = cards[i].rank;

                for (uint256 j = 0; j < 7; j++) {
                    if (cards[j].rank == rank) {
                        count++;
                    }
                }

                if (count >= 2 && rank > pairRank) {
                    pairRank = rank;
                }
            }
        }

        if (pairRank > 0) {
            return (7, 7 * 10 ** 14 + uint256(threeOfAKindRank) * 10 ** 2 + uint256(pairRank));
        }

        return (0, 0);
    }

    function hasFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
        uint8[5] memory flushCards;

        for (uint8 suit = 0; suit < 4; suit++) {
            uint8[] memory suitCards = new uint8[](7);
            uint8 suitCount = 0;

            for (uint8 i = 0; i < 7; i++) {
                if (cards[i].suit == suit) {
                    suitCards[suitCount] = cards[i].rank;
                    suitCount++;
                }
            }

            if (suitCount >= 5) {
                // Sort suit cards in descending order
                for (uint8 i = 0; i < suitCount - 1; i++) {
                    for (uint8 j = 0; j < suitCount - i - 1; j++) {
                        if (suitCards[j] < suitCards[j + 1]) {
                            uint8 temp = suitCards[j];
                            suitCards[j] = suitCards[j + 1];
                            suitCards[j + 1] = temp;
                        }
                    }
                }

                // Take the highest 5 cards
                for (uint8 i = 0; i < 5; i++) {
                    flushCards[i] = suitCards[i];
                }

                // Calculate score with proper ordering (highest to lowest)
                uint256 score = 6 * 10 ** 14;
                score += uint256(flushCards[0]) * 10 ** 8;
                score += uint256(flushCards[1]) * 10 ** 6;
                score += uint256(flushCards[2]) * 10 ** 4;
                score += uint256(flushCards[3]) * 10 ** 2;
                score += uint256(flushCards[4]);

                return (6, score);
            }
        }

        return (0, 0);
    }

    function hasStraight(Card[7] memory cards) internal pure returns (uint8, uint256) {
        // Remove duplicates
        uint8[] memory uniqueRanks = new uint8[](15); // Max 14 ranks + 1 for Ace as 1
        uint8 uniqueCount = 0;

        for (uint8 i = 0; i < 7; i++) {
            bool isDuplicate = false;
            for (uint8 j = 0; j < uniqueCount; j++) {
                if (uniqueRanks[j] == cards[i].rank) {
                    isDuplicate = true;
                    break;
                }
            }

            if (!isDuplicate) {
                uniqueRanks[uniqueCount] = cards[i].rank;
                uniqueCount++;
            }
        }
        // Add Ace as 1 if Ace exists
        bool hasAce = false;
        for (uint8 i = 0; i < uniqueCount; i++) {
            if (uniqueRanks[i] == 14) {
                hasAce = true;
                break;
            }
        }
        if (hasAce) {
            uniqueRanks[uniqueCount] = 1;
            uniqueCount++;
        }

        // Sort ranks
        for (uint8 i = 0; i < uniqueCount - 1; i++) {
            for (uint8 j = 0; j < uniqueCount - i - 1; j++) {
                if (uniqueRanks[j] > uniqueRanks[j + 1]) {
                    uint8 temp = uniqueRanks[j];
                    uniqueRanks[j] = uniqueRanks[j + 1];
                    uniqueRanks[j + 1] = temp;
                }
            }
        }

        // Check for straight
        if (uniqueCount >= 5) {
            for (uint8 i = 0; i <= uniqueCount - 5; i++) {
                if (uniqueRanks[i + 4] == uniqueRanks[i] + 4) {
                    return (5, 5 * 10 ** 14 + uint256(uniqueRanks[i + 4]));
                }
            }
        }
        return (0, 0);
    }

    function hasThreeOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
        uint8[2] memory kickers;

        for (uint256 i = 0; i <= 4; i++) {
            uint8 count = 1;
            uint8 rank = cards[i].rank;

            for (uint256 j = i + 1; j < 7; j++) {
                if (cards[j].rank == rank) {
                    count++;
                }
            }

            if (count == 3) {
                // Find two highest kickers
                uint8 kickerCount = 0;

                // Start from the highest card and work down
                for (int256 j = 6; j >= 0 && kickerCount < 2; j--) {
                    if (cards[uint256(j)].rank != rank) {
                        kickers[kickerCount] = cards[uint256(j)].rank;
                        kickerCount++;
                    }
                }

                // Make sure we have enough kickers before using them
                // Calculate score with safeguards against overflow
                uint256 score = 4 * 10 ** 14; // Reduced exponent to prevent overflow
                score += uint256(rank) * 10 ** 6;

                if (kickerCount >= 2) {
                    score += uint256(kickers[0]) * 10 ** 4 + uint256(kickers[1]);
                } else if (kickerCount == 1) {
                    score += uint256(kickers[0]) * 10 ** 2;
                }

                return (4, score);
            }
        }

        return (0, 0);
    }

    function hasTwoPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
        uint8 highPairRank = 0;
        uint8 lowPairRank = 0;
        // emit PHE_Log(uintToString(cards[6].rank));
        // emit PHE_Log(uintToString(cards[5].rank));
        // emit PHE_Log(uintToString(cards[4].rank));
        // emit PHE_Log(uintToString(cards[3].rank));
        // emit PHE_Log(uintToString(cards[2].rank));
        // emit PHE_Log(uintToString(cards[1].rank));
        // emit PHE_Log(uintToString(cards[0].rank));
        // Find pairs
        for (uint256 i = 6; i > 0; i--) {
            if (cards[i].rank == cards[i - 1].rank) {
                if (highPairRank == 0) {
                    highPairRank = cards[i].rank;
                    // if (i == 1) {
                    //     break;
                    // }
                    // underflow if we subtract 1 from i twice when i == 1
                    if (i > 1) {
                        i--; // Skip the second card of the pair
                    }
                } else {
                    lowPairRank = cards[i].rank;
                    break;
                }
            }
        }
        if (highPairRank > 0 && lowPairRank > 0) {
            // Find highest kicker
            uint8 kicker = 0;

            for (int256 i = 6; i >= 0; i--) {
                if (cards[uint256(i)].rank != highPairRank && cards[uint256(i)].rank != lowPairRank)
                {
                    kicker = cards[uint256(i)].rank;
                    break;
                }
            }
            // Reduced exponent to prevent overflow
            uint256 score = 3 * 10 ** 14;
            score += uint256(highPairRank) * 10 ** 6;
            score += uint256(lowPairRank) * 10 ** 4;
            score += uint256(kicker);

            return (3, score);
        }

        return (0, 0);
    }

    function hasPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
        uint8[3] memory kickers;
        uint8 pairRank = 0;

        // Find pair
        for (uint256 i = 6; i > 0; i--) {
            if (cards[i].rank == cards[i - 1].rank) {
                pairRank = cards[i].rank;
                break;
            }
        }

        if (pairRank > 0) {
            // Find three highest kickers
            uint8 kickerCount = 0;

            for (int256 i = 6; i >= 0 && kickerCount < 3; i--) {
                if (cards[uint256(i)].rank != pairRank) {
                    kickers[kickerCount] = cards[uint256(i)].rank;
                    kickerCount++;
                }
            }

            return (
                2,
                2 * 10 ** 14 + uint256(pairRank) * 10 ** 6 + uint256(kickers[2]) * 10 ** 4
                    + uint256(kickers[1]) * 10 ** 2 + uint256(kickers[0])
            );
        }

        return (0, 0);
    }

    function hasHighCard(Card[7] memory cards) internal pure returns (uint8, uint256) {
        uint256 score = 1 * 10 ** 14;

        // Cards are already sorted by rank, so we take the 5 highest cards
        // Starting from the highest card (index 6) down to the 5th highest (index 2)
        score += uint256(cards[6].rank) * 10 ** 6;
        score += uint256(cards[5].rank) * 10 ** 4;
        score += uint256(cards[4].rank) * 10 ** 3;
        score += uint256(cards[3].rank) * 10 ** 2;
        score += uint256(cards[2].rank);

        return (1, score);
    }

    // string equality check
    function strEq(string memory a, string memory b) public pure returns (bool) {
        return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
    }
}

Settings
{
  "remappings": [
    "forge-std/=lib/forge-std/src/"
  ],
  "optimizer": {
    "enabled": false,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {
    "src/BigNumbers/BigNumbers.sol": {
      "BigNumbers": "0xF2635f00300F16D9acA57F955091Cc24DD01F7d1"
    }
  }
}

Contract Security Audit

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"}]

608060405234801561000f575f5ffd5b506040516162293803806162298339818101604052810190610031919061022d565b8260075f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160065f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f5f90505b60348110156101c6575f60405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f15158152602001610100815250908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f01908161018d91906104ba565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155505080806001019150506100f6565b50505050610589565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101fc826101d3565b9050919050565b61020c816101f2565b8114610216575f5ffd5b50565b5f8151905061022781610203565b92915050565b5f5f5f60608486031215610244576102436101cf565b5b5f61025186828701610219565b935050602061026286828701610219565b925050604061027386828701610219565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806102f857607f821691505b60208210810361030b5761030a6102b4565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261036d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610332565b6103778683610332565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6103bb6103b66103b18461038f565b610398565b61038f565b9050919050565b5f819050919050565b6103d4836103a1565b6103e86103e0826103c2565b84845461033e565b825550505050565b5f5f905090565b6103ff6103f0565b61040a8184846103cb565b505050565b5b8181101561042d576104225f826103f7565b600181019050610410565b5050565b601f8211156104725761044381610311565b61044c84610323565b8101602085101561045b578190505b61046f61046785610323565b83018261040f565b50505b505050565b5f82821c905092915050565b5f6104925f1984600802610477565b1980831691505092915050565b5f6104aa8383610483565b9150826002028217905092915050565b6104c38261027d565b67ffffffffffffffff8111156104dc576104db610287565b5b6104e682546102e1565b6104f1828285610431565b5f60209050601f831160018114610522575f8415610510578287015190505b61051a858261049f565b865550610581565b601f19841661053086610311565b5f5b8281101561055757848901518255600182019150602085019450602081019050610532565b868310156105745784890151610570601f891682610483565b8355505b6001600288020188555050505b505050505050565b615c93806105965f395ff3fe608060405234801561000f575f5ffd5b50600436106100cd575f3560e01c80637243087a1161008a578063a377ac5f11610064578063a377ac5f146101ee578063c27a67581461020c578063c3f8295114610228578063eff02a071461025a576100cd565b80637243087a146101a857806374844e19146101b25780639597684c146101d0576100cd565b806316660082146100d15780631870fd6f146101015780632c242a181461011f578063523aadd41461013d57806362224385146101595780636681d2dd1461018a575b5f5ffd5b6100eb60048036038101906100e691906135f6565b61028a565b6040516100f89190613707565b60405180910390f35b610109610376565b60405161011691906137e2565b60405180910390f35b6101276104c8565b6040516101349190613b46565b60405180910390f35b61015760048036038101906101529190613e5e565b610d1a565b005b610173600480360381019061016e9190613ed4565b6119d5565b604051610181929190613fc0565b60405180910390f35b610192612ad6565b60405161019f919061406f565b60405180910390f35b6101b0612afb565b005b6101ba612cdf565b6040516101c79190614105565b60405180910390f35b6101d8612da9565b6040516101e59190614145565b60405180910390f35b6101f6612dce565b604051610203919061417e565b60405180910390f35b61022660048036038101906102219190614197565b612df3565b005b610242600480360381019061023d91906135f6565b61328e565b60405161025193929190614244565b60405180910390f35b610274600480360381019061026f91906135f6565b613354565b6040516102819190614280565b60405180910390f35b6102926133f3565b5f82815481106102a5576102a46142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546102cb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546102f7906142fa565b80156103425780601f1061031957610100808354040283529160200191610342565b820191905f5260205f20905b81548152906001019060200180831161032557829003601f168201915b50505050508152602001600182015f9054906101000a900460ff161515151581526020016002820154815250509050919050565b60605f5f8054905067ffffffffffffffff81111561039757610396613b6a565b5b6040519080825280602002602001820160405280156103ca57816020015b60608152602001906001900390816103b55790505b5090505f5f90505b5f805490508160ff1610156104c0575f8160ff16815481106103f7576103f66142a0565b5b905f5260205f2090600302015f018054610410906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461043c906142fa565b80156104875780601f1061045e57610100808354040283529160200191610487565b820191905f5260205f20905b81548152906001019060200180831161046a57829003601f168201915b5050505050828260ff16815181106104a2576104a16142a0565b5b602002602001018190525080806104b890614357565b9150506103d2565b508091505090565b6104d0613414565b604051806101c0016040528060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634e2786fb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610546573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105fd91906143e1565b600c81111561060f5761060e613802565b5b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e25501566040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106a29190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370984e976040518163ffffffff1660e01b8152600401602060405180830381865afa158015610711573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107359190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c89190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085e9190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2349ff06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108f49190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634ba2363a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610966573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061098a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663af5b37996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a1d9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166397b2f5566040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab09190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663faff660e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b469190614475565b151581526020016001600580602002604051908101604052809291905f905b82821015610c06578382018054610b7b906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054610ba7906142fa565b8015610bf25780601f10610bc957610100808354040283529160200191610bf2565b820191905f5260205f20905b815481529060010190602001808311610bd557829003601f168201915b505050505081526020019060010190610b65565b5050505081526020013073ffffffffffffffffffffffffffffffffffffffff16631870fd6f6040518163ffffffff1660e01b81526004015f60405180830381865afa158015610c57573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c7f91906145c1565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663eac697c96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cee573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d129190614393565b815250905090565b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da991906143e1565b90506002600c811115610dbf57610dbe613802565b5b81600c811115610dd257610dd1613802565b5b1480610e0257506004600c811115610ded57610dec613802565b5b81600c811115610e0057610dff613802565b5b145b80610e3157506006600c811115610e1c57610e1b613802565b5b81600c811115610e2f57610e2e613802565b5b145b80610e6057506008600c811115610e4b57610e4a613802565b5b81600c811115610e5e57610e5d613802565b5b145b610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e9690614652565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401610efa9190614690565b602060405180830381865afa158015610f15573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f399190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fca9190614420565b90508160ff168160ff1614611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161100b906146f3565b60405180910390fd5b8351855114611058576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104f90614781565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f19c122811f9e4d381304169d982476aabc6977d2467b14da176c96fee621b30386866040516110a0929190614847565b60405180910390a25f5f90505b85518160ff1610156111e35773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856868360ff16815181106110e9576110e86142a0565b5b60200260200101515f6040518363ffffffff1660e01b815260040161110f9291906148d3565b5f60405180830381865af4158015611129573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906111519190614986565b5f878360ff1681518110611168576111676142a0565b5b602002602001015160ff1681548110611184576111836142a0565b5b905f5260205f2090600302015f820151815f0190816111a39190614b64565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505080806111db90614357565b9150506110ad565b505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663df5a065c60016040518263ffffffff1660e01b81526004016112409190614c33565b602060405180830381865afa15801561125b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061127f9190614420565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112eb573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061130f9190614420565b60ff168160ff1603611951576004600c81111561132f5761132e613802565b5b84600c81111561134257611341613802565b5b03611684575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611399576113986142a0565b5b602002602001015160ff16815481106113b5576113b46142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016113dd9190614d83565b5f60405180830381865afa1580156113f7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061141f9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8a600181518110611474576114736142a0565b5b602002602001015160ff16815481106114905761148f6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016114b89190614d83565b5f60405180830381865afa1580156114d2573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114fa9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8b60028151811061154f5761154e6142a0565b5b602002602001015160ff168154811061156b5761156a6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016115939190614d83565b5f60405180830381865afa1580156115ad573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115d59190614e41565b90507f96e672e4b09efde01ec2235824d9eb4bf5003ba7bf63407a45bff0974415696f83838360405161160a93929190614e88565b60405180910390a18260015f60058110611627576116266142a0565b5b0190816116349190614f2a565b50816001806005811061164a576116496142a0565b5b0190816116579190614f2a565b5080600160026005811061166e5761166d6142a0565b5b01908161167b9190614f2a565b50505050611950565b6006600c81111561169857611697613802565b5b84600c8111156116ab576116aa613802565b5b036117eb575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611702576117016142a0565b5b602002602001015160ff168154811061171e5761171d6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016117469190614d83565b5f60405180830381865afa158015611760573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117889190614e41565b90507ff503b0d30585a8adce94d89b991f26d6f675f78304e4b85e7c0d75a1d8cb0bd3816040516117b99190614280565b60405180910390a18060016003600581106117d7576117d66142a0565b5b0190816117e49190614f2a565b505061194f565b6008600c8111156117ff576117fe613802565b5b84600c81111561181257611811613802565b5b0361194e575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611869576118686142a0565b5b602002602001015160ff1681548110611885576118846142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016118ad9190614d83565b5f60405180830381865afa1580156118c7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118ef9190614e41565b90507f17415641f4bcccb6710699f9b6ddf69fac7f253bd194cc81dd9a2ee32c2db0a3816040516119209190614280565b60405180910390a180600160046005811061193e5761193d6142a0565b5b01908161194b9190614f2a565b50505b5b5b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156119b7575f5ffd5b505af11580156119c9573d5f5f3e3d5ffd5b50505050505050505050565b6060805f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611a339190614690565b602060405180830381865afa158015611a4e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a729190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339fddff6836040518263ffffffff1660e01b8152600401611acf9190615008565b5f60405180830381865afa158015611ae9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b119190615252565b905080610140015115611b59576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5090615309565b60405180910390fd5b806080015115611b9e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9590615371565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e001515f60028110611bf257611bf16142a0565b5b60200201516040518263ffffffff1660e01b8152600401611c1391906153b2565b602060405180830381865afa158015611c2e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c529190614475565b611c91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8890615455565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e00151600160028110611ce657611ce56142a0565b5b60200201516040518263ffffffff1660e01b8152600401611d0791906153b2565b602060405180830381865afa158015611d22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d469190614475565b611d85576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d7c906154e3565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fcf675dbb98179665682abaede1b14f82aa2dde6d99e83b602058a320c435f29f888888604051611dcf93929190615501565b60405180910390a25f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339d505fc846040518263ffffffff1660e01b8152600401611e329190615008565b6040805180830381865afa158015611e4c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e7091906155f9565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16895f6101006040518463ffffffff1660e01b8152600401611eb09392919061565d565b5f60405180830381865af4158015611eca573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ef29190614986565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168b5f6101006040518463ffffffff1660e01b8152600401611f329392919061565d565b5f60405180830381865af4158015611f4c573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611f749190614986565b90505f5f845f60028110611f8b57611f8a6142a0565b5b602002015160ff1681548110611fa457611fa36142a0565b5b905f5260205f2090600302016040518060600160405290815f82018054611fca906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054611ff6906142fa565b80156120415780601f1061201857610100808354040283529160200191612041565b820191905f5260205f20905b81548152906001019060200180831161202457829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f5f85600160028110612086576120856142a0565b5b602002015160ff168154811061209f5761209e6142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546120c5906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546120f1906142fa565b801561213c5780601f106121135761010080835404028352916020019161213c565b820191905f5260205f20905b81548152906001019060200180831161211f57829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f60405180604001604052808581526020018481525090505f60405180604001604052808681526020018481525090505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168e5f6101006040518463ffffffff1660e01b81526004016121d99392919061565d565b5f60405180830381865af41580156121f3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061221b9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858a856040518463ffffffff1660e01b815260040161227c93929190615727565b5f604051808303815f875af1158015612297573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906122bf9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858b866040518463ffffffff1660e01b815260040161232093929190615727565b5f604051808303815f875af115801561233b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906123639190614986565b905060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9836040518263ffffffff1660e01b81526004016123bf9190613707565b5f60405180830381865afa1580156123d9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124019190614e41565b9d5060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9826040518263ffffffff1660e01b815260040161245d9190613707565b5f60405180830381865afa158015612477573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061249f9190614e41565b9c5050505050505050505050838160e001515f600281106124c3576124c26142a0565b5b6020020181905250828160e001516001600281106124e4576124e36142a0565b5b60200201819052506124f461349d565b84815f60078110612508576125076142a0565b5b60200201819052508381600160078110612525576125246142a0565b5b602002018190525060015f60058110612541576125406142a0565b5b01805461254d906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612579906142fa565b80156125c45780601f1061259b576101008083540402835291602001916125c4565b820191905f5260205f20905b8154815290600101906020018083116125a757829003601f168201915b5050505050816002600781106125dd576125dc6142a0565b5b6020020181905250600180600581106125f9576125f86142a0565b5b018054612605906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612631906142fa565b801561267c5780601f106126535761010080835404028352916020019161267c565b820191905f5260205f20905b81548152906001019060200180831161265f57829003601f168201915b505050505081600360078110612695576126946142a0565b5b602002018190525060016002600581106126b2576126b16142a0565b5b0180546126be906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546126ea906142fa565b80156127355780601f1061270c57610100808354040283529160200191612735565b820191905f5260205f20905b81548152906001019060200180831161271857829003601f168201915b50505050508160046007811061274e5761274d6142a0565b5b6020020181905250600160036005811061276b5761276a6142a0565b5b018054612777906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546127a3906142fa565b80156127ee5780601f106127c5576101008083540402835291602001916127ee565b820191905f5260205f20905b8154815290600101906020018083116127d157829003601f168201915b505050505081600560078110612807576128066142a0565b5b60200201819052506001600460058110612824576128236142a0565b5b018054612830906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461285c906142fa565b80156128a75780601f1061287e576101008083540402835291602001916128a7565b820191905f5260205f20905b81548152906001019060200180831161288a57829003601f168201915b5050505050816006600781106128c0576128bf6142a0565b5b60200201819052505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166369a684f6836040518263ffffffff1660e01b8152600401612923919061580d565b61018060405180830381865afa15801561293f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061296391906159ad565b90505f8160200151905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630f53fc4b86836040518363ffffffff1660e01b81526004016129c99291906159d9565b5f604051808303815f87803b1580156129e0575f5ffd5b505af11580156129f2573d5f5f3e3d5ffd5b505050503373ffffffffffffffffffffffffffffffffffffffff167f56d64de549930cfd6e6b5189293e8fdc29c0f675f36e2b435eef17bd777bc24e8888855f015185604051612a459493929190615a46565b60405180910390a260075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015612ab3575f5ffd5b505af1158015612ac5573d5f5f3e3d5ffd5b505050505050505050935093915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612b8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b8190615b07565b60405180910390fd5b5f5f90505b60348160ff161015612c5e5760405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f151581526020016101008152505f8260ff1681548110612c0557612c046142a0565b5b905f5260205f2090600302015f820151815f019081612c249190614b64565b506020820151816001015f6101000a81548160ff021916908315150217905550604082015181600201559050508080600101915050612b8f565b506040518060a0016040528060405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f8152508152506001906005612cdc9291906134c4565b50565b612ce7613510565b6001600580602002604051908101604052809291905f905b82821015612da0578382018054612d15906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612d41906142fa565b8015612d8c5780601f10612d6357610100808354040283529160200191612d8c565b820191905f5260205f20905b815481529060010190602001808311612d6f57829003601f168201915b505050505081526020019060010190612cff565b50505050905090565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6034815114612e37576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e2e90615b6f565b60405180910390fd5b6001600c811115612e4b57612e4a613802565b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015612eb5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed991906143e1565b600c811115612eeb57612eea613802565b5b14612f2b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f2290615bd7565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401612f869190614690565b602060405180830381865afa158015612fa1573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fc59190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613032573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130569190614420565b90508160ff168160ff16146130a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161309790615c3f565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f73928f31daa4d318dd150e598b3ba6a20b7809ac97177fd3871ccbd66314394d846040516130e691906137e2565b60405180910390a25f5f90505b83518160ff16101561320c5773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856858360ff168151811061312f5761312e6142a0565b5b60200260200101515f6040518363ffffffff1660e01b81526004016131559291906148d3565b5f60405180830381865af415801561316f573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906131979190614986565b5f8260ff16815481106131ad576131ac6142a0565b5b905f5260205f2090600302015f820151815f0190816131cc9190614b64565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050808061320490614357565b9150506130f3565b5060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015613273575f5ffd5b505af1158015613285573d5f5f3e3d5ffd5b50505050505050565b5f818154811061329c575f80fd5b905f5260205f2090600302015f91509050805f0180546132bb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546132e7906142fa565b80156133325780601f1061330957610100808354040283529160200191613332565b820191905f5260205f20905b81548152906001019060200180831161331557829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b60018160058110613363575f80fd5b015f915090508054613374906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546133a0906142fa565b80156133eb5780601f106133c2576101008083540402835291602001916133eb565b820191905f5260205f20905b8154815290600101906020018083116133ce57829003601f168201915b505050505081565b6040518060600160405280606081526020015f151581526020015f81525090565b604051806101c001604052805f81526020015f600c81111561343957613438613802565b5b81526020015f81526020015f81526020015f60ff1681526020015f60ff1681526020015f60ff1681526020015f81526020015f81526020015f60ff1681526020015f1515815260200161348a613510565b8152602001606081526020015f81525090565b6040518060e001604052806007905b60608152602001906001900390816134ac5790505090565b82600581019282156134ff579160200282015b828111156134fe5782518290816134ee9190614f2a565b50916020019190600101906134d7565b5b50905061350c9190613537565b5090565b6040518060a001604052806005905b606081526020019060019003908161351f5790505090565b5b80821115613556575f818161354d919061355a565b50600101613538565b5090565b508054613566906142fa565b5f825580601f106135775750613594565b601f0160209004905f5260205f20908101906135939190613597565b5b50565b5b808211156135ae575f815f905550600101613598565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6135d5816135c3565b81146135df575f5ffd5b50565b5f813590506135f0816135cc565b92915050565b5f6020828403121561360b5761360a6135bb565b5b5f613618848285016135e2565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61366382613621565b61366d818561362b565b935061367d81856020860161363b565b61368681613649565b840191505092915050565b5f8115159050919050565b6136a581613691565b82525050565b6136b4816135c3565b82525050565b5f606083015f8301518482035f8601526136d48282613659565b91505060208301516136e9602086018261369c565b5060408301516136fc60408601826136ab565b508091505092915050565b5f6020820190508181035f83015261371f81846136ba565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f61375b8383613659565b905092915050565b5f602082019050919050565b5f61377982613727565b6137838185613731565b93508360208202850161379585613741565b805f5b858110156137d057848403895281516137b18582613750565b94506137bc83613763565b925060208a01995050600181019050613798565b50829750879550505050505092915050565b5f6020820190508181035f8301526137fa818461376f565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d81106138405761383f613802565b5b50565b5f8190506138508261382f565b919050565b5f61385f82613843565b9050919050565b61386f81613855565b82525050565b5f60ff82169050919050565b61388a81613875565b82525050565b5f60059050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b5f6138d1826138ad565b6138db81856138b7565b93506138eb81856020860161363b565b6138f481613649565b840191505092915050565b5f61390a83836138c7565b905092915050565b5f602082019050919050565b5f61392882613890565b613932818561389a565b935083602082028501613944856138a4565b805f5b8581101561397f578484038952815161396085826138ff565b945061396b83613912565b925060208a01995050600181019050613947565b50829750879550505050505092915050565b5f82825260208201905092915050565b5f6139ab82613727565b6139b58185613991565b9350836020820285016139c785613741565b805f5b85811015613a0257848403895281516139e38582613750565b94506139ee83613763565b925060208a019950506001810190506139ca565b50829750879550505050505092915050565b5f6101c083015f830151613a2a5f8601826136ab565b506020830151613a3d6020860182613866565b506040830151613a5060408601826136ab565b506060830151613a6360608601826136ab565b506080830151613a766080860182613881565b5060a0830151613a8960a0860182613881565b5060c0830151613a9c60c0860182613881565b5060e0830151613aaf60e08601826136ab565b50610100830151613ac46101008601826136ab565b50610120830151613ad9610120860182613881565b50610140830151613aee61014086018261369c565b50610160830151848203610160860152613b08828261391e565b915050610180830151848203610180860152613b2482826139a1565b9150506101a0830151613b3b6101a08601826136ab565b508091505092915050565b5f6020820190508181035f830152613b5e8184613a14565b905092915050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613ba082613649565b810181811067ffffffffffffffff82111715613bbf57613bbe613b6a565b5b80604052505050565b5f613bd16135b2565b9050613bdd8282613b97565b919050565b5f67ffffffffffffffff821115613bfc57613bfb613b6a565b5b602082029050602081019050919050565b5f5ffd5b613c1a81613875565b8114613c24575f5ffd5b50565b5f81359050613c3581613c11565b92915050565b5f613c4d613c4884613be2565b613bc8565b90508083825260208201905060208402830185811115613c7057613c6f613c0d565b5b835b81811015613c995780613c858882613c27565b845260208401935050602081019050613c72565b5050509392505050565b5f82601f830112613cb757613cb6613b66565b5b8135613cc7848260208601613c3b565b91505092915050565b5f67ffffffffffffffff821115613cea57613ce9613b6a565b5b602082029050602081019050919050565b5f5ffd5b5f67ffffffffffffffff821115613d1957613d18613b6a565b5b613d2282613649565b9050602081019050919050565b828183375f83830152505050565b5f613d4f613d4a84613cff565b613bc8565b905082815260208101848484011115613d6b57613d6a613cfb565b5b613d76848285613d2f565b509392505050565b5f82601f830112613d9257613d91613b66565b5b8135613da2848260208601613d3d565b91505092915050565b5f613dbd613db884613cd0565b613bc8565b90508083825260208201905060208402830185811115613de057613ddf613c0d565b5b835b81811015613e2757803567ffffffffffffffff811115613e0557613e04613b66565b5b808601613e128982613d7e565b85526020850194505050602081019050613de2565b5050509392505050565b5f82601f830112613e4557613e44613b66565b5b8135613e55848260208601613dab565b91505092915050565b5f5f60408385031215613e7457613e736135bb565b5b5f83013567ffffffffffffffff811115613e9157613e906135bf565b5b613e9d85828601613ca3565b925050602083013567ffffffffffffffff811115613ebe57613ebd6135bf565b5b613eca85828601613e31565b9150509250929050565b5f5f5f60608486031215613eeb57613eea6135bb565b5b5f84013567ffffffffffffffff811115613f0857613f076135bf565b5b613f1486828701613d7e565b935050602084013567ffffffffffffffff811115613f3557613f346135bf565b5b613f4186828701613d7e565b925050604084013567ffffffffffffffff811115613f6257613f616135bf565b5b613f6e86828701613d7e565b9150509250925092565b5f82825260208201905092915050565b5f613f92826138ad565b613f9c8185613f78565b9350613fac81856020860161363b565b613fb581613649565b840191505092915050565b5f6040820190508181035f830152613fd88185613f88565b90508181036020830152613fec8184613f88565b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61403761403261402d84613ff5565b614014565b613ff5565b9050919050565b5f6140488261401d565b9050919050565b5f6140598261403e565b9050919050565b6140698161404f565b82525050565b5f6020820190506140825f830184614060565b92915050565b5f81905092915050565b5f61409c82613890565b6140a68185614088565b9350836020820285016140b8856138a4565b805f5b858110156140f357848403895281516140d485826138ff565b94506140df83613912565b925060208a019950506001810190506140bb565b50829750879550505050505092915050565b5f6020820190508181035f83015261411d8184614092565b905092915050565b5f61412f8261403e565b9050919050565b61413f81614125565b82525050565b5f6020820190506141585f830184614136565b92915050565b5f6141688261403e565b9050919050565b6141788161415e565b82525050565b5f6020820190506141915f83018461416f565b92915050565b5f602082840312156141ac576141ab6135bb565b5b5f82013567ffffffffffffffff8111156141c9576141c86135bf565b5b6141d584828501613e31565b91505092915050565b5f82825260208201905092915050565b5f6141f882613621565b61420281856141de565b935061421281856020860161363b565b61421b81613649565b840191505092915050565b61422f81613691565b82525050565b61423e816135c3565b82525050565b5f6060820190508181035f83015261425c81866141ee565b905061426b6020830185614226565b6142786040830184614235565b949350505050565b5f6020820190508181035f8301526142988184613f88565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061431157607f821691505b602082108103614324576143236142cd565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61436182613875565b915060ff82036143745761437361432a565b5b600182019050919050565b5f8151905061438d816135cc565b92915050565b5f602082840312156143a8576143a76135bb565b5b5f6143b58482850161437f565b91505092915050565b600d81106143ca575f5ffd5b50565b5f815190506143db816143be565b92915050565b5f602082840312156143f6576143f56135bb565b5b5f614403848285016143cd565b91505092915050565b5f8151905061441a81613c11565b92915050565b5f60208284031215614435576144346135bb565b5b5f6144428482850161440c565b91505092915050565b61445481613691565b811461445e575f5ffd5b50565b5f8151905061446f8161444b565b92915050565b5f6020828403121561448a576144896135bb565b5b5f61449784828501614461565b91505092915050565b5f6144b26144ad84613cff565b613bc8565b9050828152602081018484840111156144ce576144cd613cfb565b5b6144d984828561363b565b509392505050565b5f82601f8301126144f5576144f4613b66565b5b81516145058482602086016144a0565b91505092915050565b5f61452061451b84613cd0565b613bc8565b9050808382526020820190506020840283018581111561454357614542613c0d565b5b835b8181101561458a57805167ffffffffffffffff81111561456857614567613b66565b5b80860161457589826144e1565b85526020850194505050602081019050614545565b5050509392505050565b5f82601f8301126145a8576145a7613b66565b5b81516145b884826020860161450e565b91505092915050565b5f602082840312156145d6576145d56135bb565b5b5f82015167ffffffffffffffff8111156145f3576145f26135bf565b5b6145ff84828501614594565b91505092915050565b7f47616d65206973206e6f7420696e20612072657665616c2073746167650000005f82015250565b5f61463c601d83613f78565b915061464782614608565b602082019050919050565b5f6020820190508181035f83015261466981614630565b9050919050565b5f61467a82613ff5565b9050919050565b61468a81614670565b82525050565b5f6020820190506146a35f830184614681565b92915050565b7f4e6f7420796f7572207475726e20746f206465637279707400000000000000005f82015250565b5f6146dd601883613f78565b91506146e8826146a9565b602082019050919050565b5f6020820190508181035f83015261470a816146d1565b9050919050565b7f4d69736d6174636820696e2063617264496e646578657320616e6420646563725f8201527f797074696f6e56616c756573206c656e67746873000000000000000000000000602082015250565b5f61476b603483613f78565b915061477682614711565b604082019050919050565b5f6020820190508181035f8301526147988161475f565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6147d38383613881565b60208301905092915050565b5f602082019050919050565b5f6147f58261479f565b6147ff81856147a9565b935061480a836147b9565b805f5b8381101561483a57815161482188826147c8565b975061482c836147df565b92505060018101905061480d565b5085935050505092915050565b5f6040820190508181035f83015261485f81856147eb565b90508181036020830152614873818461376f565b90509392505050565b5f82825260208201905092915050565b5f61489682613621565b6148a0818561487c565b93506148b081856020860161363b565b6148b981613649565b840191505092915050565b6148cd81613691565b82525050565b5f6040820190508181035f8301526148eb818561488c565b90506148fa60208301846148c4565b9392505050565b5f5ffd5b5f5ffd5b5f6060828403121561491e5761491d614901565b5b6149286060613bc8565b90505f82015167ffffffffffffffff81111561494757614946614905565b5b614953848285016144e1565b5f83015250602061496684828501614461565b602083015250604061497a8482850161437f565b60408301525092915050565b5f6020828403121561499b5761499a6135bb565b5b5f82015167ffffffffffffffff8111156149b8576149b76135bf565b5b6149c484828501614909565b91505092915050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302614a297fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826149ee565b614a3386836149ee565b95508019841693508086168417925050509392505050565b5f614a65614a60614a5b846135c3565b614014565b6135c3565b9050919050565b5f819050919050565b614a7e83614a4b565b614a92614a8a82614a6c565b8484546149fa565b825550505050565b5f5f905090565b614aa9614a9a565b614ab4818484614a75565b505050565b5b81811015614ad757614acc5f82614aa1565b600181019050614aba565b5050565b601f821115614b1c57614aed816149cd565b614af6846149df565b81016020851015614b05578190505b614b19614b11856149df565b830182614ab9565b50505b505050565b5f82821c905092915050565b5f614b3c5f1984600802614b21565b1980831691505092915050565b5f614b548383614b2d565b9150826002028217905092915050565b614b6d82613621565b67ffffffffffffffff811115614b8657614b85613b6a565b5b614b9082546142fa565b614b9b828285614adb565b5f60209050601f831160018114614bcc575f8415614bba578287015190505b614bc48582614b49565b865550614c2b565b601f198416614bda866149cd565b5f5b82811015614c0157848901518255600182019150602085019450602081019050614bdc565b86831015614c1e5784890151614c1a601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b5f602082019050614c465f830184614226565b92915050565b5f8154614c58816142fa565b614c62818661362b565b9450600182165f8114614c7c5760018114614c9257614cc4565b60ff198316865281151560200286019350614cc4565b614c9b856149cd565b5f5b83811015614cbc57815481890152600182019150602081019050614c9d565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f614cf6614cf183614ccd565b614cd8565b9050919050565b5f819050919050565b5f614d18614d1383614ccd565b614cfd565b9050919050565b5f606083015f5f84018583035f870152614d398382614c4c565b92505060018401549050614d4c81614ce4565b614d59602087018261369c565b5060028401549050614d6a81614d06565b614d7760408701826136ab565b50819250505092915050565b5f6020820190508181035f830152614d9b8184614d1f565b905092915050565b5f67ffffffffffffffff821115614dbd57614dbc613b6a565b5b614dc682613649565b9050602081019050919050565b5f614de5614de084614da3565b613bc8565b905082815260208101848484011115614e0157614e00613cfb565b5b614e0c84828561363b565b509392505050565b5f82601f830112614e2857614e27613b66565b5b8151614e38848260208601614dd3565b91505092915050565b5f60208284031215614e5657614e556135bb565b5b5f82015167ffffffffffffffff811115614e7357614e726135bf565b5b614e7f84828501614e14565b91505092915050565b5f6060820190508181035f830152614ea08186613f88565b90508181036020830152614eb48185613f88565b90508181036040830152614ec88184613f88565b9050949350505050565b5f819050815f5260205f209050919050565b601f821115614f2557614ef681614ed2565b614eff846149df565b81016020851015614f0e578190505b614f22614f1a856149df565b830182614ab9565b50505b505050565b614f33826138ad565b67ffffffffffffffff811115614f4c57614f4b613b6a565b5b614f5682546142fa565b614f61828285614ee4565b5f60209050601f831160018114614f92575f8415614f80578287015190505b614f8a8582614b49565b865550614ff1565b601f198416614fa086614ed2565b5f5b82811015614fc757848901518255600182019150602085019450602081019050614fa2565b86831015614fe45784890151614fe0601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b61500281613875565b82525050565b5f60208201905061501b5f830184614ff9565b92915050565b61502a81614670565b8114615034575f5ffd5b50565b5f8151905061504581615021565b92915050565b5f67ffffffffffffffff82111561506557615064613b6a565b5b602082029050919050565b5f61508261507d8461504b565b613bc8565b9050806020840283018581111561509c5761509b613c0d565b5b835b818110156150e357805167ffffffffffffffff8111156150c1576150c0613b66565b5b8086016150ce8982614e14565b8552602085019450505060208101905061509e565b5050509392505050565b5f82601f83011261510157615100613b66565b5b600261510e848285615070565b91505092915050565b5f610180828403121561512d5761512c614901565b5b615138610180613bc8565b90505f61514784828501615037565b5f83015250602061515a8482850161437f565b602083015250604061516e8482850161437f565b60408301525060606151828482850161437f565b606083015250608061519684828501614461565b60808301525060a06151aa84828501614461565b60a08301525060c06151be84828501614461565b60c08301525060e082015167ffffffffffffffff8111156151e2576151e1614905565b5b6151ee848285016150ed565b60e0830152506101006152038482850161440c565b610100830152506101206152198482850161437f565b6101208301525061014061522f84828501614461565b6101408301525061016061524584828501614461565b6101608301525092915050565b5f60208284031215615267576152666135bb565b5b5f82015167ffffffffffffffff811115615284576152836135bf565b5b61529084828501615117565b91505092915050565b7f506c61796572206e6f74206a6f696e656420616674657220726f756e642073745f8201527f6172746564000000000000000000000000000000000000000000000000000000602082015250565b5f6152f3602583613f78565b91506152fe82615299565b604082019050919050565b5f6020820190508181035f830152615320816152e7565b9050919050565b7f506c6179657220666f6c646564000000000000000000000000000000000000005f82015250565b5f61535b600d83613f78565b915061536682615327565b602082019050919050565b5f6020820190508181035f8301526153888161534f565b9050919050565b50565b5f61539d5f83613f78565b91506153a88261538f565b5f82019050919050565b5f6040820190508181035f8301526153ca8184613f88565b905081810360208301526153dd81615392565b905092915050565b7f506c6179657220616c72656164792072657665616c65642063617264732028305f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61543f602f83613f78565b915061544a826153e5565b604082019050919050565b5f6020820190508181035f83015261546c81615433565b9050919050565b7f506c6179657220616c72656164792072657665616c65642063617264732028315f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f6154cd602f83613f78565b91506154d882615473565b604082019050919050565b5f6020820190508181035f8301526154fa816154c1565b9050919050565b5f6060820190508181035f83015261551981866141ee565b9050818103602083015261552d81856141ee565b9050818103604083015261554181846141ee565b9050949350505050565b5f67ffffffffffffffff82111561556557615564613b6a565b5b602082029050919050565b5f61558261557d8461554b565b613bc8565b9050806020840283018581111561559c5761559b613c0d565b5b835b818110156155c557806155b1888261440c565b84526020840193505060208101905061559e565b5050509392505050565b5f82601f8301126155e3576155e2613b66565b5b60026155f0848285615570565b91505092915050565b5f6040828403121561560e5761560d6135bb565b5b5f61561b848285016155cf565b91505092915050565b5f819050919050565b5f61564761564261563d84615624565b614014565b6135c3565b9050919050565b6156578161562d565b82525050565b5f6060820190508181035f830152615675818661488c565b905061568460208301856148c4565b615691604083018461564e565b949350505050565b5f606083015f8301518482035f8601526156b38282613659565b91505060208301516156c8602086018261369c565b5060408301516156db60408601826136ab565b508091505092915050565b5f604083015f8301518482035f8601526157008282615699565b9150506020830151848203602086015261571a8282615699565b9150508091505092915050565b5f6060820190508181035f83015261573f81866156e6565b9050818103602083015261575381856136ba565b9050818103604083015261576781846136ba565b9050949350505050565b5f60079050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b5f6157a482615771565b6157ae818561577b565b9350836020820285016157c085615785565b805f5b858110156157fb57848403895281516157dc85826138ff565b94506157e78361578e565b925060208a019950506001810190506157c3565b50829750879550505050505092915050565b5f6020820190508181035f830152615825818461579a565b905092915050565b600a8110615839575f5ffd5b50565b5f8151905061584a8161582d565b92915050565b5f67ffffffffffffffff82111561586a57615869613b6a565b5b602082029050919050565b5f6040828403121561588a57615889614901565b5b6158946040613bc8565b90505f6158a38482850161440c565b5f8301525060206158b68482850161440c565b60208301525092915050565b5f6158d46158cf84615850565b613bc8565b905080604084028301858111156158ee576158ed613c0d565b5b835b8181101561591757806159038882615875565b8452602084019350506040810190506158f0565b5050509392505050565b5f82601f83011261593557615934613b66565b5b60056159428482856158c2565b91505092915050565b5f610180828403121561596157615960614901565b5b61596b6060613bc8565b90505f61597a8482850161583c565b5f83015250602061598d8482850161437f565b60208301525060406159a184828501615921565b60408301525092915050565b5f61018082840312156159c3576159c26135bb565b5b5f6159d08482850161594b565b91505092915050565b5f6040820190506159ec5f830185614ff9565b6159f96020830184614235565b9392505050565b600a8110615a1157615a10613802565b5b50565b5f819050615a2182615a00565b919050565b5f615a3082615a14565b9050919050565b615a4081615a26565b82525050565b5f6080820190508181035f830152615a5e8187613f88565b90508181036020830152615a728186613f88565b9050615a816040830185615a37565b615a8e6060830184614235565b95945050505050565b7f4f6e6c792074686520726f6f6d20636f6e74726163742063616e2072657365745f8201527f20746865206465636b0000000000000000000000000000000000000000000000602082015250565b5f615af1602983613f78565b9150615afc82615a97565b604082019050919050565b5f6020820190508181035f830152615b1e81615ae5565b9050919050565b7f4d7573742070726f766964652065786163746c792035322063617264730000005f82015250565b5f615b59601d83613f78565b9150615b6482615b25565b602082019050919050565b5f6020820190508181035f830152615b8681615b4d565b9050919050565b7f57726f6e672073746167650000000000000000000000000000000000000000005f82015250565b5f615bc1600b83613f78565b9150615bcc82615b8d565b602082019050919050565b5f6020820190508181035f830152615bee81615bb5565b9050919050565b7f4e6f7420796f7572207475726e20746f2073687566666c6500000000000000005f82015250565b5f615c29601883613f78565b9150615c3482615bf5565b602082019050919050565b5f6020820190508181035f830152615c5681615c1d565b905091905056fea264697066735822122067650a6f6653c75c3c95a6305287e031cf5af14f2246b23333e2b61683cab17d64736f6c634300081d00330000000000000000000000000b377624bd9bfdeb5fa6d0c4621edbd9b2e7c1f90000000000000000000000009b6336c9b008e774c95ae1958e541cf791a54698000000000000000000000000af542de5c211a84330b17a3e25fd073373f593c9

Deployed Bytecode

0x608060405234801561000f575f5ffd5b50600436106100cd575f3560e01c80637243087a1161008a578063a377ac5f11610064578063a377ac5f146101ee578063c27a67581461020c578063c3f8295114610228578063eff02a071461025a576100cd565b80637243087a146101a857806374844e19146101b25780639597684c146101d0576100cd565b806316660082146100d15780631870fd6f146101015780632c242a181461011f578063523aadd41461013d57806362224385146101595780636681d2dd1461018a575b5f5ffd5b6100eb60048036038101906100e691906135f6565b61028a565b6040516100f89190613707565b60405180910390f35b610109610376565b60405161011691906137e2565b60405180910390f35b6101276104c8565b6040516101349190613b46565b60405180910390f35b61015760048036038101906101529190613e5e565b610d1a565b005b610173600480360381019061016e9190613ed4565b6119d5565b604051610181929190613fc0565b60405180910390f35b610192612ad6565b60405161019f919061406f565b60405180910390f35b6101b0612afb565b005b6101ba612cdf565b6040516101c79190614105565b60405180910390f35b6101d8612da9565b6040516101e59190614145565b60405180910390f35b6101f6612dce565b604051610203919061417e565b60405180910390f35b61022660048036038101906102219190614197565b612df3565b005b610242600480360381019061023d91906135f6565b61328e565b60405161025193929190614244565b60405180910390f35b610274600480360381019061026f91906135f6565b613354565b6040516102819190614280565b60405180910390f35b6102926133f3565b5f82815481106102a5576102a46142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546102cb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546102f7906142fa565b80156103425780601f1061031957610100808354040283529160200191610342565b820191905f5260205f20905b81548152906001019060200180831161032557829003601f168201915b50505050508152602001600182015f9054906101000a900460ff161515151581526020016002820154815250509050919050565b60605f5f8054905067ffffffffffffffff81111561039757610396613b6a565b5b6040519080825280602002602001820160405280156103ca57816020015b60608152602001906001900390816103b55790505b5090505f5f90505b5f805490508160ff1610156104c0575f8160ff16815481106103f7576103f66142a0565b5b905f5260205f2090600302015f018054610410906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461043c906142fa565b80156104875780601f1061045e57610100808354040283529160200191610487565b820191905f5260205f20905b81548152906001019060200180831161046a57829003601f168201915b5050505050828260ff16815181106104a2576104a16142a0565b5b602002602001018190525080806104b890614357565b9150506103d2565b508091505090565b6104d0613414565b604051806101c0016040528060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634e2786fb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610546573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105fd91906143e1565b600c81111561060f5761060e613802565b5b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e25501566040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106a29190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370984e976040518163ffffffff1660e01b8152600401602060405180830381865afa158015610711573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107359190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c89190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085e9190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2349ff06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108f49190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634ba2363a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610966573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061098a9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663af5b37996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a1d9190614393565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166397b2f5566040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab09190614420565b60ff16815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663faff660e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b469190614475565b151581526020016001600580602002604051908101604052809291905f905b82821015610c06578382018054610b7b906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054610ba7906142fa565b8015610bf25780601f10610bc957610100808354040283529160200191610bf2565b820191905f5260205f20905b815481529060010190602001808311610bd557829003601f168201915b505050505081526020019060010190610b65565b5050505081526020013073ffffffffffffffffffffffffffffffffffffffff16631870fd6f6040518163ffffffff1660e01b81526004015f60405180830381865afa158015610c57573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c7f91906145c1565b815260200160075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663eac697c96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cee573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d129190614393565b815250905090565b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da991906143e1565b90506002600c811115610dbf57610dbe613802565b5b81600c811115610dd257610dd1613802565b5b1480610e0257506004600c811115610ded57610dec613802565b5b81600c811115610e0057610dff613802565b5b145b80610e3157506006600c811115610e1c57610e1b613802565b5b81600c811115610e2f57610e2e613802565b5b145b80610e6057506008600c811115610e4b57610e4a613802565b5b81600c811115610e5e57610e5d613802565b5b145b610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e9690614652565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401610efa9190614690565b602060405180830381865afa158015610f15573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f399190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fca9190614420565b90508160ff168160ff1614611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161100b906146f3565b60405180910390fd5b8351855114611058576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104f90614781565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f19c122811f9e4d381304169d982476aabc6977d2467b14da176c96fee621b30386866040516110a0929190614847565b60405180910390a25f5f90505b85518160ff1610156111e35773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856868360ff16815181106110e9576110e86142a0565b5b60200260200101515f6040518363ffffffff1660e01b815260040161110f9291906148d3565b5f60405180830381865af4158015611129573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906111519190614986565b5f878360ff1681518110611168576111676142a0565b5b602002602001015160ff1681548110611184576111836142a0565b5b905f5260205f2090600302015f820151815f0190816111a39190614b64565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505080806111db90614357565b9150506110ad565b505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663df5a065c60016040518263ffffffff1660e01b81526004016112409190614c33565b602060405180830381865afa15801561125b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061127f9190614420565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663219406bd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112eb573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061130f9190614420565b60ff168160ff1603611951576004600c81111561132f5761132e613802565b5b84600c81111561134257611341613802565b5b03611684575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611399576113986142a0565b5b602002602001015160ff16815481106113b5576113b46142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016113dd9190614d83565b5f60405180830381865afa1580156113f7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061141f9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8a600181518110611474576114736142a0565b5b602002602001015160ff16815481106114905761148f6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016114b89190614d83565b5f60405180830381865afa1580156114d2573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114fa9190614e41565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f8b60028151811061154f5761154e6142a0565b5b602002602001015160ff168154811061156b5761156a6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016115939190614d83565b5f60405180830381865afa1580156115ad573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115d59190614e41565b90507f96e672e4b09efde01ec2235824d9eb4bf5003ba7bf63407a45bff0974415696f83838360405161160a93929190614e88565b60405180910390a18260015f60058110611627576116266142a0565b5b0190816116349190614f2a565b50816001806005811061164a576116496142a0565b5b0190816116579190614f2a565b5080600160026005811061166e5761166d6142a0565b5b01908161167b9190614f2a565b50505050611950565b6006600c81111561169857611697613802565b5b84600c8111156116ab576116aa613802565b5b036117eb575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611702576117016142a0565b5b602002602001015160ff168154811061171e5761171d6142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016117469190614d83565b5f60405180830381865afa158015611760573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117889190614e41565b90507ff503b0d30585a8adce94d89b991f26d6f675f78304e4b85e7c0d75a1d8cb0bd3816040516117b99190614280565b60405180910390a18060016003600581106117d7576117d66142a0565b5b0190816117e49190614f2a565b505061194f565b6008600c8111156117ff576117fe613802565b5b84600c81111561181257611811613802565b5b0361194e575f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad95f895f81518110611869576118686142a0565b5b602002602001015160ff1681548110611885576118846142a0565b5b905f5260205f2090600302016040518263ffffffff1660e01b81526004016118ad9190614d83565b5f60405180830381865afa1580156118c7573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118ef9190614e41565b90507f17415641f4bcccb6710699f9b6ddf69fac7f253bd194cc81dd9a2ee32c2db0a3816040516119209190614280565b60405180910390a180600160046005811061193e5761193d6142a0565b5b01908161194b9190614f2a565b50505b5b5b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156119b7575f5ffd5b505af11580156119c9573d5f5f3e3d5ffd5b50505050505050505050565b6060805f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401611a339190614690565b602060405180830381865afa158015611a4e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a729190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339fddff6836040518263ffffffff1660e01b8152600401611acf9190615008565b5f60405180830381865afa158015611ae9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b119190615252565b905080610140015115611b59576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5090615309565b60405180910390fd5b806080015115611b9e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9590615371565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e001515f60028110611bf257611bf16142a0565b5b60200201516040518263ffffffff1660e01b8152600401611c1391906153b2565b602060405180830381865afa158015611c2e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c529190614475565b611c91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8890615455565b60405180910390fd5b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c04407a48260e00151600160028110611ce657611ce56142a0565b5b60200201516040518263ffffffff1660e01b8152600401611d0791906153b2565b602060405180830381865afa158015611d22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d469190614475565b611d85576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d7c906154e3565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fcf675dbb98179665682abaede1b14f82aa2dde6d99e83b602058a320c435f29f888888604051611dcf93929190615501565b60405180910390a25f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166339d505fc846040518263ffffffff1660e01b8152600401611e329190615008565b6040805180830381865afa158015611e4c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e7091906155f9565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16895f6101006040518463ffffffff1660e01b8152600401611eb09392919061565d565b5f60405180830381865af4158015611eca573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ef29190614986565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168b5f6101006040518463ffffffff1660e01b8152600401611f329392919061565d565b5f60405180830381865af4158015611f4c573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611f749190614986565b90505f5f845f60028110611f8b57611f8a6142a0565b5b602002015160ff1681548110611fa457611fa36142a0565b5b905f5260205f2090600302016040518060600160405290815f82018054611fca906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054611ff6906142fa565b80156120415780601f1061201857610100808354040283529160200191612041565b820191905f5260205f20905b81548152906001019060200180831161202457829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f5f85600160028110612086576120856142a0565b5b602002015160ff168154811061209f5761209e6142a0565b5b905f5260205f2090600302016040518060600160405290815f820180546120c5906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546120f1906142fa565b801561213c5780601f106121135761010080835404028352916020019161213c565b820191905f5260205f20905b81548152906001019060200180831161211f57829003601f168201915b50505050508152602001600182015f9054906101000a900460ff1615151515815260200160028201548152505090505f60405180604001604052808581526020018481525090505f60405180604001604052808681526020018481525090505f73f2635f00300f16d9aca57f955091cc24dd01f7d16385056b168e5f6101006040518463ffffffff1660e01b81526004016121d99392919061565d565b5f60405180830381865af41580156121f3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061221b9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858a856040518463ffffffff1660e01b815260040161227c93929190615727565b5f604051808303815f875af1158015612297573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906122bf9190614986565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c5cbefa858b866040518463ffffffff1660e01b815260040161232093929190615727565b5f604051808303815f875af115801561233b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906123639190614986565b905060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9836040518263ffffffff1660e01b81526004016123bf9190613707565b5f60405180830381865afa1580156123d9573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124019190614e41565b9d5060065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636ab54ad9826040518263ffffffff1660e01b815260040161245d9190613707565b5f60405180830381865afa158015612477573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061249f9190614e41565b9c5050505050505050505050838160e001515f600281106124c3576124c26142a0565b5b6020020181905250828160e001516001600281106124e4576124e36142a0565b5b60200201819052506124f461349d565b84815f60078110612508576125076142a0565b5b60200201819052508381600160078110612525576125246142a0565b5b602002018190525060015f60058110612541576125406142a0565b5b01805461254d906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612579906142fa565b80156125c45780601f1061259b576101008083540402835291602001916125c4565b820191905f5260205f20905b8154815290600101906020018083116125a757829003601f168201915b5050505050816002600781106125dd576125dc6142a0565b5b6020020181905250600180600581106125f9576125f86142a0565b5b018054612605906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612631906142fa565b801561267c5780601f106126535761010080835404028352916020019161267c565b820191905f5260205f20905b81548152906001019060200180831161265f57829003601f168201915b505050505081600360078110612695576126946142a0565b5b602002018190525060016002600581106126b2576126b16142a0565b5b0180546126be906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546126ea906142fa565b80156127355780601f1061270c57610100808354040283529160200191612735565b820191905f5260205f20905b81548152906001019060200180831161271857829003601f168201915b50505050508160046007811061274e5761274d6142a0565b5b6020020181905250600160036005811061276b5761276a6142a0565b5b018054612777906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546127a3906142fa565b80156127ee5780601f106127c5576101008083540402835291602001916127ee565b820191905f5260205f20905b8154815290600101906020018083116127d157829003601f168201915b505050505081600560078110612807576128066142a0565b5b60200201819052506001600460058110612824576128236142a0565b5b018054612830906142fa565b80601f016020809104026020016040519081016040528092919081815260200182805461285c906142fa565b80156128a75780601f1061287e576101008083540402835291602001916128a7565b820191905f5260205f20905b81548152906001019060200180831161288a57829003601f168201915b5050505050816006600781106128c0576128bf6142a0565b5b60200201819052505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166369a684f6836040518263ffffffff1660e01b8152600401612923919061580d565b61018060405180830381865afa15801561293f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061296391906159ad565b90505f8160200151905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630f53fc4b86836040518363ffffffff1660e01b81526004016129c99291906159d9565b5f604051808303815f87803b1580156129e0575f5ffd5b505af11580156129f2573d5f5f3e3d5ffd5b505050503373ffffffffffffffffffffffffffffffffffffffff167f56d64de549930cfd6e6b5189293e8fdc29c0f675f36e2b435eef17bd777bc24e8888855f015185604051612a459493929190615a46565b60405180910390a260075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015612ab3575f5ffd5b505af1158015612ac5573d5f5f3e3d5ffd5b505050505050505050935093915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612b8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b8190615b07565b60405180910390fd5b5f5f90505b60348160ff161015612c5e5760405180606001604052806040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525081526020015f151581526020016101008152505f8260ff1681548110612c0557612c046142a0565b5b905f5260205f2090600302015f820151815f019081612c249190614b64565b506020820151816001015f6101000a81548160ff021916908315150217905550604082015181600201559050508080600101915050612b8f565b506040518060a0016040528060405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f815250815260200160405180602001604052805f8152508152506001906005612cdc9291906134c4565b50565b612ce7613510565b6001600580602002604051908101604052809291905f905b82821015612da0578382018054612d15906142fa565b80601f0160208091040260200160405190810160405280929190818152602001828054612d41906142fa565b8015612d8c5780601f10612d6357610100808354040283529160200191612d8c565b820191905f5260205f20905b815481529060010190602001808311612d6f57829003601f168201915b505050505081526020019060010190612cff565b50505050905090565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6034815114612e37576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e2e90615b6f565b60405180910390fd5b6001600c811115612e4b57612e4a613802565b5b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c040e6b86040518163ffffffff1660e01b8152600401602060405180830381865afa158015612eb5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed991906143e1565b600c811115612eeb57612eea613802565b5b14612f2b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f2290615bd7565b60405180910390fd5b5f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166322876d51336040518263ffffffff1660e01b8152600401612f869190614690565b602060405180830381865afa158015612fa1573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fc59190614420565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb9bdab46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613032573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130569190614420565b90508160ff168160ff16146130a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161309790615c3f565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f73928f31daa4d318dd150e598b3ba6a20b7809ac97177fd3871ccbd66314394d846040516130e691906137e2565b60405180910390a25f5f90505b83518160ff16101561320c5773f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856858360ff168151811061312f5761312e6142a0565b5b60200260200101515f6040518363ffffffff1660e01b81526004016131559291906148d3565b5f60405180830381865af415801561316f573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906131979190614986565b5f8260ff16815481106131ad576131ac6142a0565b5b905f5260205f2090600302015f820151815f0190816131cc9190614b64565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050808061320490614357565b9150506130f3565b5060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c5caa27b6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015613273575f5ffd5b505af1158015613285573d5f5f3e3d5ffd5b50505050505050565b5f818154811061329c575f80fd5b905f5260205f2090600302015f91509050805f0180546132bb906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546132e7906142fa565b80156133325780601f1061330957610100808354040283529160200191613332565b820191905f5260205f20905b81548152906001019060200180831161331557829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b60018160058110613363575f80fd5b015f915090508054613374906142fa565b80601f01602080910402602001604051908101604052809291908181526020018280546133a0906142fa565b80156133eb5780601f106133c2576101008083540402835291602001916133eb565b820191905f5260205f20905b8154815290600101906020018083116133ce57829003601f168201915b505050505081565b6040518060600160405280606081526020015f151581526020015f81525090565b604051806101c001604052805f81526020015f600c81111561343957613438613802565b5b81526020015f81526020015f81526020015f60ff1681526020015f60ff1681526020015f60ff1681526020015f81526020015f81526020015f60ff1681526020015f1515815260200161348a613510565b8152602001606081526020015f81525090565b6040518060e001604052806007905b60608152602001906001900390816134ac5790505090565b82600581019282156134ff579160200282015b828111156134fe5782518290816134ee9190614f2a565b50916020019190600101906134d7565b5b50905061350c9190613537565b5090565b6040518060a001604052806005905b606081526020019060019003908161351f5790505090565b5b80821115613556575f818161354d919061355a565b50600101613538565b5090565b508054613566906142fa565b5f825580601f106135775750613594565b601f0160209004905f5260205f20908101906135939190613597565b5b50565b5b808211156135ae575f815f905550600101613598565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6135d5816135c3565b81146135df575f5ffd5b50565b5f813590506135f0816135cc565b92915050565b5f6020828403121561360b5761360a6135bb565b5b5f613618848285016135e2565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61366382613621565b61366d818561362b565b935061367d81856020860161363b565b61368681613649565b840191505092915050565b5f8115159050919050565b6136a581613691565b82525050565b6136b4816135c3565b82525050565b5f606083015f8301518482035f8601526136d48282613659565b91505060208301516136e9602086018261369c565b5060408301516136fc60408601826136ab565b508091505092915050565b5f6020820190508181035f83015261371f81846136ba565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f61375b8383613659565b905092915050565b5f602082019050919050565b5f61377982613727565b6137838185613731565b93508360208202850161379585613741565b805f5b858110156137d057848403895281516137b18582613750565b94506137bc83613763565b925060208a01995050600181019050613798565b50829750879550505050505092915050565b5f6020820190508181035f8301526137fa818461376f565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600d81106138405761383f613802565b5b50565b5f8190506138508261382f565b919050565b5f61385f82613843565b9050919050565b61386f81613855565b82525050565b5f60ff82169050919050565b61388a81613875565b82525050565b5f60059050919050565b5f81905092915050565b5f819050919050565b5f81519050919050565b5f82825260208201905092915050565b5f6138d1826138ad565b6138db81856138b7565b93506138eb81856020860161363b565b6138f481613649565b840191505092915050565b5f61390a83836138c7565b905092915050565b5f602082019050919050565b5f61392882613890565b613932818561389a565b935083602082028501613944856138a4565b805f5b8581101561397f578484038952815161396085826138ff565b945061396b83613912565b925060208a01995050600181019050613947565b50829750879550505050505092915050565b5f82825260208201905092915050565b5f6139ab82613727565b6139b58185613991565b9350836020820285016139c785613741565b805f5b85811015613a0257848403895281516139e38582613750565b94506139ee83613763565b925060208a019950506001810190506139ca565b50829750879550505050505092915050565b5f6101c083015f830151613a2a5f8601826136ab565b506020830151613a3d6020860182613866565b506040830151613a5060408601826136ab565b506060830151613a6360608601826136ab565b506080830151613a766080860182613881565b5060a0830151613a8960a0860182613881565b5060c0830151613a9c60c0860182613881565b5060e0830151613aaf60e08601826136ab565b50610100830151613ac46101008601826136ab565b50610120830151613ad9610120860182613881565b50610140830151613aee61014086018261369c565b50610160830151848203610160860152613b08828261391e565b915050610180830151848203610180860152613b2482826139a1565b9150506101a0830151613b3b6101a08601826136ab565b508091505092915050565b5f6020820190508181035f830152613b5e8184613a14565b905092915050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613ba082613649565b810181811067ffffffffffffffff82111715613bbf57613bbe613b6a565b5b80604052505050565b5f613bd16135b2565b9050613bdd8282613b97565b919050565b5f67ffffffffffffffff821115613bfc57613bfb613b6a565b5b602082029050602081019050919050565b5f5ffd5b613c1a81613875565b8114613c24575f5ffd5b50565b5f81359050613c3581613c11565b92915050565b5f613c4d613c4884613be2565b613bc8565b90508083825260208201905060208402830185811115613c7057613c6f613c0d565b5b835b81811015613c995780613c858882613c27565b845260208401935050602081019050613c72565b5050509392505050565b5f82601f830112613cb757613cb6613b66565b5b8135613cc7848260208601613c3b565b91505092915050565b5f67ffffffffffffffff821115613cea57613ce9613b6a565b5b602082029050602081019050919050565b5f5ffd5b5f67ffffffffffffffff821115613d1957613d18613b6a565b5b613d2282613649565b9050602081019050919050565b828183375f83830152505050565b5f613d4f613d4a84613cff565b613bc8565b905082815260208101848484011115613d6b57613d6a613cfb565b5b613d76848285613d2f565b509392505050565b5f82601f830112613d9257613d91613b66565b5b8135613da2848260208601613d3d565b91505092915050565b5f613dbd613db884613cd0565b613bc8565b90508083825260208201905060208402830185811115613de057613ddf613c0d565b5b835b81811015613e2757803567ffffffffffffffff811115613e0557613e04613b66565b5b808601613e128982613d7e565b85526020850194505050602081019050613de2565b5050509392505050565b5f82601f830112613e4557613e44613b66565b5b8135613e55848260208601613dab565b91505092915050565b5f5f60408385031215613e7457613e736135bb565b5b5f83013567ffffffffffffffff811115613e9157613e906135bf565b5b613e9d85828601613ca3565b925050602083013567ffffffffffffffff811115613ebe57613ebd6135bf565b5b613eca85828601613e31565b9150509250929050565b5f5f5f60608486031215613eeb57613eea6135bb565b5b5f84013567ffffffffffffffff811115613f0857613f076135bf565b5b613f1486828701613d7e565b935050602084013567ffffffffffffffff811115613f3557613f346135bf565b5b613f4186828701613d7e565b925050604084013567ffffffffffffffff811115613f6257613f616135bf565b5b613f6e86828701613d7e565b9150509250925092565b5f82825260208201905092915050565b5f613f92826138ad565b613f9c8185613f78565b9350613fac81856020860161363b565b613fb581613649565b840191505092915050565b5f6040820190508181035f830152613fd88185613f88565b90508181036020830152613fec8184613f88565b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61403761403261402d84613ff5565b614014565b613ff5565b9050919050565b5f6140488261401d565b9050919050565b5f6140598261403e565b9050919050565b6140698161404f565b82525050565b5f6020820190506140825f830184614060565b92915050565b5f81905092915050565b5f61409c82613890565b6140a68185614088565b9350836020820285016140b8856138a4565b805f5b858110156140f357848403895281516140d485826138ff565b94506140df83613912565b925060208a019950506001810190506140bb565b50829750879550505050505092915050565b5f6020820190508181035f83015261411d8184614092565b905092915050565b5f61412f8261403e565b9050919050565b61413f81614125565b82525050565b5f6020820190506141585f830184614136565b92915050565b5f6141688261403e565b9050919050565b6141788161415e565b82525050565b5f6020820190506141915f83018461416f565b92915050565b5f602082840312156141ac576141ab6135bb565b5b5f82013567ffffffffffffffff8111156141c9576141c86135bf565b5b6141d584828501613e31565b91505092915050565b5f82825260208201905092915050565b5f6141f882613621565b61420281856141de565b935061421281856020860161363b565b61421b81613649565b840191505092915050565b61422f81613691565b82525050565b61423e816135c3565b82525050565b5f6060820190508181035f83015261425c81866141ee565b905061426b6020830185614226565b6142786040830184614235565b949350505050565b5f6020820190508181035f8301526142988184613f88565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061431157607f821691505b602082108103614324576143236142cd565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61436182613875565b915060ff82036143745761437361432a565b5b600182019050919050565b5f8151905061438d816135cc565b92915050565b5f602082840312156143a8576143a76135bb565b5b5f6143b58482850161437f565b91505092915050565b600d81106143ca575f5ffd5b50565b5f815190506143db816143be565b92915050565b5f602082840312156143f6576143f56135bb565b5b5f614403848285016143cd565b91505092915050565b5f8151905061441a81613c11565b92915050565b5f60208284031215614435576144346135bb565b5b5f6144428482850161440c565b91505092915050565b61445481613691565b811461445e575f5ffd5b50565b5f8151905061446f8161444b565b92915050565b5f6020828403121561448a576144896135bb565b5b5f61449784828501614461565b91505092915050565b5f6144b26144ad84613cff565b613bc8565b9050828152602081018484840111156144ce576144cd613cfb565b5b6144d984828561363b565b509392505050565b5f82601f8301126144f5576144f4613b66565b5b81516145058482602086016144a0565b91505092915050565b5f61452061451b84613cd0565b613bc8565b9050808382526020820190506020840283018581111561454357614542613c0d565b5b835b8181101561458a57805167ffffffffffffffff81111561456857614567613b66565b5b80860161457589826144e1565b85526020850194505050602081019050614545565b5050509392505050565b5f82601f8301126145a8576145a7613b66565b5b81516145b884826020860161450e565b91505092915050565b5f602082840312156145d6576145d56135bb565b5b5f82015167ffffffffffffffff8111156145f3576145f26135bf565b5b6145ff84828501614594565b91505092915050565b7f47616d65206973206e6f7420696e20612072657665616c2073746167650000005f82015250565b5f61463c601d83613f78565b915061464782614608565b602082019050919050565b5f6020820190508181035f83015261466981614630565b9050919050565b5f61467a82613ff5565b9050919050565b61468a81614670565b82525050565b5f6020820190506146a35f830184614681565b92915050565b7f4e6f7420796f7572207475726e20746f206465637279707400000000000000005f82015250565b5f6146dd601883613f78565b91506146e8826146a9565b602082019050919050565b5f6020820190508181035f83015261470a816146d1565b9050919050565b7f4d69736d6174636820696e2063617264496e646578657320616e6420646563725f8201527f797074696f6e56616c756573206c656e67746873000000000000000000000000602082015250565b5f61476b603483613f78565b915061477682614711565b604082019050919050565b5f6020820190508181035f8301526147988161475f565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6147d38383613881565b60208301905092915050565b5f602082019050919050565b5f6147f58261479f565b6147ff81856147a9565b935061480a836147b9565b805f5b8381101561483a57815161482188826147c8565b975061482c836147df565b92505060018101905061480d565b5085935050505092915050565b5f6040820190508181035f83015261485f81856147eb565b90508181036020830152614873818461376f565b90509392505050565b5f82825260208201905092915050565b5f61489682613621565b6148a0818561487c565b93506148b081856020860161363b565b6148b981613649565b840191505092915050565b6148cd81613691565b82525050565b5f6040820190508181035f8301526148eb818561488c565b90506148fa60208301846148c4565b9392505050565b5f5ffd5b5f5ffd5b5f6060828403121561491e5761491d614901565b5b6149286060613bc8565b90505f82015167ffffffffffffffff81111561494757614946614905565b5b614953848285016144e1565b5f83015250602061496684828501614461565b602083015250604061497a8482850161437f565b60408301525092915050565b5f6020828403121561499b5761499a6135bb565b5b5f82015167ffffffffffffffff8111156149b8576149b76135bf565b5b6149c484828501614909565b91505092915050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302614a297fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826149ee565b614a3386836149ee565b95508019841693508086168417925050509392505050565b5f614a65614a60614a5b846135c3565b614014565b6135c3565b9050919050565b5f819050919050565b614a7e83614a4b565b614a92614a8a82614a6c565b8484546149fa565b825550505050565b5f5f905090565b614aa9614a9a565b614ab4818484614a75565b505050565b5b81811015614ad757614acc5f82614aa1565b600181019050614aba565b5050565b601f821115614b1c57614aed816149cd565b614af6846149df565b81016020851015614b05578190505b614b19614b11856149df565b830182614ab9565b50505b505050565b5f82821c905092915050565b5f614b3c5f1984600802614b21565b1980831691505092915050565b5f614b548383614b2d565b9150826002028217905092915050565b614b6d82613621565b67ffffffffffffffff811115614b8657614b85613b6a565b5b614b9082546142fa565b614b9b828285614adb565b5f60209050601f831160018114614bcc575f8415614bba578287015190505b614bc48582614b49565b865550614c2b565b601f198416614bda866149cd565b5f5b82811015614c0157848901518255600182019150602085019450602081019050614bdc565b86831015614c1e5784890151614c1a601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b5f602082019050614c465f830184614226565b92915050565b5f8154614c58816142fa565b614c62818661362b565b9450600182165f8114614c7c5760018114614c9257614cc4565b60ff198316865281151560200286019350614cc4565b614c9b856149cd565b5f5b83811015614cbc57815481890152600182019150602081019050614c9d565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f614cf6614cf183614ccd565b614cd8565b9050919050565b5f819050919050565b5f614d18614d1383614ccd565b614cfd565b9050919050565b5f606083015f5f84018583035f870152614d398382614c4c565b92505060018401549050614d4c81614ce4565b614d59602087018261369c565b5060028401549050614d6a81614d06565b614d7760408701826136ab565b50819250505092915050565b5f6020820190508181035f830152614d9b8184614d1f565b905092915050565b5f67ffffffffffffffff821115614dbd57614dbc613b6a565b5b614dc682613649565b9050602081019050919050565b5f614de5614de084614da3565b613bc8565b905082815260208101848484011115614e0157614e00613cfb565b5b614e0c84828561363b565b509392505050565b5f82601f830112614e2857614e27613b66565b5b8151614e38848260208601614dd3565b91505092915050565b5f60208284031215614e5657614e556135bb565b5b5f82015167ffffffffffffffff811115614e7357614e726135bf565b5b614e7f84828501614e14565b91505092915050565b5f6060820190508181035f830152614ea08186613f88565b90508181036020830152614eb48185613f88565b90508181036040830152614ec88184613f88565b9050949350505050565b5f819050815f5260205f209050919050565b601f821115614f2557614ef681614ed2565b614eff846149df565b81016020851015614f0e578190505b614f22614f1a856149df565b830182614ab9565b50505b505050565b614f33826138ad565b67ffffffffffffffff811115614f4c57614f4b613b6a565b5b614f5682546142fa565b614f61828285614ee4565b5f60209050601f831160018114614f92575f8415614f80578287015190505b614f8a8582614b49565b865550614ff1565b601f198416614fa086614ed2565b5f5b82811015614fc757848901518255600182019150602085019450602081019050614fa2565b86831015614fe45784890151614fe0601f891682614b2d565b8355505b6001600288020188555050505b505050505050565b61500281613875565b82525050565b5f60208201905061501b5f830184614ff9565b92915050565b61502a81614670565b8114615034575f5ffd5b50565b5f8151905061504581615021565b92915050565b5f67ffffffffffffffff82111561506557615064613b6a565b5b602082029050919050565b5f61508261507d8461504b565b613bc8565b9050806020840283018581111561509c5761509b613c0d565b5b835b818110156150e357805167ffffffffffffffff8111156150c1576150c0613b66565b5b8086016150ce8982614e14565b8552602085019450505060208101905061509e565b5050509392505050565b5f82601f83011261510157615100613b66565b5b600261510e848285615070565b91505092915050565b5f610180828403121561512d5761512c614901565b5b615138610180613bc8565b90505f61514784828501615037565b5f83015250602061515a8482850161437f565b602083015250604061516e8482850161437f565b60408301525060606151828482850161437f565b606083015250608061519684828501614461565b60808301525060a06151aa84828501614461565b60a08301525060c06151be84828501614461565b60c08301525060e082015167ffffffffffffffff8111156151e2576151e1614905565b5b6151ee848285016150ed565b60e0830152506101006152038482850161440c565b610100830152506101206152198482850161437f565b6101208301525061014061522f84828501614461565b6101408301525061016061524584828501614461565b6101608301525092915050565b5f60208284031215615267576152666135bb565b5b5f82015167ffffffffffffffff811115615284576152836135bf565b5b61529084828501615117565b91505092915050565b7f506c61796572206e6f74206a6f696e656420616674657220726f756e642073745f8201527f6172746564000000000000000000000000000000000000000000000000000000602082015250565b5f6152f3602583613f78565b91506152fe82615299565b604082019050919050565b5f6020820190508181035f830152615320816152e7565b9050919050565b7f506c6179657220666f6c646564000000000000000000000000000000000000005f82015250565b5f61535b600d83613f78565b915061536682615327565b602082019050919050565b5f6020820190508181035f8301526153888161534f565b9050919050565b50565b5f61539d5f83613f78565b91506153a88261538f565b5f82019050919050565b5f6040820190508181035f8301526153ca8184613f88565b905081810360208301526153dd81615392565b905092915050565b7f506c6179657220616c72656164792072657665616c65642063617264732028305f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f61543f602f83613f78565b915061544a826153e5565b604082019050919050565b5f6020820190508181035f83015261546c81615433565b9050919050565b7f506c6179657220616c72656164792072657665616c65642063617264732028315f8201527f2920696e207468697320726f756e640000000000000000000000000000000000602082015250565b5f6154cd602f83613f78565b91506154d882615473565b604082019050919050565b5f6020820190508181035f8301526154fa816154c1565b9050919050565b5f6060820190508181035f83015261551981866141ee565b9050818103602083015261552d81856141ee565b9050818103604083015261554181846141ee565b9050949350505050565b5f67ffffffffffffffff82111561556557615564613b6a565b5b602082029050919050565b5f61558261557d8461554b565b613bc8565b9050806020840283018581111561559c5761559b613c0d565b5b835b818110156155c557806155b1888261440c565b84526020840193505060208101905061559e565b5050509392505050565b5f82601f8301126155e3576155e2613b66565b5b60026155f0848285615570565b91505092915050565b5f6040828403121561560e5761560d6135bb565b5b5f61561b848285016155cf565b91505092915050565b5f819050919050565b5f61564761564261563d84615624565b614014565b6135c3565b9050919050565b6156578161562d565b82525050565b5f6060820190508181035f830152615675818661488c565b905061568460208301856148c4565b615691604083018461564e565b949350505050565b5f606083015f8301518482035f8601526156b38282613659565b91505060208301516156c8602086018261369c565b5060408301516156db60408601826136ab565b508091505092915050565b5f604083015f8301518482035f8601526157008282615699565b9150506020830151848203602086015261571a8282615699565b9150508091505092915050565b5f6060820190508181035f83015261573f81866156e6565b9050818103602083015261575381856136ba565b9050818103604083015261576781846136ba565b9050949350505050565b5f60079050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b5f6157a482615771565b6157ae818561577b565b9350836020820285016157c085615785565b805f5b858110156157fb57848403895281516157dc85826138ff565b94506157e78361578e565b925060208a019950506001810190506157c3565b50829750879550505050505092915050565b5f6020820190508181035f830152615825818461579a565b905092915050565b600a8110615839575f5ffd5b50565b5f8151905061584a8161582d565b92915050565b5f67ffffffffffffffff82111561586a57615869613b6a565b5b602082029050919050565b5f6040828403121561588a57615889614901565b5b6158946040613bc8565b90505f6158a38482850161440c565b5f8301525060206158b68482850161440c565b60208301525092915050565b5f6158d46158cf84615850565b613bc8565b905080604084028301858111156158ee576158ed613c0d565b5b835b8181101561591757806159038882615875565b8452602084019350506040810190506158f0565b5050509392505050565b5f82601f83011261593557615934613b66565b5b60056159428482856158c2565b91505092915050565b5f610180828403121561596157615960614901565b5b61596b6060613bc8565b90505f61597a8482850161583c565b5f83015250602061598d8482850161437f565b60208301525060406159a184828501615921565b60408301525092915050565b5f61018082840312156159c3576159c26135bb565b5b5f6159d08482850161594b565b91505092915050565b5f6040820190506159ec5f830185614ff9565b6159f96020830184614235565b9392505050565b600a8110615a1157615a10613802565b5b50565b5f819050615a2182615a00565b919050565b5f615a3082615a14565b9050919050565b615a4081615a26565b82525050565b5f6080820190508181035f830152615a5e8187613f88565b90508181036020830152615a728186613f88565b9050615a816040830185615a37565b615a8e6060830184614235565b95945050505050565b7f4f6e6c792074686520726f6f6d20636f6e74726163742063616e2072657365745f8201527f20746865206465636b0000000000000000000000000000000000000000000000602082015250565b5f615af1602983613f78565b9150615afc82615a97565b604082019050919050565b5f6020820190508181035f830152615b1e81615ae5565b9050919050565b7f4d7573742070726f766964652065786163746c792035322063617264730000005f82015250565b5f615b59601d83613f78565b9150615b6482615b25565b602082019050919050565b5f6020820190508181035f830152615b8681615b4d565b9050919050565b7f57726f6e672073746167650000000000000000000000000000000000000000005f82015250565b5f615bc1600b83613f78565b9150615bcc82615b8d565b602082019050919050565b5f6020820190508181035f830152615bee81615bb5565b9050919050565b7f4e6f7420796f7572207475726e20746f2073687566666c6500000000000000005f82015250565b5f615c29601883613f78565b9150615c3482615bf5565b602082019050919050565b5f6020820190508181035f830152615c5681615c1d565b905091905056fea264697066735822122067650a6f6653c75c3c95a6305287e031cf5af14f2246b23333e2b61683cab17d64736f6c634300081d0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000000b377624bd9bfdeb5fa6d0c4621edbd9b2e7c1f90000000000000000000000009b6336c9b008e774c95ae1958e541cf791a54698000000000000000000000000af542de5c211a84330b17a3e25fd073373f593c9

-----Decoded View---------------
Arg [0] : _texasHoldemRoom (address): 0x0B377624bd9BFDeB5fA6d0C4621EdBD9B2E7C1F9
Arg [1] : _cryptoUtils (address): 0x9B6336C9B008E774c95AE1958e541Cf791A54698
Arg [2] : _handEvaluator (address): 0xAF542De5c211a84330b17A3E25fD073373F593C9

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000000b377624bd9bfdeb5fa6d0c4621edbd9b2e7c1f9
Arg [1] : 0000000000000000000000009b6336c9b008e774c95ae1958e541cf791a54698
Arg [2] : 000000000000000000000000af542de5c211a84330b17a3e25fd073373f593c9


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
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.