Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
CryptoUtils
Compiler Version
v0.8.29+commit.ab55807c
Optimization Enabled:
No with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import "./BigNumbers/BigNumbers.sol";
import "./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;
// 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 "./CryptoUtils.sol";
import "./DeckHandler.sol";
contract TexasHoldemRoom {
using BigNumbers for BigNumber;
enum GameStage {
Idle, // 0
Shuffle, // 1
RevealDeal, // 2
Preflop, // 3
RevealFlop, // 4
Flop, // 5
RevealTurn, // 6
Turn, // 7
RevealRiver, // 8
River, // 9
Showdown, // 10
Break, // 11
Ended // 12
}
enum Action {
None, // 0
Call, // 1
Raise, // 2
Check, // 3
Fold // 4
}
struct Player {
address addr;
uint256 chips;
uint256 currentStageBet;
uint256 totalRoundBet;
bool hasFolded;
bool isAllIn;
bool hasChecked;
string[2] cards;
uint8 seatPosition;
uint256 handScore;
bool joinedAndWaitingForNextRound;
bool leavingAfterRoundEnds;
}
uint256 public roundNumber;
GameStage public stage;
uint256 public pot;
uint256 public currentStageBet; // per player (to stay in the round)
uint256 public smallBlind;
uint256 public bigBlind;
uint8 public dealerPosition;
/**
* @dev The current player that should take an action (background or bet action)
* This is the index of the player in the players array. Not the seat position.
*/
uint8 public currentPlayerIndex;
uint8 public lastRaiseIndex;
uint256 public lastActionTimestamp;
uint8 public constant MAX_PLAYERS = 10;
uint8 public constant MIN_PLAYERS = 2;
uint8 public constant EMPTY_SEAT = 255;
uint256 public constant STARTING_CHIPS = 1000;
CryptoUtils public cryptoUtils;
DeckHandler public deckHandler;
Player[MAX_PLAYERS] public players;
uint8[MAX_PLAYERS] public seatPositionToPlayerIndex;
uint8 public numPlayers;
bool public isPrivate;
event GameStarted(uint256 dealerPosition);
event NewStage(GameStage stage);
event PlayerMoved(address indexed player, Action indexed action, uint256 amount);
event PotWon(address[] winners, uint8[] winnerPlayerIndexes, uint256 amount);
event InvalidCardsReported(address indexed player);
event PlayerJoined(
address indexed player, uint8 indexed playerIndex, uint8 indexed seatPosition
);
event PlayerLeft(address indexed player, uint8 indexed playerIndex, uint8 indexed seatPosition);
// a non-player could report an idle player
event IdlePlayerKicked(
address indexed addressReporting, address indexed playerReported, uint256 timeElapsed
);
// event THP_Log(string message);
constructor(address _cryptoUtils, uint256 _smallBlind, bool _isPrivate) {
cryptoUtils = CryptoUtils(_cryptoUtils);
smallBlind = _smallBlind;
bigBlind = _smallBlind * 2;
stage = GameStage.Idle;
roundNumber = 0;
isPrivate = _isPrivate;
dealerPosition = 0;
currentPlayerIndex = 0;
numPlayers = 0;
// possibly move this to setDeckHandler() to reduce initcode size
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
seatPositionToPlayerIndex[i] = EMPTY_SEAT;
players[i] = Player({
addr: address(0),
chips: 0,
currentStageBet: 0,
totalRoundBet: 0,
hasFolded: false,
hasChecked: false,
isAllIn: false,
cards: ["", ""],
seatPosition: i,
handScore: 0,
joinedAndWaitingForNextRound: false,
leavingAfterRoundEnds: false
});
}
}
/**
* @dev Should only be set by the deployer contract. Can only be called once.
*/
function setDeckHandler(address _deckHandler) external {
require(address(deckHandler) == address(0), "DeckHandler already set");
deckHandler = DeckHandler(_deckHandler);
}
/**
* @dev Returns the index of the player in the players array for a given address
* @dev Reverts if the player is not found in the players array
*/
function getPlayerIndexFromAddr(address addr) external view returns (uint8) {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr == addr) {
return i;
}
}
revert("Player not found for given address");
}
/**
* @dev Finds the next active player index clockwise of the current player using seat position
* @dev Skips players that have folded or are all-in
* @dev Returns the current player index if no active players are found
*/
function getNextActivePlayer(bool requireActive) public view returns (uint8) {
// get the current player's seat position
uint8 currentSeatPosition = players[currentPlayerIndex].seatPosition; // 1, 1
// loop over the players in the ascending order of their seat positions
// until we find an active player
// TODO: create a number of players that played in the current round and use that for the modulo
// and for the next seat index. (don't use players that joined the game after the round started)
// todo : % numPlayers or % MAX_PLAYERS?
uint8 nextSeatIndex = (currentSeatPosition + 1) % MAX_PLAYERS; // 2 % 2 = 0
while (nextSeatIndex != currentSeatPosition) {
// TODO: add a status for a player that has joined, but did not start the round
uint8 playerIndex = seatPositionToPlayerIndex[nextSeatIndex];
// skip empty seats and players that joined after the round started
if (playerIndex != EMPTY_SEAT) {
Player memory checkPlayer = players[playerIndex];
if (!checkPlayer.joinedAndWaitingForNextRound) {
// TODO: check if the player has cards (joined before the round started)
if (!requireActive) {
return playerIndex;
}
if (!checkPlayer.hasFolded && !checkPlayer.isAllIn) {
return playerIndex;
}
}
}
nextSeatIndex = (nextSeatIndex + 1) % MAX_PLAYERS;
}
// if no active players are found, return the current player
return currentPlayerIndex;
}
function removePlayer(uint8 playerIndex) internal {
// Reset player states
players[playerIndex].joinedAndWaitingForNextRound = false;
players[playerIndex].leavingAfterRoundEnds = false;
// saving contract size and not setting to 0. These are set to 0 when a new player joins.
// players[playerIndex].currentStageBet = 0;
// players[playerIndex].totalRoundBet = 0;
// players[playerIndex].hasFolded = false;
// players[playerIndex].hasChecked = false;
// players[playerIndex].isAllIn = false;
// players[playerIndex].cards = ["", ""];
players[playerIndex].handScore = 0;
// players[playerIndex].chips = 0;
seatPositionToPlayerIndex[players[playerIndex].seatPosition] = EMPTY_SEAT;
numPlayers--;
emit PlayerLeft(players[playerIndex].addr, playerIndex, players[playerIndex].seatPosition);
players[playerIndex].addr = address(0);
// players[playerIndex].seatPosition = EMPTY_SEAT; // intialized as playerIndex in the constructor
}
/**
* @dev This function is callable by players in the room. If the player is currently waiting
* for the next round to start, they will be removed from the room immediately.
*
* @dev If the player is currently in the middle of a round (folded or active),
* they will be removed from the room at the end of the round.
*/
function leaveGame() external {
uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
require(playerIndex != EMPTY_SEAT, "Player not in game");
if (players[playerIndex].joinedAndWaitingForNextRound) {
// set their seat as empty and remove their player from the players array
removePlayer(playerIndex);
} else {
// will be removed from the game at the end of the round
players[playerIndex].leavingAfterRoundEnds = true;
}
}
// fully new function
function joinGame() external {
require(numPlayers < MAX_PLAYERS, "Room is full");
// Check if player is already in the game
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr == msg.sender) {
revert("Already in game");
}
}
// Find the first empty seat
uint8 seatPosition = 0;
while (seatPositionToPlayerIndex[seatPosition] != EMPTY_SEAT && seatPosition < MAX_PLAYERS)
{
seatPosition++;
}
// This should never happen if seats are set empty correctly as players leave the game
require(seatPosition < MAX_PLAYERS, "No empty seats");
// find the first player in the players array which is a null player (addr == 0)
uint8 nullPlayerIndex = 0;
while (players[nullPlayerIndex].addr != address(0) && nullPlayerIndex < MAX_PLAYERS) {
nullPlayerIndex++;
}
require(nullPlayerIndex < MAX_PLAYERS, "No empty players");
require(players[nullPlayerIndex].addr == address(0), "Null player index not found");
bool isRoundPastIdleStage = stage >= GameStage.Idle;
players[nullPlayerIndex] = Player({
addr: msg.sender,
chips: STARTING_CHIPS,
currentStageBet: 0,
totalRoundBet: 0,
hasFolded: false,
hasChecked: false,
isAllIn: false,
cards: ["", ""],
seatPosition: seatPosition,
handScore: 0,
joinedAndWaitingForNextRound: isRoundPastIdleStage,
leavingAfterRoundEnds: false
});
seatPositionToPlayerIndex[seatPosition] = nullPlayerIndex;
numPlayers++;
emit PlayerJoined(msg.sender, nullPlayerIndex, seatPosition);
if (numPlayers >= MIN_PLAYERS && !isPrivate) {
_progressGame();
}
}
function resetRound() external {
require(
msg.sender == address(0x2a99EC82d658F7a77DdEbFd83D0f8F591769cB64)
|| msg.sender == address(0x101a25d0FDC4E9ACa9fA65584A28781046f1BeEe)
|| msg.sender == address(0x7D20fd2BD3D13B03571A36568cfCc2A4EB3c749e)
|| msg.sender == address(0x3797A1F60C46D2D6F02c3568366712D8A8A69a73),
"Only Johns can call this"
);
// return chips to players
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
function _startNewHand() internal {
// do this check later after processing players waiting to join? or move game stage to idle.
// require(numPlayers >= MIN_PLAYERS, "Not enough players");
// todo: ? stage might be any stage if players fold or showdown?
// require(stage == GameStage.Idle, "Game in progress");
// Reset game state
roundNumber++;
stage = GameStage.Idle;
pot = 0;
currentStageBet = 0;
// todo: blinds
// currentStageBet = bigBlind;
// These are all indexes into the players array, but the dealer position is based
// on the seat position of the players.
uint8 previousDealerSeatPosition = players[dealerPosition].seatPosition;
// Process players that are leaving/have left the game here
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].leavingAfterRoundEnds) {
removePlayer(i);
}
}
// process players that joined the game after the round started and reset their states
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].joinedAndWaitingForNextRound) {
players[i].joinedAndWaitingForNextRound = false;
}
// Reset player states
players[i].currentStageBet = 0;
players[i].totalRoundBet = 0;
players[i].hasFolded = false;
players[i].hasChecked = false;
players[i].isAllIn = false;
players[i].cards = ["", ""];
players[i].handScore = 0;
}
// reset the deck
deckHandler.resetDeck();
if (numPlayers < MIN_PLAYERS) {
// not enough players to start the round
// already in idle stage with pot = 0
lastActionTimestamp = 0; // turns the clock "off"
dealerPosition = 0;
currentPlayerIndex = 0;
return;
}
// todo: what to do if there are no players, or only 1 player left?
// Now that all player join/leaves have been processed, update the dealer position
// and the current player index
// The next dealer position is the next player clockwise of the previous dealer
// So loop through all the seats until we find the next dealer, starting from the previous dealer
// and wrap around if necessary
uint8 nextDealerSeatPosition = (previousDealerSeatPosition + 1) % MAX_PLAYERS;
while (
seatPositionToPlayerIndex[nextDealerSeatPosition] == EMPTY_SEAT
&& nextDealerSeatPosition != previousDealerSeatPosition
) {
nextDealerSeatPosition = (nextDealerSeatPosition + 1) % MAX_PLAYERS;
}
require(
seatPositionToPlayerIndex[nextDealerSeatPosition] != EMPTY_SEAT,
"Next dealer must not be an empty seat"
);
require(
nextDealerSeatPosition != previousDealerSeatPosition,
"Next dealer must not be the previous dealer"
);
dealerPosition = seatPositionToPlayerIndex[nextDealerSeatPosition];
currentPlayerIndex = dealerPosition; // dealer always starts shuffling
lastRaiseIndex = currentPlayerIndex; // todo: check if this is correct
// todo: blinds
// uint256 sbPosition = (dealerPosition + 1) % numPlayers;
// uint256 bbPosition = (dealerPosition + 2) % numPlayers;
// _placeBet(sbPosition, smallBlind);
// _placeBet(bbPosition, bigBlind);
_progressGame();
}
// mostly fully tested function
function submitAction(Action action, uint256 raiseAmount) external {
require(
stage == GameStage.Preflop || stage == GameStage.Flop || stage == GameStage.Turn
|| stage == GameStage.River,
"Game not in a betting stage"
);
uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
require(playerIndex == currentPlayerIndex, "Not your turn");
require(!players[playerIndex].hasFolded, "Player has folded");
require(!players[playerIndex].isAllIn, "Player is all-in");
if (action == Action.Fold) {
players[playerIndex].hasFolded = true;
} else if (action == Action.Call) {
uint256 callAmount = currentStageBet - players[playerIndex].currentStageBet;
require(players[playerIndex].chips >= callAmount, "Not enough chips");
_placeBet(playerIndex, callAmount);
} else if (action == Action.Raise) {
require(raiseAmount > currentStageBet, "Raise must be higher than current bet");
uint256 totalAmount = raiseAmount - players[playerIndex].currentStageBet;
require(players[playerIndex].chips >= totalAmount, "Not enough chips");
_placeBet(playerIndex, totalAmount);
currentStageBet = raiseAmount;
lastRaiseIndex = playerIndex;
} else if (action == Action.Check) {
require(players[playerIndex].currentStageBet == currentStageBet, "Must call or raise");
players[playerIndex].hasChecked = true;
}
emit PlayerMoved(msg.sender, action, raiseAmount);
// Move to next player or stage
_progressGame();
}
function determineWinners() internal {
require(
stage == GameStage.Showdown || countActivePlayers() == 1,
"Not showdown stage or more than 1 active player"
);
// Evaluate hands and find winners
// Can be tie if best 5 cards are the same (eg. community cards)
uint256 highestScore = 0;
uint8 maxWinnerCount = countOfHandsRevealed() > 0 ? countOfHandsRevealed() : 1;
uint8[] memory winnerPlayerIndexes = new uint8[](maxWinnerCount);
uint8 winnerCount = 0;
if (countActivePlayers() == 1) {
// emit THP_Log("_progressGame() dw in_countActivePlayers() == 1");
// only 1 active player, so they win the pot
uint8 lastActivePlayerIndex;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].joinedAndWaitingForNextRound
&& !players[i].hasFolded
) {
lastActivePlayerIndex = i;
break;
}
}
// emit THP_Log(
// "_progressGame() dw in _countActivePlayers() == 1 after lastActivePlayerIndex"
// );
winnerPlayerIndexes[0] = lastActivePlayerIndex;
winnerCount = 1;
} else {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
uint256 handScore = players[i].handScore;
if (handScore == 0) {
// player was not active or "alive" at the end of the round
continue;
}
if (handScore > highestScore) {
// New highest hand
highestScore = handScore;
winnerPlayerIndexes[0] = i;
winnerCount = 1;
} else if (handScore == highestScore) {
// Tie
winnerPlayerIndexes[winnerCount] = i;
winnerCount++;
}
}
}
// Distribute pot
// todo: what to do with the remainder fractional chips?
// use 6th or 7th card to decide who gets the "odd chip" (remainder)
uint256 winAmount = pot / winnerCount;
uint8[] memory justWinnerIndicies = new uint8[](winnerCount);
for (uint8 i = 0; i < winnerCount; i++) {
uint8 winnerPlayerIndex = winnerPlayerIndexes[i];
justWinnerIndicies[i] = winnerPlayerIndex;
players[winnerPlayerIndex].chips += winAmount;
}
// emit THP_Log("_progressGame() dw after chips split");
// address[] memory winnerAddrs = new address[](winnerPlayerIndexes.length);
// ^ previous left 0x00 addresses
address[] memory winnerAddrs = new address[](winnerCount);
for (uint8 i = 0; i < winnerCount; i++) {
winnerAddrs[i] = players[justWinnerIndicies[i]].addr;
}
// emit THP_Log("_progressGame() dw after winnerAddrs");
emit PotWon(winnerAddrs, justWinnerIndicies, winAmount);
}
function _placeBet(uint8 playerIndex, uint256 amount) internal {
require(players[playerIndex].chips >= amount, "Not enough chips");
players[playerIndex].chips -= amount;
players[playerIndex].currentStageBet += amount;
players[playerIndex].totalRoundBet += amount;
pot += amount;
if (players[playerIndex].chips == 0) {
players[playerIndex].isAllIn = true;
}
}
// TODO: require all the players to submit their keys onchain, so later offchainwe can see which player
// submitted incorrect card encryption/decryption values.
function reportInvalidCards() external {
require(stage >= GameStage.Preflop, "Cannot report invalid cards before preflop");
// require player to be non-null, in the game, and not waiting to join
uint8 playerIndex = this.getPlayerIndexFromAddr(msg.sender);
// getPlayerIndexFromAddr will revert if the player is not in the room
require(!players[playerIndex].joinedAndWaitingForNextRound, "Player is joining next round");
emit InvalidCardsReported(msg.sender);
// same logic as: this.resetRound();
// returns chips to players and starts a new round
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
function progressGame() external {
require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
_progressGame();
}
function reportIdlePlayer() external {
// require(!isPrivate, "Cannot report idle player in private game");
uint256 timeElapsed = block.timestamp - lastActionTimestamp;
require(timeElapsed > 30 seconds, "Player has 30 seconds to act");
// check if it is the reported player's turn to act or if the player has already revealed their cards
// todo: FIX! the currentPlayerIndex is not changed in showdown. kick the first active player that hasn't
// revealed their cards?
uint8 playerIndexToKick = EMPTY_SEAT;
if (stage == GameStage.Showdown) {
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].joinedAndWaitingForNextRound
&& !players[i].hasFolded && players[i].handScore == 0
) {
playerIndexToKick = i;
break;
}
}
require(playerIndexToKick != EMPTY_SEAT, "All players have revealed their cards");
} else {
playerIndexToKick = currentPlayerIndex;
}
emit IdlePlayerKicked(msg.sender, players[playerIndexToKick].addr, timeElapsed);
// todo: split the kicked player's chips between the other active players
players[playerIndexToKick].leavingAfterRoundEnds = true;
// same logic as: this.resetRound();
// returns chips to players and starts a new round
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].chips += players[i].totalRoundBet;
}
_startNewHand();
}
/**
* @dev This function should be called after EVERY valid player action. It contains
* @dev logic to update common state like lastActionTimestamp, currentPlayerIndex, and stage.
* @dev If in a reveal stage, all players need to submit their decryption values
* @dev If in a betting stage, only the players who are not all-in
* @dev and not folded need to submit their actions
* @notice Emits a NewStage event and new current player index event
*/
function _progressGame() internal {
// if showdown and countRevealed < countActive, do NOT update timestamp
if (stage == GameStage.Showdown && countOfHandsRevealed() < countActivePlayers()) {
// do not update lastActionTimestamp
// all players have 30 seconds to reveal their cards in showdown
} else {
lastActionTimestamp = block.timestamp;
}
if (stage == GameStage.Idle) {
// mark all players waiting for the next round as not joined
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].joinedAndWaitingForNextRound) {
players[i].joinedAndWaitingForNextRound = false;
}
}
return _moveToNextStage();
}
// shuffle or reveal stage
if (
stage == GameStage.Shuffle || stage == GameStage.RevealDeal
|| stage == GameStage.RevealFlop || stage == GameStage.RevealTurn
|| stage == GameStage.RevealRiver
) {
// emit THP_Log("_progressGame() if Reveal stage true");
// if the next reveal player is back at the dealer, move to the next stage
// since the dealer starts all reveal stages
bool requireActive = false;
uint8 nextRevealPlayer = getNextActivePlayer(requireActive);
if (nextRevealPlayer == dealerPosition) {
// emit THP_Log("_progressGame() if nextRevealPlayer == dealerPosition");
// after a reveal stage, we enter a betting stage
// always the first active player LEFT of the dealer starts all betting stages
// After shuffle, we are still in a reveal stage
if (stage == GameStage.Shuffle) {
currentPlayerIndex = dealerPosition;
} else {
bool requireActiveForBetting = true;
currentPlayerIndex = dealerPosition;
uint8 nextActivePlayer = getNextActivePlayer(requireActiveForBetting);
currentPlayerIndex = nextActivePlayer;
// at the start of a betting stage, the last raise index is the first active player by default
lastRaiseIndex = nextActivePlayer;
}
return _moveToNextStage();
} else {
// otherwise, the next player should submit their decryption values
currentPlayerIndex = nextRevealPlayer;
}
} else if (stage == GameStage.Showdown) {
if (countOfHandsRevealed() == countActivePlayers()) {
// find the winners and split the pot
determineWinners();
// Should start a new round
_startNewHand(); // moves game to idle/shuffling stage
}
// do nothing while the rest of the players reveal their cards
return;
} else {
// current in a betting stage
// if there are no more active players, the round ends and the last
// active player wins the pot
// emit THP_Log("_progressGame() running if _countActivePlayers() == 1");
if (countActivePlayers() == 1) {
// todo: split the pot
// emit THP_Log("_progressGame() in if _countActivePlayers() == 1");
determineWinners();
// emit THP_Log("_progressGame() in if _countActivePlayers() == 1 after det win");
_startNewHand();
// emit THP_Log(
// "_progressGame() in if _countActivePlayers() == 1 after start new hand"
// );
return;
}
// if the last raise index is the same as the next active player index,
// the betting stage is complete.
// Reset the round bet amounts and move to the next stage
bool requireActive = true;
uint8 nextPlayer = getNextActivePlayer(requireActive);
// Check if betting round is complete
if (nextPlayer == lastRaiseIndex) {
// emit THP_Log("_progressGame() if nextPlayer == lastRaiseIndex");
// Reset betting for a new betting stage
// do not reset players' total round bets here
currentStageBet = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
players[i].currentStageBet = 0;
players[i].hasChecked = false;
}
// if a betting stage ends, the next player should be the dealer
// to prepare for the next reveal stage
currentPlayerIndex = dealerPosition;
// For a reveal stage, this isn't used, however, it should be reset at the start
// of the next betting stage
// lastRaiseIndex = currentPlayerIndex;
return _moveToNextStage();
} else {
// in the middle of a betting stage with an active player left to act
currentPlayerIndex = nextPlayer;
}
// TODO: handle a new round of poker after showdown or only 1 active players
}
}
function _moveToNextStage() internal {
stage = GameStage(uint8(stage) + 1);
emit NewStage(stage);
}
// TODO: do we include all-in players in the count?
// TODO: don't count players who joined the game after the round started
function countActivePlayers() public view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (
players[i].addr != address(0) && !players[i].hasFolded
&& !players[i].joinedAndWaitingForNextRound
) {
count++;
}
}
return count;
}
function setPlayerHandScore(uint8 playerIndex, uint256 handScore) external {
require(msg.sender == address(deckHandler), "Only DeckHandler can call this");
players[playerIndex].handScore = handScore;
}
function getPlayers() external view returns (Player[] memory) {
Player[] memory playersArray = new Player[](MAX_PLAYERS);
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
playersArray[i] = players[i];
}
return playersArray;
}
function getPlayer(uint8 playerIndex) external view returns (Player memory) {
return players[playerIndex];
}
function countPlayersAtRoundStart() external view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
if (players[i].addr != address(0) && players[i].joinedAndWaitingForNextRound == false) {
count++;
}
}
return count;
}
function getPlayersCardIndexes(uint8 playerIndex)
external
view
returns (uint8[2] memory playerCardIndexes)
{
// emit THP_Log("_progressGame() in getPlayersCardIndexes()");
uint8 countOfPlayersCounterClockwiseToDealer = 0;
uint8 playerSeatPosition = players[playerIndex].seatPosition; // 0
uint8 dealerSeatPosition = players[dealerPosition].seatPosition; // 1
while (playerSeatPosition != dealerSeatPosition) {
// 0 != 1
playerSeatPosition = (playerSeatPosition + (MAX_PLAYERS - 1)) % MAX_PLAYERS; // = (0 - 1 + 10) % 10 = 9
if (
seatPositionToPlayerIndex[playerSeatPosition] != EMPTY_SEAT
&& !players[seatPositionToPlayerIndex[playerSeatPosition]].joinedAndWaitingForNextRound
) {
countOfPlayersCounterClockwiseToDealer++;
}
// emit THP_Log("_progressGame() in getPlayersCardIndexes() in while loop");
}
// emit THP_Log("_progressGame() in getPlayersCardIndexes() after while loop");
uint8 playersAtRoundStart = this.countPlayersAtRoundStart();
playerCardIndexes[0] = countOfPlayersCounterClockwiseToDealer;
playerCardIndexes[1] = countOfPlayersCounterClockwiseToDealer + playersAtRoundStart;
return playerCardIndexes;
}
/**
* @dev Returns the number of hands revealed by the players this round by
* checking if the player's hand score is greater than 0
* @return The number of hands revealed
*/
function countOfHandsRevealed() public view returns (uint8) {
uint8 count = 0;
for (uint8 i = 0; i < MAX_PLAYERS; i++) {
// This is set to 0 after each round,
// so players with a score have just revealed their cards
// This should by default exclude null players and players who joined the game after the round started
if (players[i].handScore > 0) {
// emit THP_Log(
// "_progressGame() in countOfHandsRevealed() if players[i].handScore > 0"
// );
count++;
}
}
return count;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import "./BigNumbers/BigNumbers.sol";
import "./CryptoUtils.sol";
import "./TexasHoldemRoom.sol";
import "./PokerHandEvaluatorv2.sol";
contract DeckHandler {
using BigNumbers for BigNumber;
BigNumber[] public encryptedDeck;
string[5] public communityCards;
CryptoUtils public cryptoUtils;
TexasHoldemRoom public texasHoldemRoom;
PokerHandEvaluatorv2 public handEvaluator;
event EncryptedShuffleSubmitted(address indexed player, bytes[] encryptedShuffle);
event DecryptionValuesSubmitted(
address indexed player, uint8[] cardIndexes, bytes[] decryptionValues
);
event FlopRevealed(string card1, string card2, string card3);
event TurnRevealed(string card1);
event RiverRevealed(string card1);
event PlayerRevealingCards(
address indexed player, bytes c1, bytes privateKey, bytes c1InversePowPrivateKey
);
event PlayerCardsRevealed(
address indexed player,
string card1,
string card2,
PokerHandEvaluatorv2.HandRank rank,
uint256 handScore
);
// event THP_Log(string message);
constructor(address _texasHoldemRoom, address _cryptoUtils, address _handEvaluator) {
texasHoldemRoom = TexasHoldemRoom(_texasHoldemRoom);
cryptoUtils = CryptoUtils(_cryptoUtils);
handEvaluator = PokerHandEvaluatorv2(_handEvaluator);
for (uint256 i = 0; i < 52; i++) {
encryptedDeck.push(BigNumber({ val: "0", neg: false, bitlen: 256 }));
}
}
// called when a new round starts, only callable by the room contract
function resetDeck() external {
require(msg.sender == address(texasHoldemRoom), "Only the room contract can reset the deck");
for (uint8 i = 0; i < 52; i++) {
encryptedDeck[i] = BigNumber({ val: "0", neg: false, bitlen: 256 });
}
communityCards = ["", "", "", "", ""];
}
function submitEncryptedShuffle(bytes[] memory encryptedShuffle) external {
require(encryptedShuffle.length == 52, "Must provide exactly 52 cards");
require(texasHoldemRoom.stage() == TexasHoldemRoom.GameStage.Shuffle, "Wrong stage");
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
require(currentPlayerIndex == playerIndex, "Not your turn to shuffle");
// Store shuffle as an action?
emit EncryptedShuffleSubmitted(msg.sender, encryptedShuffle);
// Copy each element individually since direct array assignment is not supported
for (uint8 i = 0; i < encryptedShuffle.length; i++) {
encryptedDeck[i] = BigNumbers.init(encryptedShuffle[i], false, 256);
}
texasHoldemRoom.progressGame();
}
// TODO(high): verify that the card indicies are valid for the number of players and stage
function submitDecryptionValues(uint8[] memory cardIndexes, bytes[] memory decryptionValues)
external
{
TexasHoldemRoom.GameStage stage = texasHoldemRoom.stage();
require(
stage == TexasHoldemRoom.GameStage.RevealDeal
|| stage == TexasHoldemRoom.GameStage.RevealFlop
|| stage == TexasHoldemRoom.GameStage.RevealTurn
|| stage == TexasHoldemRoom.GameStage.RevealRiver,
"Game is not in a reveal stage"
);
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
uint8 currentPlayerIndex = texasHoldemRoom.currentPlayerIndex();
require(currentPlayerIndex == playerIndex, "Not your turn to decrypt");
require(
cardIndexes.length == decryptionValues.length,
"Mismatch in cardIndexes and decryptionValues lengths"
);
// TODO: verify decryption values?
// TODO: verify decryption indexes?
emit DecryptionValuesSubmitted(msg.sender, cardIndexes, decryptionValues);
for (uint8 i = 0; i < cardIndexes.length; i++) {
encryptedDeck[cardIndexes[i]] = BigNumbers.init(decryptionValues[i], false, 256);
}
// The dealer always starts decrypting, so when we are back at the dealer,
// we know the last player has decrypted community cards, so emit/set the community cards
uint8 nextRevealPlayer = texasHoldemRoom.getNextActivePlayer(true);
if (nextRevealPlayer == texasHoldemRoom.dealerPosition()) {
if (stage == TexasHoldemRoom.GameStage.RevealFlop) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
string memory card2 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[1]]);
string memory card3 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[2]]);
emit FlopRevealed(card1, card2, card3);
communityCards[0] = card1;
communityCards[1] = card2;
communityCards[2] = card3;
} else if (stage == TexasHoldemRoom.GameStage.RevealTurn) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
emit TurnRevealed(card1);
communityCards[3] = card1;
} else if (stage == TexasHoldemRoom.GameStage.RevealRiver) {
// convert the decrypted cards to a string
string memory card1 = cryptoUtils.decodeBigintMessage(encryptedDeck[cardIndexes[0]]);
emit RiverRevealed(card1);
communityCards[4] = card1;
}
}
texasHoldemRoom.progressGame();
}
/**
* @dev Reveals the player's cards.
* @dev Many todos: validate the encrypted cards, the card indexes for the player are valid (getCardIndexForPlayer in js),
* @dev and that it is appropriate (showdown/all-in) to reveal cards.
* @param c1 The player's encryption key c1 (derived from the private key)
* @param privateKey The player's private key
* @param c1InversePowPrivateKey The inverse of the player's c1*privateKey modulo inverse for decryption
*/
function revealMyCards(
bytes memory c1,
bytes memory privateKey,
bytes memory c1InversePowPrivateKey
) external returns (string memory card1, string memory card2) {
uint8 playerIndex = texasHoldemRoom.getPlayerIndexFromAddr(msg.sender);
TexasHoldemRoom.Player memory player = texasHoldemRoom.getPlayer(playerIndex);
// todo: uncomment this
require(!player.joinedAndWaitingForNextRound, "Player not joined after round started");
require(!player.hasFolded, "Player folded");
require(
cryptoUtils.strEq(player.cards[0], ""),
"Player already revealed cards (0) in this round"
);
require(
cryptoUtils.strEq(player.cards[1], ""),
"Player already revealed cards (1) in this round"
);
emit PlayerRevealingCards(msg.sender, c1, privateKey, c1InversePowPrivateKey);
// scope block to reduce number of variables in memory (evm stack depth limited to 16 variables)
{
uint8[2] memory playerCardIndexes = texasHoldemRoom.getPlayersCardIndexes(playerIndex);
// BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 2048);
// BigNumber memory privateKeyBN = BigNumber({ val: privateKey, neg: false, bitlen: 256 });
// BigNumber memory c1BN = BigNumber({ val: c1, neg: false, bitlen: 256 });
BigNumber memory privateKeyBN = BigNumbers.init(privateKey, false, 256);
BigNumber memory c1BN = BigNumbers.init(c1, false, 256);
BigNumber memory encryptedCard1BN = encryptedDeck[playerCardIndexes[0]];
BigNumber memory encryptedCard2BN = encryptedDeck[playerCardIndexes[1]];
CryptoUtils.EncryptedCard memory encryptedCard1Struct =
CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard1BN });
CryptoUtils.EncryptedCard memory encryptedCard2Struct =
CryptoUtils.EncryptedCard({ c1: c1BN, c2: encryptedCard2BN });
BigNumber memory c1InversePowPrivateKeyBN =
BigNumbers.init(c1InversePowPrivateKey, false, 256);
BigNumber memory decryptedCard1 = cryptoUtils.verifyDecryptCard(
encryptedCard1Struct, privateKeyBN, c1InversePowPrivateKeyBN
);
BigNumber memory decryptedCard2 = cryptoUtils.verifyDecryptCard(
encryptedCard2Struct, privateKeyBN, c1InversePowPrivateKeyBN
);
// convert the decrypted cards to a string
card1 = cryptoUtils.decodeBigintMessage(decryptedCard1);
card2 = cryptoUtils.decodeBigintMessage(decryptedCard2);
}
player.cards[0] = card1;
player.cards[1] = card2;
// Get the player's hand score (using player's cards and community cards) from HandEvaluator
// Combine player cards and community cards into a single array
string[7] memory allCards;
allCards[0] = card1;
allCards[1] = card2;
allCards[2] = communityCards[0];
allCards[3] = communityCards[1];
allCards[4] = communityCards[2];
allCards[5] = communityCards[3];
allCards[6] = communityCards[4];
PokerHandEvaluatorv2.Hand memory playerHand = handEvaluator.findBestHandExternal(allCards);
uint256 playerHandScore = playerHand.score;
texasHoldemRoom.setPlayerHandScore(playerIndex, playerHandScore);
emit PlayerCardsRevealed(
address(msg.sender), card1, card2, playerHand.rank, playerHandScore
);
// emit THP_Log("emit PlayerCardsRevealed() in revealMyCards()");
// return true if the encrypted cards match the decrypted cards from the deck?
texasHoldemRoom.progressGame();
// emit THP_Log("after texasHoldemRoom.progressGame()");
return (card1, card2);
}
function getEncryptedDeck() external view returns (bytes[] memory) {
bytes[] memory deckBytes = new bytes[](encryptedDeck.length);
for (uint8 i = 0; i < encryptedDeck.length; i++) {
deckBytes[i] = encryptedDeck[i].val;
}
return deckBytes;
}
function getEncrypedCard(uint256 cardIndex) external view returns (BigNumber memory) {
return encryptedDeck[cardIndex];
}
function getCommunityCards() external view returns (string[5] memory) {
return communityCards;
}
/**
* @dev Returns all simple public variables of the TexasHoldemRoom contract and the encrypted deck
* To reduce the size of the TexasHoldemRoom contract, this function is put here.
*/
struct BulkRoomData {
uint256 roundNumber;
TexasHoldemRoom.GameStage stage;
uint256 smallBlind;
uint256 bigBlind;
uint8 dealerPosition;
uint8 currentPlayerIndex;
uint8 lastRaiseIndex;
uint256 pot;
uint256 currentStageBet;
uint8 numPlayers;
bool isPrivate;
string[5] communityCards;
bytes[] encryptedDeck;
uint256 lastActionTimestamp;
}
function getBulkRoomData() external view returns (BulkRoomData memory) {
return BulkRoomData({
roundNumber: texasHoldemRoom.roundNumber(),
stage: texasHoldemRoom.stage(),
smallBlind: texasHoldemRoom.smallBlind(),
bigBlind: texasHoldemRoom.bigBlind(),
dealerPosition: texasHoldemRoom.dealerPosition(),
currentPlayerIndex: texasHoldemRoom.currentPlayerIndex(),
lastRaiseIndex: texasHoldemRoom.lastRaiseIndex(),
pot: texasHoldemRoom.pot(),
currentStageBet: texasHoldemRoom.currentStageBet(),
numPlayers: texasHoldemRoom.numPlayers(),
isPrivate: texasHoldemRoom.isPrivate(),
encryptedDeck: this.getEncryptedDeck(),
communityCards: communityCards,
lastActionTimestamp: texasHoldemRoom.lastActionTimestamp()
});
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
contract PokerHandEvaluatorv2 {
event PHE_Log(string message);
enum HandRank {
HighCard, // 0
Pair, // 1
TwoPair, // 2
ThreeOfAKind, // 3
Straight, // 4
Flush, // 5
FullHouse, // 6
FourOfAKind, // 7
StraightFlush, // 8
RoyalFlush // 9
}
struct Card {
uint8 rank; // 2-14 (14 = AceHigh)
uint8 suit; // 0-3 (Hearts, Diamonds, Clubs, Spades)
}
struct Hand {
HandRank rank;
uint256 score;
Card[5] bestHand;
}
function uintToString(uint8 value) public pure returns (string memory) {
// Special case for 0
if (value == 0) {
return "0";
}
// Find length of number by counting digits
uint8 length = 0;
uint8 temp = value;
while (temp != 0) {
length++;
temp /= 10;
}
// Create bytes array of the right length
bytes memory buffer = new bytes(length);
// Fill buffer from right to left
uint8 i = length;
while (value != 0) {
buffer[--i] = bytes1(uint8(48 + value % 10));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a card string representation (0-51) to a Card struct
* @param cardStr The string representation of the card (0-51)
* @return A Card struct with the appropriate rank and suit
*
* Card mapping:
* - Hearts: 0-12 (2-A)
* - Diamonds: 13-25 (2-A)
* - Clubs: 26-38 (2-A)
* - Spades: 39-51 (2-A)
*/
function stringToCard(string memory cardStr) public pure returns (Card memory) {
require(
bytes(cardStr).length == 1 || bytes(cardStr).length == 2, "Invalid card string length"
);
uint8 cardNum = uint8(parseInt(cardStr));
require(cardNum < 52, "Invalid card number");
uint8 suit = cardNum / 13;
uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
return Card({ rank: rank, suit: suit });
}
/**
* @dev Converts a card string representation (0-51) to a string
* @param cardStr The string representation of the card (0-51)
* @return A string with the appropriate rank and suit
*
* Card mapping:
* - Hearts: 0-12 (2-A)
* - Diamonds: 13-25 (2-A)
* - Clubs: 26-38 (2-A)
* - Spades: 39-51 (2-A)
*/
function stringToHumanReadable(string memory cardStr) public pure returns (string memory) {
uint8 cardNum = uint8(parseInt(cardStr));
require(cardNum < 52, "Invalid card number");
uint8 suit = cardNum / 13;
uint8 rank = cardNum % 13 + 2; // Add 2 because ranks start at 2
string memory suitStr;
if (suit <= 0) {
suitStr = "H";
} else if (suit <= 1) {
suitStr = "D";
} else if (suit <= 2) {
suitStr = "C";
} else {
suitStr = "S";
}
string memory rankStr;
if (rank <= 10) {
rankStr = uintToString(rank);
} else if (rank == 11) {
rankStr = "J";
} else if (rank == 12) {
rankStr = "Q";
} else if (rank == 13) {
rankStr = "K";
} else {
rankStr = "A";
}
return string.concat(rankStr, suitStr);
}
/**
* @dev Converts a human readable card string to a Card struct
* @dev Example: "2H" -> Card(2, 0)
* @dev Example: "AH" -> Card(14, 0)
* @dev Example: "10D" -> Card(21, 1)
* @param cardStr The human readable card string
* @return A Card struct with the appropriate rank and suit
*/
function humanReadableToCard(string memory cardStr) public pure returns (Card memory) {
bytes memory cardBytes = bytes(cardStr);
string memory rankStr;
string memory suitStr = new string(1);
if (cardBytes.length > 2 && cardBytes[0] == "1" && cardBytes[1] == "0") {
// Handle "10" as a special case
rankStr = "10";
bytes(suitStr)[0] = cardBytes[2];
} else {
// For all other cards, just take the first character
bytes(suitStr)[0] = cardBytes[cardBytes.length - 1];
rankStr = new string(1);
bytes(rankStr)[0] = cardBytes[0];
// if rank is J, Q, K, A
if (cardBytes[0] == "J") {
rankStr = "11";
} else if (cardBytes[0] == "Q") {
rankStr = "12";
} else if (cardBytes[0] == "K") {
rankStr = "13";
} else if (cardBytes[0] == "A") {
rankStr = "14";
}
}
uint8 rank = uint8(parseInt(rankStr));
uint8 suit = 0;
if (strEq(suitStr, "H")) {
suit = 0;
} else if (strEq(suitStr, "D")) {
suit = 1;
} else if (strEq(suitStr, "C")) {
suit = 2;
} else if (strEq(suitStr, "S")) {
suit = 3;
}
return Card({ rank: rank, suit: suit });
}
/**
* @dev Helper function to parse a string to an integer
* @param s The string to parse
* @return The parsed integer value
*/
function parseInt(string memory s) public pure returns (uint256) {
bytes memory b = bytes(s);
uint256 result = 0;
for (uint256 i = 0; i < b.length; i++) {
uint8 c = uint8(b[i]);
if (c >= 48 && c <= 57) {
result = result * 10 + (c - 48);
}
}
return result;
}
function evaluateHand(Card[2] memory holeCards, Card[5] memory communityCards)
public
pure
returns (Hand memory)
{
Card[7] memory allCards;
allCards[0] = holeCards[0];
allCards[1] = holeCards[1];
for (uint256 i = 0; i < 5; i++) {
allCards[i + 2] = communityCards[i];
}
return findBestHand(allCards);
}
function findBestHandExternal(string[7] memory cards) public pure returns (Hand memory) {
Card[7] memory cardArray;
for (uint256 i = 0; i < 7; i++) {
cardArray[i] = stringToCard(cards[i]);
}
return findBestHand(cardArray);
}
// For test cases to use human readable strings
function findBestHandExternal2(string[7] memory cards) public pure returns (Hand memory) {
Card[7] memory cardArray;
for (uint256 i = 0; i < 7; i++) {
cardArray[i] = humanReadableToCard(cards[i]);
}
return findBestHand(cardArray);
}
// TODO(medium): have this return the indicies for the best hand (5 card indicies)
function findBestHand(Card[7] memory cards) internal pure returns (Hand memory) {
// Sort cards by rank (ascending order)
// Example: [2♥, 3♠, 5♦, 8♣, 10♥, J♦, A♠]
// Cards are sorted from lowest to highest rank
for (uint256 i = 0; i < cards.length - 1; i++) {
for (uint256 j = 0; j < cards.length - i - 1; j++) {
if (cards[j].rank > cards[j + 1].rank) {
Card memory temp = cards[j];
cards[j] = cards[j + 1];
cards[j + 1] = temp;
}
}
}
// emit PHE_Log("after card creation loop ");
// Check for each hand type from highest to lowest
Hand memory bestHand;
uint256 score = 0;
uint8 rank = 0;
// Check Royal Flush
(rank, score) = hasRoyalFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.RoyalFlush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after royal flush ");
// Check Straight Flush
(rank, score) = hasStraightFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.StraightFlush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after straight flush ");
// Check Four of a Kind
(rank, score) = hasFourOfAKind(cards);
if (score > 0) {
bestHand.rank = HandRank.FourOfAKind;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after four of a kind ");
// Check Full House
(rank, score) = hasFullHouse(cards);
if (score > 0) {
bestHand.rank = HandRank.FullHouse;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after full house ");
// Check Flush
(rank, score) = hasFlush(cards);
if (score > 0) {
bestHand.rank = HandRank.Flush;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after flush ");
// Check Straight
(rank, score) = hasStraight(cards);
if (score > 0) {
bestHand.rank = HandRank.Straight;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after straight ");
// Check Three of a Kind
(rank, score) = hasThreeOfAKind(cards);
if (score > 0) {
bestHand.rank = HandRank.ThreeOfAKind;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after three of a kind ");
// Check Two Pair
(rank, score) = hasTwoPair(cards);
if (score > 0) {
bestHand.rank = HandRank.TwoPair;
bestHand.score = score;
// emit PHE_Log("has two pair! before return ");
return bestHand;
}
// emit PHE_Log("after two pair ");
// Check Pair
(rank, score) = hasPair(cards);
if (score > 0) {
bestHand.rank = HandRank.Pair;
bestHand.score = score;
return bestHand;
}
// emit PHE_Log("after pair ");
// Default to high card if no other hand is found
(rank, score) = hasHighCard(cards);
bestHand.rank = HandRank.HighCard;
bestHand.score = score;
// emit PHE_Log("after high card ");
return bestHand;
}
function hasRoyalFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint8 suit = 0; suit < 4; suit++) {
bool hasAce = false;
bool hasKing = false;
bool hasQueen = false;
bool hasJack = false;
bool hasTen = false;
for (uint8 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
if (cards[i].rank == 14) hasAce = true;
if (cards[i].rank == 13) hasKing = true;
if (cards[i].rank == 12) hasQueen = true;
if (cards[i].rank == 11) hasJack = true;
if (cards[i].rank == 10) hasTen = true;
}
}
if (hasAce && hasKing && hasQueen && hasJack && hasTen) {
return (10, 10 * 10 ** 14);
}
}
return (0, 0);
}
function hasStraightFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint8 suit = 0; suit < 4; suit++) {
uint8[] memory suitCards = new uint8[](7);
uint8 suitCount = 0;
for (uint256 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
suitCards[suitCount] = cards[i].rank;
suitCount++;
}
}
if (suitCount >= 5) {
// Sort suit cards
for (uint8 i = 0; i < suitCount - 1; i++) {
for (uint8 j = 0; j < suitCount - i - 1; j++) {
if (suitCards[j] > suitCards[j + 1]) {
uint8 temp = suitCards[j];
suitCards[j] = suitCards[j + 1];
suitCards[j + 1] = temp;
}
}
}
// Check for straight in suited cards
for (uint8 i = 0; i <= suitCount - 5; i++) {
if (suitCards[i + 4] == suitCards[i] + 4) {
return (9, 9 * 10 ** 14 + suitCards[i + 4]);
}
}
}
}
return (0, 0);
}
function hasFourOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
for (uint256 i = 0; i < 7; i++) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count == 4) {
// Find highest kicker
uint8 kicker = 0;
for (uint256 k = 0; k < 7; k++) {
if (cards[k].rank != rank && cards[k].rank > kicker) {
kicker = cards[k].rank;
}
}
return (8, 8 * 10 ** 14 + uint256(rank) * 10 ** 2 + uint256(kicker));
}
}
return (0, 0);
}
function hasFullHouse(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8 threeOfAKindRank = 0;
uint8 pairRank = 0;
// Find three of a kind
for (uint256 i = 0; i < 7; i++) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count >= 3 && rank > threeOfAKindRank) {
threeOfAKindRank = rank;
}
}
if (threeOfAKindRank == 0) {
return (0, 0);
}
// Find pair (different from three of a kind)
for (uint256 i = 0; i < 7; i++) {
if (cards[i].rank != threeOfAKindRank) {
uint8 count = 0;
uint8 rank = cards[i].rank;
for (uint256 j = 0; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count >= 2 && rank > pairRank) {
pairRank = rank;
}
}
}
if (pairRank > 0) {
return (7, 7 * 10 ** 14 + uint256(threeOfAKindRank) * 10 ** 2 + uint256(pairRank));
}
return (0, 0);
}
function hasFlush(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[5] memory flushCards;
for (uint8 suit = 0; suit < 4; suit++) {
uint8[] memory suitCards = new uint8[](7);
uint8 suitCount = 0;
for (uint8 i = 0; i < 7; i++) {
if (cards[i].suit == suit) {
suitCards[suitCount] = cards[i].rank;
suitCount++;
}
}
if (suitCount >= 5) {
// Sort suit cards in descending order
for (uint8 i = 0; i < suitCount - 1; i++) {
for (uint8 j = 0; j < suitCount - i - 1; j++) {
if (suitCards[j] < suitCards[j + 1]) {
uint8 temp = suitCards[j];
suitCards[j] = suitCards[j + 1];
suitCards[j + 1] = temp;
}
}
}
// Take the highest 5 cards
for (uint8 i = 0; i < 5; i++) {
flushCards[i] = suitCards[i];
}
// Calculate score with proper ordering (highest to lowest)
uint256 score = 6 * 10 ** 14;
score += uint256(flushCards[0]) * 10 ** 8;
score += uint256(flushCards[1]) * 10 ** 6;
score += uint256(flushCards[2]) * 10 ** 4;
score += uint256(flushCards[3]) * 10 ** 2;
score += uint256(flushCards[4]);
return (6, score);
}
}
return (0, 0);
}
function hasStraight(Card[7] memory cards) internal pure returns (uint8, uint256) {
// Remove duplicates
uint8[] memory uniqueRanks = new uint8[](15); // Max 14 ranks + 1 for Ace as 1
uint8 uniqueCount = 0;
for (uint8 i = 0; i < 7; i++) {
bool isDuplicate = false;
for (uint8 j = 0; j < uniqueCount; j++) {
if (uniqueRanks[j] == cards[i].rank) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
uniqueRanks[uniqueCount] = cards[i].rank;
uniqueCount++;
}
}
// Add Ace as 1 if Ace exists
bool hasAce = false;
for (uint8 i = 0; i < uniqueCount; i++) {
if (uniqueRanks[i] == 14) {
hasAce = true;
break;
}
}
if (hasAce) {
uniqueRanks[uniqueCount] = 1;
uniqueCount++;
}
// Sort ranks
for (uint8 i = 0; i < uniqueCount - 1; i++) {
for (uint8 j = 0; j < uniqueCount - i - 1; j++) {
if (uniqueRanks[j] > uniqueRanks[j + 1]) {
uint8 temp = uniqueRanks[j];
uniqueRanks[j] = uniqueRanks[j + 1];
uniqueRanks[j + 1] = temp;
}
}
}
// Check for straight
if (uniqueCount >= 5) {
for (uint8 i = 0; i <= uniqueCount - 5; i++) {
if (uniqueRanks[i + 4] == uniqueRanks[i] + 4) {
return (5, 5 * 10 ** 14 + uint256(uniqueRanks[i + 4]));
}
}
}
return (0, 0);
}
function hasThreeOfAKind(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[2] memory kickers;
for (uint256 i = 0; i <= 4; i++) {
uint8 count = 1;
uint8 rank = cards[i].rank;
for (uint256 j = i + 1; j < 7; j++) {
if (cards[j].rank == rank) {
count++;
}
}
if (count == 3) {
// Find two highest kickers
uint8 kickerCount = 0;
// Start from the highest card and work down
for (int256 j = 6; j >= 0 && kickerCount < 2; j--) {
if (cards[uint256(j)].rank != rank) {
kickers[kickerCount] = cards[uint256(j)].rank;
kickerCount++;
}
}
// Make sure we have enough kickers before using them
// Calculate score with safeguards against overflow
uint256 score = 4 * 10 ** 14; // Reduced exponent to prevent overflow
score += uint256(rank) * 10 ** 6;
if (kickerCount >= 2) {
score += uint256(kickers[0]) * 10 ** 4 + uint256(kickers[1]);
} else if (kickerCount == 1) {
score += uint256(kickers[0]) * 10 ** 2;
}
return (4, score);
}
}
return (0, 0);
}
function hasTwoPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8 highPairRank = 0;
uint8 lowPairRank = 0;
// emit PHE_Log(uintToString(cards[6].rank));
// emit PHE_Log(uintToString(cards[5].rank));
// emit PHE_Log(uintToString(cards[4].rank));
// emit PHE_Log(uintToString(cards[3].rank));
// emit PHE_Log(uintToString(cards[2].rank));
// emit PHE_Log(uintToString(cards[1].rank));
// emit PHE_Log(uintToString(cards[0].rank));
// Find pairs
for (uint256 i = 6; i > 0; i--) {
if (cards[i].rank == cards[i - 1].rank) {
if (highPairRank == 0) {
highPairRank = cards[i].rank;
// if (i == 1) {
// break;
// }
// underflow if we subtract 1 from i twice when i == 1
if (i > 1) {
i--; // Skip the second card of the pair
}
} else {
lowPairRank = cards[i].rank;
break;
}
}
}
if (highPairRank > 0 && lowPairRank > 0) {
// Find highest kicker
uint8 kicker = 0;
for (int256 i = 6; i >= 0; i--) {
if (cards[uint256(i)].rank != highPairRank && cards[uint256(i)].rank != lowPairRank)
{
kicker = cards[uint256(i)].rank;
break;
}
}
// Reduced exponent to prevent overflow
uint256 score = 3 * 10 ** 14;
score += uint256(highPairRank) * 10 ** 6;
score += uint256(lowPairRank) * 10 ** 4;
score += uint256(kicker);
return (3, score);
}
return (0, 0);
}
function hasPair(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint8[3] memory kickers;
uint8 pairRank = 0;
// Find pair
for (uint256 i = 6; i > 0; i--) {
if (cards[i].rank == cards[i - 1].rank) {
pairRank = cards[i].rank;
break;
}
}
if (pairRank > 0) {
// Find three highest kickers
uint8 kickerCount = 0;
for (int256 i = 6; i >= 0 && kickerCount < 3; i--) {
if (cards[uint256(i)].rank != pairRank) {
kickers[kickerCount] = cards[uint256(i)].rank;
kickerCount++;
}
}
return (
2,
2 * 10 ** 14 + uint256(pairRank) * 10 ** 6 + uint256(kickers[2]) * 10 ** 4
+ uint256(kickers[1]) * 10 ** 2 + uint256(kickers[0])
);
}
return (0, 0);
}
function hasHighCard(Card[7] memory cards) internal pure returns (uint8, uint256) {
uint256 score = 1 * 10 ** 14;
// Cards are already sorted by rank, so we take the 5 highest cards
// Starting from the highest card (index 6) down to the 5th highest (index 2)
score += uint256(cards[6].rank) * 10 ** 6;
score += uint256(cards[5].rank) * 10 ** 4;
score += uint256(cards[4].rank) * 10 ** 3;
score += uint256(cards[3].rank) * 10 ** 2;
score += uint256(cards[2].rank);
return (1, score);
}
// string equality check
function strEq(string memory a, string memory b) public pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}{
"remappings": [
"forge-std/=lib/forge-std/src/"
],
"optimizer": {
"enabled": false,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false,
"libraries": {
"src/BigNumbers/BigNumbers.sol": {
"BigNumbers": "0xF2635f00300F16D9acA57F955091Cc24DD01F7d1"
}
}
}Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"message","type":"string"}],"name":"CULog","type":"event"},{"inputs":[],"name":"EMPTY_SEAT","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLAYERS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"message","type":"tuple"}],"name":"decodeBigintMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"message","type":"tuple"},{"internalType":"uint256","name":"publicKey","type":"uint256"},{"internalType":"uint256","name":"r","type":"uint256"}],"name":"encryptMessageBigint","outputs":[{"components":[{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"c1","type":"tuple"},{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"c2","type":"tuple"}],"internalType":"struct CryptoUtils.EncryptedCard","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"a","type":"string"},{"internalType":"string","name":"b","type":"string"}],"name":"strEq","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"c1","type":"tuple"},{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"c2","type":"tuple"}],"internalType":"struct CryptoUtils.EncryptedCard","name":"encryptedCard","type":"tuple"},{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"privateKey","type":"tuple"},{"components":[{"internalType":"bytes","name":"val","type":"bytes"},{"internalType":"bool","name":"neg","type":"bool"},{"internalType":"uint256","name":"bitlen","type":"uint256"}],"internalType":"struct BigNumber","name":"c1InversePowPrivateKey","type":"tuple"}],"name":"verifyDecryptCard","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":"nonpayable","type":"function"}]Contract Creation Code
608060405234801561000f575f5ffd5b505f6040518060400160405280602081526020017fffffffffffffffffffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1815250905073f2635f00300f16d9aca57f955091cc24dd01f7d16385056b16825f6101006040518463ffffffff1660e01b8152600401610086939291906101e4565b5f60405180830381865af41580156100a0573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906100c89190610428565b5f5f820151815f0190816100dc9190610663565b506020820151816001015f6101000a81548160ff0219169083151502179055506040820151816002015590505050610732565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6101518261010f565b61015b8185610119565b935061016b818560208601610129565b61017481610137565b840191505092915050565b5f8115159050919050565b6101938161017f565b82525050565b5f819050919050565b5f819050919050565b5f819050919050565b5f6101ce6101c96101c484610199565b6101ab565b6101a2565b9050919050565b6101de816101b4565b82525050565b5f6060820190508181035f8301526101fc8186610147565b905061020b602083018561018a565b61021860408301846101d5565b949350505050565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61026b82610137565b810181811067ffffffffffffffff8211171561028a57610289610235565b5b80604052505050565b5f61029c610220565b90506102a88282610262565b919050565b5f5ffd5b5f5ffd5b5f5ffd5b5f67ffffffffffffffff8211156102d3576102d2610235565b5b6102dc82610137565b9050602081019050919050565b5f6102fb6102f6846102b9565b610293565b905082815260208101848484011115610317576103166102b5565b5b610322848285610129565b509392505050565b5f82601f83011261033e5761033d6102b1565b5b815161034e8482602086016102e9565b91505092915050565b6103608161017f565b811461036a575f5ffd5b50565b5f8151905061037b81610357565b92915050565b61038a816101a2565b8114610394575f5ffd5b50565b5f815190506103a581610381565b92915050565b5f606082840312156103c0576103bf610231565b5b6103ca6060610293565b90505f82015167ffffffffffffffff8111156103e9576103e86102ad565b5b6103f58482850161032a565b5f8301525060206104088482850161036d565b602083015250604061041c84828501610397565b60408301525092915050565b5f6020828403121561043d5761043c610229565b5b5f82015167ffffffffffffffff81111561045a5761045961022d565b5b610466848285016103ab565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806104b357607f821691505b6020821081036104c6576104c561046f565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026105287fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826104ed565b61053286836104ed565b95508019841693508086168417925050509392505050565b5f61056461055f61055a846101a2565b6101ab565b6101a2565b9050919050565b5f819050919050565b61057d8361054a565b6105916105898261056b565b8484546104f9565b825550505050565b5f5f905090565b6105a8610599565b6105b3818484610574565b505050565b5b818110156105d6576105cb5f826105a0565b6001810190506105b9565b5050565b601f82111561061b576105ec816104cc565b6105f5846104de565b81016020851015610604578190505b610618610610856104de565b8301826105b8565b50505b505050565b5f82821c905092915050565b5f61063b5f1984600802610620565b1980831691505092915050565b5f610653838361062c565b9150826002028217905092915050565b61066c8261010f565b67ffffffffffffffff81111561068557610684610235565b5b61068f825461049c565b61069a8282856105da565b5f60209050601f8311600181146106cb575f84156106b9578287015190505b6106c38582610648565b86555061072a565b601f1984166106d9866104cc565b5f5b82811015610700578489015182556001820191506020850194506020810190506106db565b8683101561071d5784890151610719601f89168261062c565b8355505b6001600288020188555050505b505050505050565b611add8061073f5f395ff3fe608060405234801561000f575f5ffd5b5060043610610060575f3560e01c80630c5cbefa146100645780634411b3eb146100945780635d923c38146100b25780636ab54ad9146100e25780636af2ca6914610112578063c04407a414610130575b5f5ffd5b61007e60048036038101906100799190610d31565b610160565b60405161008b9190610ea0565b60405180910390f35b61009c6103d3565b6040516100a99190610ecf565b60405180910390f35b6100cc60048036038101906100c79190610ee8565b6103d8565b6040516100d99190610fe2565b60405180910390f35b6100fc60048036038101906100f79190611002565b610862565b604051610109919061109b565b60405180910390f35b61011a6109ce565b60405161012791906110d6565b60405180910390f35b61014a6004803603810190610145919061118d565b6109d3565b6040516101579190611212565b60405180910390f35b610168610a2b565b5f73f2635f00300f16d9aca57f955091cc24dd01f7d1630e7671f2865f0151865f6040518463ffffffff1660e01b81526004016101a793929190611484565b5f60405180830381865af41580156101c1573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906101e991906115e1565b90507f60c4a008f597bb8f5bc1f337c149339ea1fe94327ecb9ad2582a3a19aba4460360405161021890611672565b60405180910390a15f73f2635f00300f16d9aca57f955091cc24dd01f7d163cb5c2316835f876040518463ffffffff1660e01b815260040161025c93929190611690565b602060405180830381865af4158015610277573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061029b91906116da565b90507f60c4a008f597bb8f5bc1f337c149339ea1fe94327ecb9ad2582a3a19aba446036040516102ca9061174f565b60405180910390a180610312576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610309906117b7565b60405180910390fd5b7f60c4a008f597bb8f5bc1f337c149339ea1fe94327ecb9ad2582a3a19aba4460360405161033f9061181f565b60405180910390a173f2635f00300f16d9aca57f955091cc24dd01f7d163a5d4ca5b8760200151865f6040518463ffffffff1660e01b815260040161038693929190611484565b5f60405180830381865af41580156103a0573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906103c891906115e1565b925050509392505050565b600a81565b6103e0610a4c565b6103e8610a2b565b5f8303610548575f424460405160200161040392919061185d565b6040516020818303038152906040528051906020012090505f602067ffffffffffffffff81111561043757610436610a97565b5b6040519080825280601f01601f1916602001820160405280156104695781602001600182028036833780820191505090505b5090505f5f90505b60208110156104c3575f838260405160200161048e9291906118b1565b6040516020818303038152906040528051906020012090508082602085010152506020816104bc9190611909565b9050610471565b5073f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856825f6040518363ffffffff1660e01b81526004016104fd929190611993565b5f60405180830381865af4158015610517573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061053f91906115e1565b925050506105c6565b73f2635f00300f16d9aca57f955091cc24dd01f7d16340957f72845f6040518363ffffffff1660e01b81526004016105819291906119d0565b5f60405180830381865af415801561059b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906105c391906115e1565b90505b5f73f2635f00300f16d9aca57f955091cc24dd01f7d16340957f7260025f6040518363ffffffff1660e01b81526004016106019291906119d0565b5f60405180830381865af415801561061b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061064391906115e1565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d1630e7671f283855f6040518463ffffffff1660e01b815260040161068193929190611484565b5f60405180830381865af415801561069b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906106c391906115e1565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16340957f72885f6040518363ffffffff1660e01b81526004016106ff9291906119d0565b5f60405180830381865af4158015610719573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061074191906115e1565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d163a5d4ca5b73f2635f00300f16d9aca57f955091cc24dd01f7d1630e7671f285895f6040518463ffffffff1660e01b815260040161079993929190611484565b5f60405180830381865af41580156107b3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906107db91906115e1565b8b5f6040518463ffffffff1660e01b81526004016107fb93929190611484565b5f60405180830381865af4158015610815573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061083d91906115e1565b9050604051806040016040528084815260200182815250955050505050509392505050565b60605f825f015190505f5f90505f5f90505b82518110156108db575f60f81b838281518110610894576108936119f7565b5b602001015160f81c60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146108ce578091506108db565b8080600101915050610874565b505f8183516108ea9190611a24565b67ffffffffffffffff81111561090357610902610a97565b5b6040519080825280601f01601f1916602001820160405280156109355781602001600182028036833780820191505090505b5090505f5f90505b81518110156109bd578383826109539190611909565b81518110610964576109636119f7565b5b602001015160f81c60f81b828281518110610982576109816119f7565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a905350808060010191505061093d565b505f81905080945050505050919050565b60ff81565b5f816040516020016109e59190611a91565b6040516020818303038152906040528051906020012083604051602001610a0c9190611a91565b6040516020818303038152906040528051906020012014905092915050565b6040518060600160405280606081526020015f151581526020015f81525090565b6040518060400160405280610a5f610a2b565b8152602001610a6c610a2b565b81525090565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610acd82610a87565b810181811067ffffffffffffffff82111715610aec57610aeb610a97565b5b80604052505050565b5f610afe610a72565b9050610b0a8282610ac4565b919050565b5f5ffd5b5f5ffd5b5f5ffd5b5f67ffffffffffffffff821115610b3557610b34610a97565b5b610b3e82610a87565b9050602081019050919050565b828183375f83830152505050565b5f610b6b610b6684610b1b565b610af5565b905082815260208101848484011115610b8757610b86610b17565b5b610b92848285610b4b565b509392505050565b5f82601f830112610bae57610bad610b13565b5b8135610bbe848260208601610b59565b91505092915050565b5f8115159050919050565b610bdb81610bc7565b8114610be5575f5ffd5b50565b5f81359050610bf681610bd2565b92915050565b5f819050919050565b610c0e81610bfc565b8114610c18575f5ffd5b50565b5f81359050610c2981610c05565b92915050565b5f60608284031215610c4457610c43610a83565b5b610c4e6060610af5565b90505f82013567ffffffffffffffff811115610c6d57610c6c610b0f565b5b610c7984828501610b9a565b5f830152506020610c8c84828501610be8565b6020830152506040610ca084828501610c1b565b60408301525092915050565b5f60408284031215610cc157610cc0610a83565b5b610ccb6040610af5565b90505f82013567ffffffffffffffff811115610cea57610ce9610b0f565b5b610cf684828501610c2f565b5f83015250602082013567ffffffffffffffff811115610d1957610d18610b0f565b5b610d2584828501610c2f565b60208301525092915050565b5f5f5f60608486031215610d4857610d47610a7b565b5b5f84013567ffffffffffffffff811115610d6557610d64610a7f565b5b610d7186828701610cac565b935050602084013567ffffffffffffffff811115610d9257610d91610a7f565b5b610d9e86828701610c2f565b925050604084013567ffffffffffffffff811115610dbf57610dbe610a7f565b5b610dcb86828701610c2f565b9150509250925092565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f610e0782610dd5565b610e118185610ddf565b9350610e21818560208601610def565b610e2a81610a87565b840191505092915050565b610e3e81610bc7565b82525050565b610e4d81610bfc565b82525050565b5f606083015f8301518482035f860152610e6d8282610dfd565b9150506020830151610e826020860182610e35565b506040830151610e956040860182610e44565b508091505092915050565b5f6020820190508181035f830152610eb88184610e53565b905092915050565b610ec981610bfc565b82525050565b5f602082019050610ee25f830184610ec0565b92915050565b5f5f5f60608486031215610eff57610efe610a7b565b5b5f84013567ffffffffffffffff811115610f1c57610f1b610a7f565b5b610f2886828701610c2f565b9350506020610f3986828701610c1b565b9250506040610f4a86828701610c1b565b9150509250925092565b5f606083015f8301518482035f860152610f6e8282610dfd565b9150506020830151610f836020860182610e35565b506040830151610f966040860182610e44565b508091505092915050565b5f604083015f8301518482035f860152610fbb8282610f54565b91505060208301518482036020860152610fd58282610f54565b9150508091505092915050565b5f6020820190508181035f830152610ffa8184610fa1565b905092915050565b5f6020828403121561101757611016610a7b565b5b5f82013567ffffffffffffffff81111561103457611033610a7f565b5b61104084828501610c2f565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f61106d82611049565b6110778185611053565b9350611087818560208601610def565b61109081610a87565b840191505092915050565b5f6020820190508181035f8301526110b38184611063565b905092915050565b5f60ff82169050919050565b6110d0816110bb565b82525050565b5f6020820190506110e95f8301846110c7565b92915050565b5f67ffffffffffffffff82111561110957611108610a97565b5b61111282610a87565b9050602081019050919050565b5f61113161112c846110ef565b610af5565b90508281526020810184848401111561114d5761114c610b17565b5b611158848285610b4b565b509392505050565b5f82601f83011261117457611173610b13565b5b813561118484826020860161111f565b91505092915050565b5f5f604083850312156111a3576111a2610a7b565b5b5f83013567ffffffffffffffff8111156111c0576111bf610a7f565b5b6111cc85828601611160565b925050602083013567ffffffffffffffff8111156111ed576111ec610a7f565b5b6111f985828601611160565b9150509250929050565b61120c81610bc7565b82525050565b5f6020820190506112255f830184611203565b92915050565b5f82825260208201905092915050565b5f61124582610dd5565b61124f818561122b565b935061125f818560208601610def565b61126881610a87565b840191505092915050565b61127c81610bc7565b82525050565b61128b81610bfc565b82525050565b5f606083015f8301518482035f8601526112ab828261123b565b91505060208301516112c06020860182611273565b5060408301516112d36040860182611282565b508091505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061132257607f821691505b602082108103611335576113346112de565b5b50919050565b5f819050815f5260205f209050919050565b5f81546113598161130b565b611363818661122b565b9450600182165f811461137d5760018114611393576113c5565b60ff1983168652811515602002860193506113c5565b61139c8561133b565b5f5b838110156113bd5781548189015260018201915060208101905061139e565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f6113f76113f2836113ce565b6113d9565b9050919050565b5f819050919050565b5f611419611414836113ce565b6113fe565b9050919050565b5f606083015f5f84018583035f87015261143a838261134d565b9250506001840154905061144d816113e5565b61145a6020870182611273565b506002840154905061146b81611407565b6114786040870182611282565b50819250505092915050565b5f6060820190508181035f83015261149c8186611291565b905081810360208301526114b08185611291565b905081810360408301526114c48184611420565b9050949350505050565b5f6114e06114db84610b1b565b610af5565b9050828152602081018484840111156114fc576114fb610b17565b5b611507848285610def565b509392505050565b5f82601f83011261152357611522610b13565b5b81516115338482602086016114ce565b91505092915050565b5f8151905061154a81610bd2565b92915050565b5f8151905061155e81610c05565b92915050565b5f6060828403121561157957611578610a83565b5b6115836060610af5565b90505f82015167ffffffffffffffff8111156115a2576115a1610b0f565b5b6115ae8482850161150f565b5f8301525060206115c18482850161153c565b60208301525060406115d584828501611550565b60408301525092915050565b5f602082840312156115f6576115f5610a7b565b5b5f82015167ffffffffffffffff81111561161357611612610a7f565b5b61161f84828501611564565b91505092915050565b7f6331506f77507269766174654b657900000000000000000000000000000000005f82015250565b5f61165c600f83611053565b915061166782611628565b602082019050919050565b5f6020820190508181035f83015261168981611650565b9050919050565b5f6060820190508181035f8301526116a88186611291565b905081810360208301526116bc8185611420565b905081810360408301526116d08184611291565b9050949350505050565b5f602082840312156116ef576116ee610a7b565b5b5f6116fc8482850161153c565b91505092915050565b7f766572696679526573756c7400000000000000000000000000000000000000005f82015250565b5f611739600c83611053565b915061174482611705565b602082019050919050565b5f6020820190508181035f8301526117668161172d565b9050919050565b7f496e76616c6964206d6f64756c617220696e76657273650000000000000000005f82015250565b5f6117a1601783611053565b91506117ac8261176d565b602082019050919050565b5f6020820190508181035f8301526117ce81611795565b9050919050565b7f6d6f646d756c00000000000000000000000000000000000000000000000000005f82015250565b5f611809600683611053565b9150611814826117d5565b602082019050919050565b5f6020820190508181035f830152611836816117fd565b9050919050565b5f819050919050565b61185761185282610bfc565b61183d565b82525050565b5f6118688285611846565b6020820191506118788284611846565b6020820191508190509392505050565b5f819050919050565b5f819050919050565b6118ab6118a682611888565b611891565b82525050565b5f6118bc828561189a565b6020820191506118cc8284611846565b6020820191508190509392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61191382610bfc565b915061191e83610bfc565b9250828201905080821115611936576119356118dc565b5b92915050565b5f82825260208201905092915050565b5f61195682610dd5565b611960818561193c565b9350611970818560208601610def565b61197981610a87565b840191505092915050565b61198d81610bc7565b82525050565b5f6040820190508181035f8301526119ab818561194c565b90506119ba6020830184611984565b9392505050565b6119ca81610bfc565b82525050565b5f6040820190506119e35f8301856119c1565b6119f06020830184611984565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f611a2e82610bfc565b9150611a3983610bfc565b9250828203905081811115611a5157611a506118dc565b5b92915050565b5f81905092915050565b5f611a6b82611049565b611a758185611a57565b9350611a85818560208601610def565b80840191505092915050565b5f611a9c8284611a61565b91508190509291505056fea26469706673582212209ccf670fdd2d6be00fef623dcd7ed3545f4badddcf67ab9703e8f4782124f46864736f6c634300081d0033
Deployed Bytecode
0x608060405234801561000f575f5ffd5b5060043610610060575f3560e01c80630c5cbefa146100645780634411b3eb146100945780635d923c38146100b25780636ab54ad9146100e25780636af2ca6914610112578063c04407a414610130575b5f5ffd5b61007e60048036038101906100799190610d31565b610160565b60405161008b9190610ea0565b60405180910390f35b61009c6103d3565b6040516100a99190610ecf565b60405180910390f35b6100cc60048036038101906100c79190610ee8565b6103d8565b6040516100d99190610fe2565b60405180910390f35b6100fc60048036038101906100f79190611002565b610862565b604051610109919061109b565b60405180910390f35b61011a6109ce565b60405161012791906110d6565b60405180910390f35b61014a6004803603810190610145919061118d565b6109d3565b6040516101579190611212565b60405180910390f35b610168610a2b565b5f73f2635f00300f16d9aca57f955091cc24dd01f7d1630e7671f2865f0151865f6040518463ffffffff1660e01b81526004016101a793929190611484565b5f60405180830381865af41580156101c1573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906101e991906115e1565b90507f60c4a008f597bb8f5bc1f337c149339ea1fe94327ecb9ad2582a3a19aba4460360405161021890611672565b60405180910390a15f73f2635f00300f16d9aca57f955091cc24dd01f7d163cb5c2316835f876040518463ffffffff1660e01b815260040161025c93929190611690565b602060405180830381865af4158015610277573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061029b91906116da565b90507f60c4a008f597bb8f5bc1f337c149339ea1fe94327ecb9ad2582a3a19aba446036040516102ca9061174f565b60405180910390a180610312576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610309906117b7565b60405180910390fd5b7f60c4a008f597bb8f5bc1f337c149339ea1fe94327ecb9ad2582a3a19aba4460360405161033f9061181f565b60405180910390a173f2635f00300f16d9aca57f955091cc24dd01f7d163a5d4ca5b8760200151865f6040518463ffffffff1660e01b815260040161038693929190611484565b5f60405180830381865af41580156103a0573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906103c891906115e1565b925050509392505050565b600a81565b6103e0610a4c565b6103e8610a2b565b5f8303610548575f424460405160200161040392919061185d565b6040516020818303038152906040528051906020012090505f602067ffffffffffffffff81111561043757610436610a97565b5b6040519080825280601f01601f1916602001820160405280156104695781602001600182028036833780820191505090505b5090505f5f90505b60208110156104c3575f838260405160200161048e9291906118b1565b6040516020818303038152906040528051906020012090508082602085010152506020816104bc9190611909565b9050610471565b5073f2635f00300f16d9aca57f955091cc24dd01f7d163f33d5856825f6040518363ffffffff1660e01b81526004016104fd929190611993565b5f60405180830381865af4158015610517573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061053f91906115e1565b925050506105c6565b73f2635f00300f16d9aca57f955091cc24dd01f7d16340957f72845f6040518363ffffffff1660e01b81526004016105819291906119d0565b5f60405180830381865af415801561059b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906105c391906115e1565b90505b5f73f2635f00300f16d9aca57f955091cc24dd01f7d16340957f7260025f6040518363ffffffff1660e01b81526004016106019291906119d0565b5f60405180830381865af415801561061b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061064391906115e1565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d1630e7671f283855f6040518463ffffffff1660e01b815260040161068193929190611484565b5f60405180830381865af415801561069b573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906106c391906115e1565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d16340957f72885f6040518363ffffffff1660e01b81526004016106ff9291906119d0565b5f60405180830381865af4158015610719573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061074191906115e1565b90505f73f2635f00300f16d9aca57f955091cc24dd01f7d163a5d4ca5b73f2635f00300f16d9aca57f955091cc24dd01f7d1630e7671f285895f6040518463ffffffff1660e01b815260040161079993929190611484565b5f60405180830381865af41580156107b3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906107db91906115e1565b8b5f6040518463ffffffff1660e01b81526004016107fb93929190611484565b5f60405180830381865af4158015610815573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061083d91906115e1565b9050604051806040016040528084815260200182815250955050505050509392505050565b60605f825f015190505f5f90505f5f90505b82518110156108db575f60f81b838281518110610894576108936119f7565b5b602001015160f81c60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146108ce578091506108db565b8080600101915050610874565b505f8183516108ea9190611a24565b67ffffffffffffffff81111561090357610902610a97565b5b6040519080825280601f01601f1916602001820160405280156109355781602001600182028036833780820191505090505b5090505f5f90505b81518110156109bd578383826109539190611909565b81518110610964576109636119f7565b5b602001015160f81c60f81b828281518110610982576109816119f7565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a905350808060010191505061093d565b505f81905080945050505050919050565b60ff81565b5f816040516020016109e59190611a91565b6040516020818303038152906040528051906020012083604051602001610a0c9190611a91565b6040516020818303038152906040528051906020012014905092915050565b6040518060600160405280606081526020015f151581526020015f81525090565b6040518060400160405280610a5f610a2b565b8152602001610a6c610a2b565b81525090565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610acd82610a87565b810181811067ffffffffffffffff82111715610aec57610aeb610a97565b5b80604052505050565b5f610afe610a72565b9050610b0a8282610ac4565b919050565b5f5ffd5b5f5ffd5b5f5ffd5b5f67ffffffffffffffff821115610b3557610b34610a97565b5b610b3e82610a87565b9050602081019050919050565b828183375f83830152505050565b5f610b6b610b6684610b1b565b610af5565b905082815260208101848484011115610b8757610b86610b17565b5b610b92848285610b4b565b509392505050565b5f82601f830112610bae57610bad610b13565b5b8135610bbe848260208601610b59565b91505092915050565b5f8115159050919050565b610bdb81610bc7565b8114610be5575f5ffd5b50565b5f81359050610bf681610bd2565b92915050565b5f819050919050565b610c0e81610bfc565b8114610c18575f5ffd5b50565b5f81359050610c2981610c05565b92915050565b5f60608284031215610c4457610c43610a83565b5b610c4e6060610af5565b90505f82013567ffffffffffffffff811115610c6d57610c6c610b0f565b5b610c7984828501610b9a565b5f830152506020610c8c84828501610be8565b6020830152506040610ca084828501610c1b565b60408301525092915050565b5f60408284031215610cc157610cc0610a83565b5b610ccb6040610af5565b90505f82013567ffffffffffffffff811115610cea57610ce9610b0f565b5b610cf684828501610c2f565b5f83015250602082013567ffffffffffffffff811115610d1957610d18610b0f565b5b610d2584828501610c2f565b60208301525092915050565b5f5f5f60608486031215610d4857610d47610a7b565b5b5f84013567ffffffffffffffff811115610d6557610d64610a7f565b5b610d7186828701610cac565b935050602084013567ffffffffffffffff811115610d9257610d91610a7f565b5b610d9e86828701610c2f565b925050604084013567ffffffffffffffff811115610dbf57610dbe610a7f565b5b610dcb86828701610c2f565b9150509250925092565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f610e0782610dd5565b610e118185610ddf565b9350610e21818560208601610def565b610e2a81610a87565b840191505092915050565b610e3e81610bc7565b82525050565b610e4d81610bfc565b82525050565b5f606083015f8301518482035f860152610e6d8282610dfd565b9150506020830151610e826020860182610e35565b506040830151610e956040860182610e44565b508091505092915050565b5f6020820190508181035f830152610eb88184610e53565b905092915050565b610ec981610bfc565b82525050565b5f602082019050610ee25f830184610ec0565b92915050565b5f5f5f60608486031215610eff57610efe610a7b565b5b5f84013567ffffffffffffffff811115610f1c57610f1b610a7f565b5b610f2886828701610c2f565b9350506020610f3986828701610c1b565b9250506040610f4a86828701610c1b565b9150509250925092565b5f606083015f8301518482035f860152610f6e8282610dfd565b9150506020830151610f836020860182610e35565b506040830151610f966040860182610e44565b508091505092915050565b5f604083015f8301518482035f860152610fbb8282610f54565b91505060208301518482036020860152610fd58282610f54565b9150508091505092915050565b5f6020820190508181035f830152610ffa8184610fa1565b905092915050565b5f6020828403121561101757611016610a7b565b5b5f82013567ffffffffffffffff81111561103457611033610a7f565b5b61104084828501610c2f565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f61106d82611049565b6110778185611053565b9350611087818560208601610def565b61109081610a87565b840191505092915050565b5f6020820190508181035f8301526110b38184611063565b905092915050565b5f60ff82169050919050565b6110d0816110bb565b82525050565b5f6020820190506110e95f8301846110c7565b92915050565b5f67ffffffffffffffff82111561110957611108610a97565b5b61111282610a87565b9050602081019050919050565b5f61113161112c846110ef565b610af5565b90508281526020810184848401111561114d5761114c610b17565b5b611158848285610b4b565b509392505050565b5f82601f83011261117457611173610b13565b5b813561118484826020860161111f565b91505092915050565b5f5f604083850312156111a3576111a2610a7b565b5b5f83013567ffffffffffffffff8111156111c0576111bf610a7f565b5b6111cc85828601611160565b925050602083013567ffffffffffffffff8111156111ed576111ec610a7f565b5b6111f985828601611160565b9150509250929050565b61120c81610bc7565b82525050565b5f6020820190506112255f830184611203565b92915050565b5f82825260208201905092915050565b5f61124582610dd5565b61124f818561122b565b935061125f818560208601610def565b61126881610a87565b840191505092915050565b61127c81610bc7565b82525050565b61128b81610bfc565b82525050565b5f606083015f8301518482035f8601526112ab828261123b565b91505060208301516112c06020860182611273565b5060408301516112d36040860182611282565b508091505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061132257607f821691505b602082108103611335576113346112de565b5b50919050565b5f819050815f5260205f209050919050565b5f81546113598161130b565b611363818661122b565b9450600182165f811461137d5760018114611393576113c5565b60ff1983168652811515602002860193506113c5565b61139c8561133b565b5f5b838110156113bd5781548189015260018201915060208101905061139e565b808801955050505b50505092915050565b5f815f1c9050919050565b5f60ff82169050919050565b5f6113f76113f2836113ce565b6113d9565b9050919050565b5f819050919050565b5f611419611414836113ce565b6113fe565b9050919050565b5f606083015f5f84018583035f87015261143a838261134d565b9250506001840154905061144d816113e5565b61145a6020870182611273565b506002840154905061146b81611407565b6114786040870182611282565b50819250505092915050565b5f6060820190508181035f83015261149c8186611291565b905081810360208301526114b08185611291565b905081810360408301526114c48184611420565b9050949350505050565b5f6114e06114db84610b1b565b610af5565b9050828152602081018484840111156114fc576114fb610b17565b5b611507848285610def565b509392505050565b5f82601f83011261152357611522610b13565b5b81516115338482602086016114ce565b91505092915050565b5f8151905061154a81610bd2565b92915050565b5f8151905061155e81610c05565b92915050565b5f6060828403121561157957611578610a83565b5b6115836060610af5565b90505f82015167ffffffffffffffff8111156115a2576115a1610b0f565b5b6115ae8482850161150f565b5f8301525060206115c18482850161153c565b60208301525060406115d584828501611550565b60408301525092915050565b5f602082840312156115f6576115f5610a7b565b5b5f82015167ffffffffffffffff81111561161357611612610a7f565b5b61161f84828501611564565b91505092915050565b7f6331506f77507269766174654b657900000000000000000000000000000000005f82015250565b5f61165c600f83611053565b915061166782611628565b602082019050919050565b5f6020820190508181035f83015261168981611650565b9050919050565b5f6060820190508181035f8301526116a88186611291565b905081810360208301526116bc8185611420565b905081810360408301526116d08184611291565b9050949350505050565b5f602082840312156116ef576116ee610a7b565b5b5f6116fc8482850161153c565b91505092915050565b7f766572696679526573756c7400000000000000000000000000000000000000005f82015250565b5f611739600c83611053565b915061174482611705565b602082019050919050565b5f6020820190508181035f8301526117668161172d565b9050919050565b7f496e76616c6964206d6f64756c617220696e76657273650000000000000000005f82015250565b5f6117a1601783611053565b91506117ac8261176d565b602082019050919050565b5f6020820190508181035f8301526117ce81611795565b9050919050565b7f6d6f646d756c00000000000000000000000000000000000000000000000000005f82015250565b5f611809600683611053565b9150611814826117d5565b602082019050919050565b5f6020820190508181035f830152611836816117fd565b9050919050565b5f819050919050565b61185761185282610bfc565b61183d565b82525050565b5f6118688285611846565b6020820191506118788284611846565b6020820191508190509392505050565b5f819050919050565b5f819050919050565b6118ab6118a682611888565b611891565b82525050565b5f6118bc828561189a565b6020820191506118cc8284611846565b6020820191508190509392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61191382610bfc565b915061191e83610bfc565b9250828201905080821115611936576119356118dc565b5b92915050565b5f82825260208201905092915050565b5f61195682610dd5565b611960818561193c565b9350611970818560208601610def565b61197981610a87565b840191505092915050565b61198d81610bc7565b82525050565b5f6040820190508181035f8301526119ab818561194c565b90506119ba6020830184611984565b9392505050565b6119ca81610bfc565b82525050565b5f6040820190506119e35f8301856119c1565b6119f06020830184611984565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f611a2e82610bfc565b9150611a3983610bfc565b9250828203905081811115611a5157611a506118dc565b5b92915050565b5f81905092915050565b5f611a6b82611049565b611a758185611a57565b9350611a85818560208601610def565b80840191505092915050565b5f611a9c8284611a61565b91508190509291505056fea26469706673582212209ccf670fdd2d6be00fef623dcd7ed3545f4badddcf67ab9703e8f4782124f46864736f6c634300081d0033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
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.