Overview
APE Balance
APE Value
Less Than $0.01 (@ $0.17/APE)Multichain Info
Latest 10 from a total of 10 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Start Game | 16294773 | 245 days ago | IN | 0 APE | 0.00422837 | ||||
| Admin Set Pyth P... | 16294765 | 245 days ago | IN | 0 APE | 0.00078453 | ||||
| Join Lobby | 16294660 | 245 days ago | IN | 0.01 APE | 0.00286552 | ||||
| Join Lobby | 16294656 | 245 days ago | IN | 0.01 APE | 0.00286552 | ||||
| Join Lobby | 16294649 | 245 days ago | IN | 0.01 APE | 0.00286552 | ||||
| Join Lobby | 16294641 | 245 days ago | IN | 0.01 APE | 0.00286552 | ||||
| Create Lobby | 16294635 | 245 days ago | IN | 0.01 APE | 0.00748163 | ||||
| Admin Set Town H... | 16294595 | 245 days ago | IN | 0 APE | 0.00122375 | ||||
| Admin Set Backen... | 16294591 | 245 days ago | IN | 0 APE | 0.00121307 | ||||
| Admin Set Pyth P... | 16294586 | 245 days ago | IN | 0 APE | 0.00121922 |
Latest 1 internal transaction
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 16294773 | 245 days ago | 0.02 APE |
Cross-Chain Transactions
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// =================================================================================================
// Imports
// =================================================================================================
// --- OpenZeppelin Imports ---
// These imports are required for interacting with the OpenZeppelin contracts.
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
// --- Pyth Entropy Imports ---
// These interfaces are required for interacting with the Pyth Network's secure randomness oracle.
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
// --- Custom Libraries ---
import "../GameDefinitions.sol";
import "../libraries/RoleLogic.sol";
import "../libraries/PayoutLogic.sol";
import "../libraries/VoteLogic.sol";
// =================================================================================================
// Contract Definition: DemWitches (v3.9)
// =================================================================================================
/**
* @notice This contract implements the 'DemWitches' game on ApeChain Mainnet.
* @dev Built for Solidity ^0.8.20, integrating with Pyth Entropy for secure random number generation.
*/
contract DemWitches is Ownable, ReentrancyGuard, Pausable, IEntropyConsumer {
// =================================================================================================
// Constants
// =================================================================================================
/**
* @notice The reward for a dead Townfolk when their team wins, displayed as a percentage of their tribute (buy-in).
* @dev Value is in Basis Points (BPS). E.g., 5000 means 50%.
*/
// uint256 public constant DEAD_TOWNFOLK_REWARD = 5000; // Moved to GameDefinitions.sol
/**
* @notice The duration of the assigned grace periods (seconds) after a phase ends before public timeout is allowed.
* @dev This buffer allows the backend a chance to process before public intervention.
*/
uint256 public constant GRACE_DURATION = 5 * 60; // 5 minutes
/**
* @notice The maximum platform fee displayed in a percentage (BPS) that the owner can set.
* @dev Caps the platform fee to prevent excessively high charges.
*/
uint16 public constant MAX_FEE = 1000; // 10%
/**
* @notice The maximum number of players allowed in any given game.
* @dev Absolute upper limit for game room capacity.
*/
uint8 public constant MAX_PLAYERS = 12;
/**
* @notice The minimum number of players allowed in any given game for it to start.
* @dev Ensures games are viable and engaging.
*/
uint8 public constant MIN_PLAYERS = 5;
/**
* @notice The total number of witches set for all games. (This version is fixed to 1).
* @dev Defines the core Witch vs. Townfolk ratio.
*/
// uint8 public constant WITCH_COUNT = 1; // Moved to GameDefinitions.sol
// =================================================================================================
// State Variables
// =================================================================================================
/** @notice Immutable address of the Pyth Network Entropy oracle contract, set at deployment. */
IEntropy public immutable PYTH_ENTROPY; // Access via pythEntropyAddress()
/** @dev Internal counter for assigning unique IDs to newly created games. Use `nextGameID()` to view. */
uint256 private _nextGameId;
/** @dev Address of the Pyth Network service provider. Use `pythProviderAddress()` to view. */
address private _pythProviderAddress;
/** @dev Wallet address authorized for backend server operations. Use `backendAddress()` to view. */
address private _backendAddress;
/** @dev Wallet address designated for a Validator role. Use `getValidatorAddress()` to view. */
address private _validatorAddress;
/**
* @notice The designated wallet address where platform fees collected from completed games will be directly transferred.
* @dev Configured by the owner. Use `getTownHallWallet()` to view.
*/
address public townHallWallet;
/** @dev Default platform fee (basis points) for games. Use `fee()` to view. */
uint256 private _feeBps;
/** @dev Approximation of native $APE funds locked in active or unsettled games. Use `getPlayersFundsLocked()` to view. */
uint256 private _totalPlayerFundsLocked;
/** @dev First eligible NFT collection for Witch role boost. Use `getBoostedCollection1()` to view. */
address private _boostedCollection1;
/** @dev Second eligible NFT collection for Witch role boost. Use `getBoostedCollection2()` to view. */
address private _boostedCollection2;
/** @dev Configured duration (seconds) for Night phases. Use `nightDuration()` to view. */
uint256 private _nightDuration;
/** @dev Configured duration (seconds) for Day phases. Use `dayDuration()` to view. */
uint256 private _dayDuration;
/** @dev Configured duration (seconds) for Revote phases. Use `revoteDuration()` to view. */
uint256 private _revoteDuration;
/** @dev Configurable duration (seconds) an unstarted lobby can exist before public cancellation via timeout. */
uint256 private _lobbyTimeoutDuration;
/** @dev Configurable duration (seconds) Dem Witches will wait for Pyth randomness before admin can cancel. */
uint256 private _randomnessTimeoutDefault;
// NFT Boost Tier Configuration
/** @dev Thresholds for NFT counts for tiered boosts. Length must be N. */
uint8[] private _nftBoostTiersCounts;
/** @dev Boost BPS values for each tier. Length must be N+1. */
uint256[] private _nftBoostTiersBps;
// Game Data Mappings
mapping(uint256 => GameSettings) internal games;
mapping(uint256 => mapping(address => Player)) internal gamePlayers;
mapping(uint256 => address[]) internal playerList;
mapping(uint256 => address[]) internal playersInRevote;
mapping(uint64 => uint256) internal sequenceNumberToGameId;
mapping(uint256 => address[]) internal winners;
mapping(uint256 => uint256) internal payoutPerWinnerTownfolk;
// =================================================================================================
// Structs & Enums (Moved to GameDefinitions.sol)
// =================================================================================================
// enum Role, enum GameState, struct Player, struct GameSettings are now imported from GameDefinitions.sol
/**
* @notice Provides a publicly viewable snapshot of a player's information, Excluding their secret role to maintain game integrity.
*/
struct PublicPlayerInfo {
/** @notice The unique wallet address of the player. */
address playerAddress;
/** @notice Tracks whether the player is currently active or has been eliminated. */
bool isAlive;
/** @notice Indicates if the player has successfully claimed their winnings or refund. */
bool claimedPayout;
/** @notice The tiered NFT boost percentage (basis points) that was cached for this player upon joining. */
uint256 nftBoostBps;
}
// =================================================================================================
// Events
// =================================================================================================
/**
* @notice Emitted when a new game lobby is successfully created.
* @param gameId Unique ID of the created game.
* @param host Address of the game's host.
* @param gameBuyIn Fixed native $APE buy-in amount (Wei) for this game.
* @param maxPlayers Maximum number of players allowed in this game.
* @param feeBps Platform fee (basis points) applied to this game.
*/
event GameCreated(uint256 indexed gameId, address indexed host, uint256 gameBuyIn, uint8 maxPlayers, uint256 feeBps);
/**
* @notice Emitted when a player successfully joins a game lobby.
* @param gameId ID of the game joined.
* @param player Address of the player who joined.
* @param nftBoostAppliedBps NFT boost (BPS) applied to the player upon joining.
*/
event PlayerJoined(uint256 indexed gameId, address indexed player, uint256 nftBoostAppliedBps);
/**
* @notice Emitted when a game host requests to start the game, initiating Pyth randomness.
* @param gameId ID of the game being started.
* @param host Address of the host initiating the start.
* @param sequenceNumber Pyth Entropy request sequence number for tracking the randomness request.
*/
event GameStartRequested(uint256 indexed gameId, address indexed host, uint64 sequenceNumber);
/**
* @notice Emitted when Pyth randomness is successfully received via callback.
* @param gameId ID of the game for which randomness was received.
* @param sequenceNumber Pyth Entropy request sequence number that was fulfilled.
*/
event RandomnessReceived(uint256 indexed gameId, uint64 indexed sequenceNumber);
/**
* @notice Emitted after roles have been assigned using Pyth randomness at the start of a game.
* @param gameId ID of the game where roles were assigned.
*/
event RolesAssigned(uint256 indexed gameId);
/**
* @notice Emitted when a Night phase begins.
* @param gameId ID of the game.
* @param round The current game round number.
*/
event NightPhaseStarted(uint256 indexed gameId, uint256 round);
/**
* @notice Emitted when a player is cursed by the Witch during the Night phase.
* @param gameId ID of the game.
* @param round The round number of the curse.
* @param target Address of the player cursed by the Witch.
*/
event PlayerCursed(uint256 indexed gameId, uint256 round, address indexed target);
/**
* @notice Emitted when the Witch's curse results in the last Townfolk being eliminated due to the final kill mechanic.
* @param gameId ID of the game.
* @param round The round number of this event.
* @param lastTownfolk Address of the last Townfolk who was eliminated.
*/
event WitchKilledLastTownfolk(uint256 indexed gameId, uint256 round, address indexed lastTownfolk);
/**
* @notice Emitted when a Day phase begins.
* @param gameId ID of the game.
* @param round The current game round number.
* @param killedInNight Address of the player killed in the preceding Night phase (0x0 if no one was killed).
*/
event DayPhaseStarted(uint256 indexed gameId, uint256 round, address killedInNight);
/**
* @notice Emitted when a player is eliminated (burned) as a result of Day phase voting.
* @param gameId ID of the game.
* @param round The round number of the burning.
* @param target Address of the player who was burned.
* @param votes Number of votes the burned player received (for informational context).
*/
event PlayerBurned(uint256 indexed gameId, uint256 round, address indexed target, uint256 votes);
/**
* @notice Emitted if a Day phase vote results in a tie between two or more players.
* @param gameId ID of the game.
* @param round The round number where the tie occurred.
* @param tiedPlayers Array of player addresses who were involved in the tie.
*/
event VoteTied(uint256 indexed gameId, uint256 round, address[] tiedPlayers);
/**
* @notice Emitted when a Revote phase begins due to a tie in the preceding Day phase voting.
* @param gameId ID of the game.
* @param round The round number of the revote (same as the Day phase that tied).
* @param playersInRevote Array of player addresses participating in this revote.
*/
event RevotePhaseStarted(uint256 indexed gameId, uint256 round, address[] playersInRevote);
/**
* @notice Emitted when a player is eliminated (burned) as a result of Revote phase voting.
* @param gameId ID of the game.
* @param round The round number of the burning after the revote.
* @param target Address of the player who was burned after the revote.
* @param votes Number of votes the burned player received in the revote (for informational context).
*/
event PlayerBurnedAfterRevote(uint256 indexed gameId, uint256 round, address indexed target, uint256 votes);
/**
* @notice Emitted if a Revote phase also results in a tie (meaning no one was burned from the revote).
* @param gameId ID of the game.
* @param round The round number where the revote tied.
*/
event RevoteTied(uint256 indexed gameId, uint256 round);
/**
* @notice Emitted when a game concludes and winners are determined.
* @param gameId ID of the finished game.
* @param winningTeam The role of the team that won the game (Townfolk or Witch).
* @param winners Array of wallet addresses of the winning players (alive members of the winning team).
* @param payoutPerWinner The calculated payout amount per living winner of the winning team (for context; actual claims are separate).
*/
event GameFinished(uint256 indexed gameId, Role winningTeam, address[] winners, uint256 payoutPerWinner);
/**
* @notice Emitted when a player successfully claims their payout or refund.
* @param gameId ID of the game from which the claim was made.
* @param player Address of the player who successfully claimed their funds.
* @param amount Amount of native $APE claimed by the player.
*/
event PayoutClaimed(uint256 indexed gameId, address indexed player, uint256 amount);
/**
* @notice Emitted when a game is cancelled by an admin or due to a timeout.
* @param gameId ID of the cancelled game.
* @param cancelledBy Address of the account that triggered the cancellation.
* @param reason A descriptive reason for the game's cancellation.
*/
event GameCancelled(uint256 indexed gameId, address indexed cancelledBy, string reason);
/**
* @notice Emitted when a player is refunded their buy-in, typically due to game cancellation.
* @param gameId ID of the game related to the refund.
* @param player Address of the player who received the refund.
* @param amount Amount of native $APE refunded to the player.
*/
event PlayerRefunded(uint256 indexed gameId, address indexed player, uint256 amount);
/**
* @notice Emitted when an admin-triggered payout retry for a player succeeds.
* @param gameId ID of the game.
* @param player Address of the player whose payout was successfully retried.
* @param amount Amount of native $APE paid out.
*/
event PayoutRetrySucceeded(uint256 indexed gameId, address indexed player, uint256 amount);
/**
* @notice Emitted when an admin-triggered payout retry for a player fails.
* @param gameId ID of the game.
* @param player Address of the player whose payout retry failed.
*/
event PayoutRetryFailed(uint256 indexed gameId, address indexed player);
/**
* @notice Emitted when a game phase is advanced due to a public timeout (e.g., backend inactivity).
* @param gameId ID of the game.
* @param timedOutPhase The specific game phase that timed out.
* @param round The game round number during which the timeout occurred.
* @param reason The reason for the forced phase advance (usually "Public timeout fallback invoked").
*/
event PhaseForcedByTimeout(uint256 indexed gameId, GameState timedOutPhase, uint256 round, string reason);
/**
* @notice Emitted when a player is eliminated due to inactivity by an authorized operator.
* @param gameId ID of the game.
* @param round The game round number when the elimination occurred.
* @param player Address of the player who was eliminated for inactivity.
*/
event PlayerInactiveEliminated(uint256 indexed gameId, uint256 round, address indexed player);
/**
* @notice Emitted when the default platform fee is updated by the contract owner.
* @param newFeeBasisPoints The new platform fee percentage in basis points (BPS).
*/
event PlatformFeeSet(uint256 newFeeBasisPoints);
/**
* @notice Emitted when the Pyth Network entropy provider address is updated by the contract owner.
* @param providerAddress The new Pyth provider address.
*/
event PythProviderSet(address indexed providerAddress);
/**
* @notice Emitted when the trusted backend server address is updated by the contract owner.
* @param backendAddress The new authorized backend address.
*/
event BackendAddressSet(address indexed backendAddress);
/**
* @notice Emitted when the validator address is updated by the contract owner.
* @param oldValidator The previous validator address (can be address(0)).
* @param newValidator The new validator address.
*/
event ValidatorSet(address indexed oldValidator, address indexed newValidator);
/**
* @notice Emitted when an NFT collection address for role boost is set or updated by the owner.
* @param index Identifier for the collection slot (1 for the first, 2 for the second).
* @param collection The new contract address of the NFT collection.
*/
event NftCollectionSet(uint8 indexed index, address indexed collection);
/**
* @notice Emitted when game phase durations are updated by the owner.
* @param lobby New duration for lobby timeout in seconds.
* @param randomness New duration for randomness timeout in seconds.
* @param night New duration for Night phases in seconds.
* @param day New duration for Day phases in seconds.
* @param revote New duration for Revote phases in seconds.
*/
event GameDurationsSet(uint256 lobby, uint256 randomness, uint256 night, uint256 day, uint256 revote);
/**
* @notice Emitted when NFT boost tiers are updated by the owner.
* @param nftCounts Array of NFT count thresholds for tiers.
* @param boostBps Array of boost BPS values for corresponding tiers.
*/
event NftBoostTiersSet(uint8[] nftCounts, uint256[] boostBps);
/**
* @notice Emitted when the town hall (treasury) wallet address for fee collection is updated by the owner.
* @param newTownHallWallet The new wallet address for receiving platform fees.
*/
event TownHallWalletSet(address indexed newTownHallWallet);
/**
* @notice Emitted when the contract's pausable functions are paused by the owner.
* @param account The owner address that executed the pause.
*/
event GamePaused(address account);
/**
* @notice Emitted when the contract's pausable functions are unpaused by the owner.
* @param account The owner address that executed the unpause.
*/
event GameUnpaused(address account);
/**
* @notice Emitted when the game state is forcibly changed by an admin using `adminSetPhase`.
* @param gameId ID of the game whose state was changed.
* @param oldState The game state before the admin action.
* @param newState The game state after the admin action.
* @param admin Address of the admin who forced the state change.
*/
event AdminForcedState(uint256 indexed gameId, GameState oldState, GameState newState, address indexed admin);
// =================================================================================================
// Errors
// =================================================================================================
/**
* @notice IncorrectAmount: Incorrect amount of $APE sent for an operation.
* @param sent Amount sent.
* @param required Amount required.
*/
error InvalidAmount(uint256 sent, uint256 required);
/**
* @notice IncorrectBuyInAmount: Player sent an incorrect buy-in amount.
* @param sent Amount of $APE sent.
* @param required Required $APE buy-in.
*/
error IncorrectBuyInAmount(uint256 sent, uint256 required);
/** @notice ZeroAddress: A critical address parameter was the zero address. */
error ZeroAddress();
/**
* @notice MaxPlayersInvalid: `maxPlayers` for a game is outside allowed limits.
* @param maxPlayers Invalid number specified.
*/
error MaxPlayersInvalid(uint8 maxPlayers);
/**
* @notice FeeTooHigh: Attempt to set platform fee higher than `MAX_FEE`.
* @param feeBps Fee (BPS) attempted.
* @param maxBps Maximum allowed fee (BPS).
*/
error FeeTooHigh(uint256 feeBps, uint256 maxBps);
/** @notice DurationTooShort: Game phase durations set too short. */
error DurationTooShort();
/** @notice InvalidBoostTierConfiguration: NFT boost tier configuration is invalid (e.g., array length mismatch, unsorted counts). */
error InvalidBoostTierConfiguration();
/** @notice PythProviderAddressNotSet: Action requires Pyth provider address, but it's not set. */
error PythProviderAddressNotSet();
/** @notice BackendAddressNotSet: Action requires backend address, but it's not set. */
error BackendAddressNotSet();
/** @notice TownHallWalletNotSet: Platform fees cannot be transferred because the town hall wallet is not set. */
error TownHallWalletNotSet();
/**
* @notice GameNotFound: Operation attempted on a non-existent game ID.
* @param gameId Non-existent game ID.
*/
error GameNotFound(uint256 gameId);
/**
* @notice GameFull: Player attempts to join a game that is already full.
* @param gameId ID of the full game.
* @param maxPlayers Max capacity of the game.
*/
error GameFull(uint256 gameId, uint8 maxPlayers);
/**
* @notice NotEnoughPlayers: Attempt to start a game with fewer than `MIN_PLAYERS`.
* @param gameId ID of the game.
* @param current Current players in lobby.
* @param required Minimum players required.
*/
error NotEnoughPlayers(uint256 gameId, uint8 current, uint8 required);
/**
* @notice GameAlreadyStartedOrFinished: Attempt to start a game that already started or finished.
* @param gameId ID of the game.
*/
error GameAlreadyStartedOrFinished(uint256 gameId);
/**
* @notice InvalidGameState: Game is not in the expected state for an action.
* @param gameId ID of the game.
* @param expected Expected game state.
* @param actual Actual game state.
*/
error InvalidGameState(uint256 gameId, GameState expected, GameState actual);
/**
* @notice WrongGameStateForAction: Action attempted in a game state where it's not applicable.
* @param gameId ID of the game.
* @param actual Current (invalid for action) state.
*/
error WrongGameStateForAction(uint256 gameId, GameState actual);
/**
* @notice GameNotFinished: Payout-related action on a game not yet finished.
* @param gameId ID of the game.
* @param actual Current state (not Finished).
*/
error GameNotFinished(uint256 gameId, GameState actual);
/**
* @notice GameIsFinished: Action requiring an active game on a game already finished/cancelled.
* @param gameId ID of the game.
*/
error GameIsFinished(uint256 gameId);
/**
* @notice RandomnessNotReady: Roles/actions attempted before Pyth randomness fulfilled.
* @param gameId ID of the game.
* @param actual Current state, implying randomness not ready.
*/
error RandomnessNotReady(uint256 gameId, GameState actual);
/**
* @notice AlreadyJoined: Player tries to join a game they are already part of.
* @param gameId ID of the game.
* @param player Player attempting re-join.
*/
error AlreadyJoined(uint256 gameId, address player);
/**
* @notice PlayerNotInGame: Action for a player not registered in the game.
* @param gameId ID of the game.
* @param player Player not found.
*/
error PlayerNotInGame(uint256 gameId, address player);
/**
* @notice PlayerNotAlive: Action requiring an alive player on an eliminated player.
* @param gameId ID of the game.
* @param player Player who is not alive.
*/
error PlayerNotAlive(uint256 gameId, address player);
/**
* @notice NotGameHost: Host-specific action by a non-host.
* @param gameId ID of the game.
* @param sender Unauthorized caller.
* @param host Actual game host.
*/
error NotGameHost(uint256 gameId, address sender, address host);
/**
* @notice NotAWitch: Witch-specific action by a non-Witch.
* @param gameId ID of the game.
* @param sender Non-Witch caller.
*/
error NotAWitch(uint256 gameId, address sender);
/**
* @notice AlreadyClaimed: Player attempts to claim a payout/refund already received.
* @param gameId ID of the game.
* @param player Player attempting double-claim.
*/
error AlreadyClaimed(uint256 gameId, address player);
/**
* @notice InvalidVoteTarget: Vote cast for an invalid target.
* @param gameId ID of the game.
* @param target Invalid target address.
*/
error InvalidVoteTarget(uint256 gameId, address target);
/**
* @notice InvalidRevoteTarget: Revote cast for a player not in the current revote.
* @param gameId ID of the game.
* @param target Invalid revote target.
*/
error InvalidRevoteTarget(uint256 gameId, address target);
/**
* @notice CannotTargetSelf: Player attempts self-targeting where disallowed.
* @param gameId ID of the game.
* @param player Player attempting self-target.
*/
error CannotTargetSelf(uint256 gameId, address player);
/** @notice NativeTransferFailed: Low-level native $APE transfer (send/call) failed. */
error NativeTransferFailed();
/**
* @notice PayoutRetryInvalid: Admin payout retry for ineligible player or zero amount.
* @param gameId ID of the game.
* @param player Player for whom retry is invalid.
*/
error PayoutRetryInvalid(uint256 gameId, address player);
/**
* @notice InvalidCallbackOrigin: Pyth `entropyCallback` called by an unauthorized address.
* @param expected Expected Pyth Entropy contract address.
* @param actual Actual caller address.
*/
error InvalidCallbackOrigin(address expected, address actual);
/** @notice InsufficientContractBalance: Contract's native $APE balance insufficient for a transfer. */
error InsufficientContractBalance();
/**
* @notice InsufficientFundsForPythFee: Contract's native $APE balance insufficient for Pyth fee.
* @param required $APE amount required for Pyth fee.
* @param balance Current contract $APE balance.
*/
error InsufficientFundsForPythFee(uint256 required, uint256 balance);
/**
* @notice InvalidSequenceNumber: Invalid or unexpected Pyth sequence number encountered.
* @param sequenceNum The invalid sequence number.
*/
error InvalidSequenceNumber(uint64 sequenceNum);
/** @notice NotAuthorized: Restricted function called by an unauthorized address. */
error NotAuthorized();
/** @notice TimeoutNotReached: Timeout-dependent action attempted before timeout period elapsed. */
error TimeoutNotReached();
/**
* @notice InternalAssignmentError: Internal error during role assignment process.
* @param gameId ID of game where assignment failed.
*/
error InternalAssignmentError(uint256 gameId);
/**
* @notice CancellationFailed: Game cancellation process failed unexpectedly.
* @param gameId ID of game where cancellation failed.
*/
error CancellationFailed(uint256 gameId);
// =================================================================================================
// Modifiers
// =================================================================================================
/**
* @notice Ensures a function operates on a valid, existing game.
* @dev Reverts if `games[_gameId].host` is `address(0)`.
* @param _gameId The ID of the game to check.
*/
modifier onlyWhenGameExists(uint256 _gameId) {
if (games[_gameId].host == address(0)) revert GameNotFound(_gameId);
_;
}
/**
* @notice Restricts function execution to the game's original host.
* @dev Reverts if `msg.sender` is not `games[_gameId].host`.
* @param _gameId The ID of the game to check host against.
*/
modifier onlyGameHost(uint256 _gameId) {
if (msg.sender != games[_gameId].host) revert NotGameHost(_gameId, msg.sender, games[_gameId].host);
_;
}
/**
* @notice Ensures the game is in the specified state.
* @dev Reverts if `games[_gameId].currentState` is not `_expectedState`.
* @param _gameId The ID of the game to check state of.
* @param _expectedState The `GameState` the game must be in.
*/
modifier inState(uint256 _gameId, GameState _expectedState) {
GameState cs = games[_gameId].currentState;
if (cs != _expectedState) revert InvalidGameState(_gameId, _expectedState, cs);
_;
}
/**
* @notice Ensures the game has not already concluded (Finished or Cancelled).
* @dev Reverts if `games[_gameId].currentState` is `GameState.Finished` or `GameState.Cancelled`.
* @param _gameId The ID of the game to check status of.
*/
modifier notFinished(uint256 _gameId) {
GameState cs = games[_gameId].currentState;
if (cs == GameState.Finished || cs == GameState.Cancelled) revert GameIsFinished(_gameId);
_;
}
/**
* @notice Ensures the player exists and is alive in the game.
* @dev Reverts if player record doesn't exist or `player.isAlive` is false.
* @param _gameId The ID of the game.
* @param _playerAddress The address of the player to check.
*/
modifier playerIsAlive(uint256 _gameId, address _playerAddress) {
Player storage p = gamePlayers[_gameId][_playerAddress];
if (p.playerAddress == address(0)) revert PlayerNotInGame(_gameId, _playerAddress);
if (!p.isAlive) revert PlayerNotAlive(_gameId, _playerAddress);
_;
}
/**
* @notice Ensures the player is a participant in the game (alive or dead).
* @dev Reverts if player record doesn't exist.
* @param _gameId The ID of the game.
* @param _playerAddress The address of the player to check.
*/
modifier playerExists(uint256 _gameId, address _playerAddress) {
if (gamePlayers[_gameId][_playerAddress].playerAddress == address(0)) revert PlayerNotInGame(_gameId, _playerAddress);
_;
}
/**
* @notice Restricts function execution to the contract Owner, the configured Backend address, or the configured Validator address.
* @dev Used for operations requiring a high level of trust or operational capability.
*/
modifier onlyAuthorizedOps() {
if (msg.sender != owner() && msg.sender != _backendAddress && msg.sender != _validatorAddress) revert NotAuthorized();
_;
}
/**
* @notice Restricts function execution strictly to either the configured Backend address or the contract Owner.
* @dev Used for core game processing functions primarily handled by the backend.
*/
modifier onlyBackendOrOwner() {
if (msg.sender != _backendAddress && msg.sender != owner()) revert NotAuthorized();
_;
}
// =================================================================================================
// Receive Function
// =================================================================================================
/**
* @notice Allows the contract to receive direct native $APE transfers.
* @dev This is primarily for the owner or system to fund the contract with native tokens
* required to pay fees for Pyth Network randomness requests, or for other direct deposits if necessary.
*/
receive() external payable {
// Accepts native $APE transfers. No specific logic needed here beyond acceptance.
}
// =================================================================================================
// Constructor
// =================================================================================================
/**
* @notice Initializes the DemWitches contract upon deployment.
* @dev Sets immutable and initial configurable parameters. The `_initialContractOwner` will have
* administrative privileges to further configure the contract (e.g., backend address, NFT collections).
* @param _pythEntropyAddr The immutable blockchain address of the Pyth Network Entropy oracle contract. This cannot be changed post-deployment.
* @param _initialContractOwner The wallet address that will be designated as the initial owner of this contract.
* @param _initialDefaultFeeBps The initial default platform fee percentage (expressed in basis points, e.g., 100 for 1%) to be applied to games.
*/
constructor(
address _pythEntropyAddr,
address _initialContractOwner,
uint256 _initialDefaultFeeBps
) Ownable(_initialContractOwner) {
if (_pythEntropyAddr == address(0)) revert ZeroAddress();
if (_initialDefaultFeeBps > MAX_FEE) revert FeeTooHigh(_initialDefaultFeeBps, MAX_FEE);
PYTH_ENTROPY = IEntropy(_pythEntropyAddr);
_feeBps = _initialDefaultFeeBps;
_nightDuration = 3 * 60; // Default 3 minutes
_dayDuration = 5 * 60; // Default 5 minutes
_revoteDuration = 3 * 60; // Default 3 minutes
_lobbyTimeoutDuration = 1 * 60 * 60; // Default 1 hour
_randomnessTimeoutDefault = 1 * 60 * 60; // Default 1 hour
// Initialize default NFT boost tiers
_nftBoostTiersCounts = [1, 5, 10];
_nftBoostTiersBps = [300, 1000, 2000, 3500]; // Corresponds to 0, 1, 2-5, 6-10, 11+
emit PlatformFeeSet(_initialDefaultFeeBps);
emit GameDurationsSet(_lobbyTimeoutDuration, _randomnessTimeoutDefault, _nightDuration, _dayDuration, _revoteDuration);
emit NftBoostTiersSet(_nftBoostTiersCounts, _nftBoostTiersBps);
}
// =================================================================================================
// External Write Functions: Game Setup & Participation
// =================================================================================================
/**
* @notice Create a new game lobby with a fixed buy-in.
* @dev Allowed by any player.
* @param _gameBuyIn The exact native $APE amount (in Wei) required for each player to join.
* @param _maxPlayers The maximum number of players allowed to join.
*/
function createLobby(uint256 _gameBuyIn, uint8 _maxPlayers)
external
payable
whenNotPaused
nonReentrant
{
if (_gameBuyIn == 0) { revert IncorrectBuyInAmount(0, 1); }
if (_maxPlayers < MIN_PLAYERS || _maxPlayers > MAX_PLAYERS) { revert MaxPlayersInvalid(_maxPlayers); }
if (msg.value != _gameBuyIn) { revert IncorrectBuyInAmount(msg.value, _gameBuyIn); }
uint256 gameIdToCreate = _nextGameId++;
uint256 platformFeeForThisGame = _feeBps;
GameSettings storage newGame = games[gameIdToCreate];
newGame.host = msg.sender;
newGame.gameBuyIn = _gameBuyIn;
newGame.maxPlayers = _maxPlayers;
newGame.totalPot = msg.value;
newGame.platformFeeBasisPoints = platformFeeForThisGame;
newGame.currentState = GameState.Lobby;
newGame.createdAt = block.timestamp;
uint256 hostNftBoostPercentage = RoleLogic.calculateTieredNftBoostInLibrary(msg.sender, _boostedCollection1, _boostedCollection2, _nftBoostTiersCounts, _nftBoostTiersBps);
gamePlayers[gameIdToCreate][msg.sender] = Player({
playerAddress: msg.sender, role: Role.Unassigned, isAlive: true,
claimedPayout: false, nftBoostBps: hostNftBoostPercentage
});
playerList[gameIdToCreate].push(msg.sender);
_totalPlayerFundsLocked += _gameBuyIn;
emit GameCreated(gameIdToCreate, msg.sender, _gameBuyIn, _maxPlayers, platformFeeForThisGame);
emit PlayerJoined(gameIdToCreate, msg.sender, hostNftBoostPercentage);
}
/**
* @notice Join a lobby by paying the game buy-in.
* @dev Allowed by any player.
* @param _gameId The ID of the game to join.
*/
function joinLobby(uint256 _gameId)
external
payable
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Lobby)
{
GameSettings storage gameToJoin = games[_gameId];
if (playerList[_gameId].length >= gameToJoin.maxPlayers) { revert GameFull(_gameId, gameToJoin.maxPlayers); }
if (gamePlayers[_gameId][msg.sender].playerAddress != address(0)) { revert AlreadyJoined(_gameId, msg.sender); }
uint256 requiredBuyInAmount = gameToJoin.gameBuyIn;
if (msg.value != requiredBuyInAmount) { revert IncorrectBuyInAmount(msg.value, requiredBuyInAmount); }
uint256 playerNftBoostPercentage = RoleLogic.calculateTieredNftBoostInLibrary(msg.sender, _boostedCollection1, _boostedCollection2, _nftBoostTiersCounts, _nftBoostTiersBps);
gamePlayers[_gameId][msg.sender] = Player({
playerAddress: msg.sender, role: Role.Unassigned, isAlive: true,
claimedPayout: false, nftBoostBps: playerNftBoostPercentage
});
playerList[_gameId].push(msg.sender);
gameToJoin.totalPot += msg.value;
_totalPlayerFundsLocked += msg.value;
emit PlayerJoined(_gameId, msg.sender, playerNftBoostPercentage);
}
/**
* @notice Start the game.
* @param _gameId The ID of the game to start.
*/
function startGame(uint256 _gameId)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
onlyGameHost(_gameId)
inState(_gameId, GameState.Lobby)
{
GameSettings storage gameToStart = games[_gameId];
if (gameToStart.pythSequenceNumber != 0) { revert GameAlreadyStartedOrFinished(_gameId); }
uint currentPlayersCount = playerList[_gameId].length;
if (currentPlayersCount < MIN_PLAYERS) { revert NotEnoughPlayers(_gameId, uint8(currentPlayersCount), MIN_PLAYERS); }
address pythProvider = _pythProviderAddress;
if (pythProvider == address(0)) { revert PythProviderAddressNotSet(); }
uint128 pythOracleFee = PYTH_ENTROPY.getFee(pythProvider);
if (address(this).balance < pythOracleFee) { revert InsufficientFundsForPythFee(pythOracleFee, address(this).balance); }
bytes32 randomnessRequestSeed = keccak256(abi.encodePacked(_gameId, block.timestamp, currentPlayersCount, msg.sender));
uint64 entropySequenceNumber = PYTH_ENTROPY.requestWithCallback{value: pythOracleFee}(pythProvider, randomnessRequestSeed);
gameToStart.pythSequenceNumber = entropySequenceNumber;
sequenceNumberToGameId[entropySequenceNumber] = _gameId;
gameToStart.currentState = GameState.WaitingForRandomness;
gameToStart.phaseStartTime = block.timestamp;
emit GameStartRequested(_gameId, msg.sender, entropySequenceNumber);
}
/**
* @notice Claim your reward after the game ends.
* @dev Allowed by any player on the winning team.
* @param _gameId The ID of the finished game.
*/
function claimReward(uint256 _gameId)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Finished)
playerExists(_gameId, msg.sender)
{
Player storage claimingPlayer = gamePlayers[_gameId][msg.sender];
if (claimingPlayer.claimedPayout) { revert AlreadyClaimed(_gameId, msg.sender); }
GameSettings storage finishedGame = games[_gameId];
(uint256 payoutAmountDue, ) = PayoutLogic.calculatePayoutInLibrary(
_gameId,
msg.sender,
finishedGame,
claimingPlayer,
playerList[_gameId],
winners[_gameId],
payoutPerWinnerTownfolk[_gameId]
);
if (payoutAmountDue == 0) { revert PayoutRetryInvalid(_gameId, msg.sender); }
claimingPlayer.claimedPayout = true;
if (payoutAmountDue <= _totalPlayerFundsLocked) {
_totalPlayerFundsLocked -= payoutAmountDue;
} else {
_totalPlayerFundsLocked = 0;
}
safeNativeTransfer(payable(msg.sender), payoutAmountDue);
emit PayoutClaimed(_gameId, msg.sender, payoutAmountDue);
}
/**
* @notice Allows the game host to cancel the game in any active state (Lobby, WaitingForRandomness, Night, Day, Revote) after Platform Fee
* @param _gameId The ID of the game to be cancelled by its host.
*/
function hostCancelGame(uint256 _gameId)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
onlyGameHost(_gameId) // Ensures only the host can call
{
GameSettings storage gameToCancel = games[_gameId];
// Prevent cancellation if already finished or cancelled
if (gameToCancel.currentState == GameState.Finished || gameToCancel.currentState == GameState.Cancelled) {
revert GameIsFinished(_gameId);
}
uint256 initialPot = gameToCancel.totalPot;
uint256 feeAmount = 0;
if (gameToCancel.platformFeeBasisPoints > 0 && townHallWallet != address(0)) {
feeAmount = (initialPot * gameToCancel.platformFeeBasisPoints) / 10000;
if (feeAmount > 0 && feeAmount <= initialPot) { // Ensure fee is not more than pot
safeNativeTransfer(payable(townHallWallet), feeAmount);
} else {
feeAmount = 0; // Cannot pay fee if it's zero or exceeds pot
}
}
uint256 netPotForRefunds = initialPot - feeAmount;
// Decrement global locked funds by the initial pot value as it's now being distributed or paid as fee
if (initialPot <= _totalPlayerFundsLocked) {
_totalPlayerFundsLocked -= initialPot;
} else {
_totalPlayerFundsLocked = 0; // Should not happen if accounting is correct
}
address[] storage playersToRefundList = playerList[_gameId];
uint256 totalBuyInsForGame = gameToCancel.gameBuyIn * playersToRefundList.length;
if (totalBuyInsForGame > 0) { // Avoid division by zero if no buy-ins (e.g. empty lobby somehow)
for (uint i = 0; i < playersToRefundList.length; i++) {
address playerAddressToRefund = playersToRefundList[i];
Player storage playerRecord = gamePlayers[_gameId][playerAddressToRefund];
// Calculate proportional refund from the net pot
uint256 actualRefundAmount = (gameToCancel.gameBuyIn * netPotForRefunds) / totalBuyInsForGame;
if (actualRefundAmount > 0) {
safeNativeTransfer(payable(playerAddressToRefund), actualRefundAmount);
emit PlayerRefunded(_gameId, playerAddressToRefund, actualRefundAmount);
}
playerRecord.claimedPayout = true; // Mark as "claimed" to prevent other claims
}
}
gameToCancel.currentState = GameState.Cancelled;
gameToCancel.totalPot = 0;
// Consider a new event like HostGameCancelled or add a parameter to GameCancelled
emit GameCancelled(_gameId, msg.sender, "Cancelled by host; fee deducted, refunds processed.");
}
// =================================================================================================
// External Write Functions: Admin, Owner, and Validator Controls
// =================================================================================================
/**
* @notice Set the trusted backend address.
* @dev Allowed by owner only.
* @param _newBackendAddress The new wallet address designated as the trusted backend.
*/
function adminSetBackend(address _newBackendAddress) external onlyOwner {
if (_newBackendAddress == address(0)) revert ZeroAddress();
_adminSetAddresses(_newBackendAddress, address(0), address(0), address(0));
}
/**
* @notice Sets the validator's wallet address.
* @dev Allowed by owner only.
* @param _newValidatorAddress The new wallet address designated as the validator.
*/
function adminSetValidatorAddress(address _newValidatorAddress) external onlyOwner {
if (_newValidatorAddress == address(0)) revert ZeroAddress();
_adminSetAddresses(address(0), _newValidatorAddress, address(0), address(0));
}
/**
* @notice Sets the designated treasury wallet for platform fees.
* @dev Allowed by owner only.
* @param _newTownHallWallet The new wallet address for collecting platform fees.
*/
function adminSetTownHallWallet(address _newTownHallWallet) external onlyOwner {
if (_newTownHallWallet == address(0)) revert ZeroAddress();
_adminSetAddresses(address(0), address(0), address(0), _newTownHallWallet);
}
/**
* @notice Set the first NFT collection to be eligible for a boost.
* @dev Allowed by owner only.
* @param _collectionAddress The ERC721 contract address of the first eligible NFT collection.
*/
function adminSetBoostCollection1(address _collectionAddress) external onlyOwner {
_boostedCollection1 = _collectionAddress;
emit NftCollectionSet(1, _collectionAddress);
}
/**
* @notice Set the second NFT collection that is boost eligible.
* @dev Allowed by owner only.
* @param _collectionAddress The ERC721 contract address of the second eligible NFT collection.
*/
function adminSetBoostCollection2(address _collectionAddress) external onlyOwner {
_boostedCollection2 = _collectionAddress;
emit NftCollectionSet(2, _collectionAddress);
}
/**
* @notice Set the boost percentage per NFT owned.
* @dev Allowed by owner only.
* @param nftCounts_ Array of NFT count thresholds for tiers (e.g., [1, 5, 10] meaning tiers for 1, 2-5, 6-10 NFTs).
* @param boostBps_ Array of BPS values for tiers (e.g., [300, 1000, 2000, 3500], corresponding to the tiers defined by `nftCounts_` and one additional for counts exceeding the last threshold). Length must be `nftCounts_.length + 1`.
*/
function adminSetBoost(uint8[] calldata nftCounts_, uint256[] calldata boostBps_) external onlyOwner {
if (boostBps_.length != nftCounts_.length + 1) { // e.g., counts [1,5,10] (3 elements) needs BPS [for 0, for 1, for 2-5, for 6-10, for 11+] (5 elements) - wait, this is wrong.
// If counts is [1,5,10], BPS should be [BPS for 1, BPS for 2-5, BPS for 6-10, BPS for >10]. So length is counts.length + 1.
// And a BPS for 0 NFTs. So, if counts is [C1, C2, C3], BPS is [BPS_0, BPS_C1, BPS_C2_to_C1, BPS_C3_to_C2, BPS_GT_C3]
// Let's simplify: counts are upper bounds of previous tier. BPS[0] is for count 0. BPS[i] is for counts up to counts[i-1]. BPS[last] is for > counts[last-1]
// Example: counts [1, 5, 10]. BPS [BPS_0, BPS_1, BPS_2_5, BPS_6_10, BPS_GT_10] - length 5. counts length 3. boostBps_.length == nftCounts_.length + 2? No.
// Let's stick to: counts are thresholds. BPS[i] is for count <= counts[i]. BPS[last] is for > counts[last-1].
// If counts = [1, 5, 10], then BPS = [BPS_for_1, BPS_for_2_to_5, BPS_for_6_to_10, BPS_for_GT_10]. Length is counts.length + 1.
revert InvalidBoostTierConfiguration(); // Corrected length check
}
if (nftCounts_.length > 0) { // Only check sorting if there are counts
for (uint i = 0; i < nftCounts_.length; i++) {
if (nftCounts_[i] == 0) revert InvalidBoostTierConfiguration(); // Counts must be > 0
if (i > 0 && nftCounts_[i] <= nftCounts_[i-1]) revert InvalidBoostTierConfiguration(); // Must be sorted and strictly increasing
}
}
for (uint i = 0; i < boostBps_.length; i++) {
if (boostBps_[i] > BASIS_POINTS * 10) revert InvalidBoostTierConfiguration(); // Arbitrary sanity check: boost shouldn't be > 1000%
}
_nftBoostTiersCounts = nftCounts_;
_nftBoostTiersBps = boostBps_;
emit NftBoostTiersSet(nftCounts_, boostBps_);
}
/**
* @notice Set Night, Day,Lobby, Randomness, and Revote durations.
* @dev Allowed by owner only.
* @param lobby_ New duration for lobby timeout in seconds.
* @param randomness_ New duration for Pyth randomness timeout in seconds.
* @param night_ The new duration in seconds for the Night phase.
* @param day_ The new duration in seconds for the Day phase.
* @param revote_ The new duration in seconds for the Revote phase.
*/
function adminSetDurations(uint256 lobby_, uint256 randomness_, uint256 night_, uint256 day_, uint256 revote_) external onlyOwner {
if (night_ < 60 || day_ < 60 || revote_ < 30 || lobby_ < 300 || randomness_ < 300) revert DurationTooShort(); // Min 5 mins for timeouts
_lobbyTimeoutDuration = lobby_;
_randomnessTimeoutDefault = randomness_;
_nightDuration = night_;
_dayDuration = day_;
_revoteDuration = revote_;
emit GameDurationsSet(lobby_, randomness_, night_, day_, revote_);
}
/**
* @notice Eliminate a player from an ongoing game.
* @dev Allowed by Owner, Backend or Validator. Checks win conditions.
* @param _gameId The ID of the game from which to eliminate the player.
* @param _playerAddress The address of the player to be eliminated.
*/
function adminKill(uint256 _gameId, address _playerAddress)
external
onlyWhenGameExists(_gameId)
playerExists(_gameId, _playerAddress)
onlyAuthorizedOps
notFinished(_gameId)
{
if (!gamePlayers[_gameId][_playerAddress].isAlive) revert PlayerNotAlive(_gameId, _playerAddress);
gamePlayers[_gameId][_playerAddress].isAlive = false;
emit PlayerInactiveEliminated(_gameId, games[_gameId].currentRound, _playerAddress);
checkWinConditions(_gameId);
}
/**
* @notice Cancel a game stuck waiting for randomness and refund all players.
* @dev Allowed by owner or backend only.
* @param _gameId The ID of the game stuck in the `WaitingForRandomness` state.
*/
function backendCancelGame(uint256 _gameId)
external
onlyOwner
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.WaitingForRandomness)
{
GameSettings storage gameToResolve = games[_gameId];
if (block.timestamp < gameToResolve.phaseStartTime + _randomnessTimeoutDefault) revert TimeoutNotReached();
adminCancelAndRefund(_gameId, "Cancelled by admin: Pyth Network randomness request timed out.");
}
/**
* @notice Manually retry to reward a winning player in a finished game.
* @dev Allowed by Owner, Backend or Validator.
* @param _gameId The ID of the finished game.
* @param _playerAddress The address of the player for whom to retry the payout/refund.
*/
function adminRetryReward(uint256 _gameId, address _playerAddress)
external
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Finished)
playerExists(_gameId, _playerAddress)
onlyAuthorizedOps
{
Player storage targetPlayer = gamePlayers[_gameId][_playerAddress];
if (targetPlayer.claimedPayout) { revert PayoutRetryInvalid(_gameId, _playerAddress); }
GameSettings storage finishedGame = games[_gameId];
(uint256 payoutAmountDue, ) = PayoutLogic.calculatePayoutInLibrary(
_gameId,
_playerAddress,
finishedGame,
targetPlayer,
playerList[_gameId],
winners[_gameId],
payoutPerWinnerTownfolk[_gameId]
);
if (payoutAmountDue == 0) { revert PayoutRetryInvalid(_gameId, _playerAddress); }
if (payoutAmountDue <= _totalPlayerFundsLocked) {
_totalPlayerFundsLocked -= payoutAmountDue;
} else {
_totalPlayerFundsLocked = 0;
}
safeNativeTransfer(payable(_playerAddress), payoutAmountDue);
targetPlayer.claimedPayout = true;
emit PayoutRetrySucceeded(_gameId, _playerAddress, payoutAmountDue);
}
/**
* @notice Set the Pyth entropy provider address.
* @dev Allowed by owner only.
* @param _providerAddress The new address for the Pyth provider.
*/
function adminSetPythProvider(address _providerAddress) external onlyOwner {
if (_providerAddress == address(0)) revert ZeroAddress();
_adminSetAddresses(address(0), address(0), _providerAddress, address(0));
}
/**
* @notice Set the platform fee in "basis points".
* @dev Allowed by owner only.
* @param _newFeeBps The new default platform fee percentage in basis points.
*/
function adminSetFee(uint256 _newFeeBps) external onlyOwner {
if (_newFeeBps > MAX_FEE) revert FeeTooHigh(_newFeeBps, MAX_FEE);
_feeBps = _newFeeBps;
emit PlatformFeeSet(_newFeeBps);
}
/**
* @notice Pause the entire contract.
* @dev Allowed by owner only.
*/
function adminPause() external onlyOwner whenNotPaused {
_pause();
emit GamePaused(msg.sender);
}
/**
* @notice Unpause the contract.
* @dev Allowed by owner only.
*/
function adminUnpause() external onlyOwner whenPaused {
_unpause();
emit GameUnpaused(msg.sender);
}
/**
* @notice Cancel any game at any stage.
* @dev Allowed by owner only.
* @param _gameId The ID of the game to be cancelled.
*/
function adminCancelGame(uint256 _gameId)
external
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Lobby)
{
bool isHost = (msg.sender == games[_gameId].host);
bool isOwner = (msg.sender == owner());
bool isBackend = (msg.sender == _backendAddress);
bool isValidator = (msg.sender == _validatorAddress);
if (!(isHost || isOwner || isBackend || isValidator)) revert NotAuthorized();
adminCancelAndRefund(_gameId, isHost ? "Cancelled by host while in Lobby state." : "Cancelled by admin/backend/validator while in Lobby state.");
}
/**
* @notice Force the game into either Night or Day (or other states).
* @dev Allowed by owner only.
* @param _gameId The ID of the game to modify.
* @param _newState The target `GameState` to transition the game into.
*/
function adminSetPhase(uint256 _gameId, GameState _newState) external onlyOwner onlyWhenGameExists(_gameId) {
GameSettings storage gameToForce = games[_gameId];
GameState oldState = gameToForce.currentState;
if (_newState == GameState.Lobby) {
require(oldState == GameState.Lobby || oldState == GameState.Cancelled, "AdminSetPhase: Cannot forcibly revert an active or finished game back to Lobby state.");
}
if (_newState == GameState.WaitingForRandomness) {
require(oldState == GameState.Lobby, "AdminSetPhase: Can only forcibly transition to WaitingForRandomness state from Lobby state.");
}
if (_newState == GameState.Finished && gameToForce.winningTeam == Role.Unassigned) {
revert("AdminSetPhase: Winning team must be determined before game can be forcibly set to Finished state.");
}
gameToForce.currentState = _newState;
if (_newState == GameState.Night || _newState == GameState.Day || _newState == GameState.Revote) {
gameToForce.phaseStartTime = block.timestamp;
if (_newState == GameState.Night && gameToForce.currentRound == 0) {
gameToForce.currentRound = 1;
}
}
emit AdminForcedState(_gameId, oldState, _newState, msg.sender);
}
// /**
// * @notice Throw this game in the dumpster and "soft rug" everyone (renounce ownership).
// * @dev Allowed by owner only.
// */
// function adminGiveUp() external onlyOwner {
// renounceOwnership();
// }
// /**
// * @notice Give the whole project away to someone else (transfer ownership).
// * @dev Allowed by owner only.
// * @param _newOwner The address of the new owner who will gain administrative control.
// */
// function adminNewOwner(address _newOwner) external onlyOwner {
// transferOwnership(_newOwner);
// }
// =================================================================================================
// External Write Functions: Restricted Phase Processing (Called by Backend/Owner)
// =================================================================================================
/**
* @notice Process witch's vote to determine which player is cursed.
* @param _gameId The ID of the game.
* @param _targetAddress The address of the player targeted.
*/
function voteAndCurse(uint256 _gameId, address _targetAddress)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Night)
onlyBackendOrOwner
{
processNightInternal(_gameId, _targetAddress);
}
/**
* @notice Process daytime votes to determine if a suspect is burned or if a revote is needed.
* @param _gameId The ID of the game.
* @param _voters Array of addresses of players who submitted a valid vote/abstain action to the backend.
* @param _votedFor Parallel array indicating the address each voter in `_voters` voted for. `address(0)` signifies an abstain vote or no valid vote from a player.
*/
function voteAndBurn(uint256 _gameId, address[] memory _voters, address[] memory _votedFor)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Day)
onlyBackendOrOwner
{
processDayInternal(_gameId, _voters, _votedFor);
}
/**
* @notice Process revote results and burn the losing player.
* @param _gameId The ID of the game.
* @param _voters An array of addresses of players who submitted a valid revote action to the backend.
* @param _votedFor A parallel array indicating the address each voter in `_voters` voted for. The target must be one of the `playersInRevote` list.
*/
function voteAgain(uint256 _gameId, address[] memory _voters, address[] memory _votedFor)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Revote)
onlyBackendOrOwner
{
processRevoteInternal(_gameId, _voters, _votedFor);
}
// =================================================================================================
// External Write Functions: Public Fallbacks for Liveness
// =================================================================================================
/**
* @notice Force a Night/Day transition, if for some reason it failed
* @dev Allowed by any player.
* @param _gameId The ID of the potentially stalled game.
*/
function checkTime(uint256 _gameId)
external
whenNotPaused
onlyWhenGameExists(_gameId)
notFinished(_gameId)
{
GameSettings storage gameToAdvance = games[_gameId];
GameState currentPhase = gameToAdvance.currentState;
uint256 phaseStartTime = gameToAdvance.phaseStartTime;
uint256 phaseConfiguredDuration;
if (currentPhase == GameState.Night) { phaseConfiguredDuration = _nightDuration; }
else if (currentPhase == GameState.Day) { phaseConfiguredDuration = _dayDuration; }
else if (currentPhase == GameState.Revote) { phaseConfiguredDuration = _revoteDuration; }
else { revert WrongGameStateForAction(_gameId, currentPhase); }
uint256 backendActionDeadline = phaseStartTime + phaseConfiguredDuration + GRACE_DURATION;
if (block.timestamp < backendActionDeadline) { revert TimeoutNotReached(); }
emit PhaseForcedByTimeout(_gameId, currentPhase, gameToAdvance.currentRound, "Public timeout fallback invoked by user.");
if (currentPhase == GameState.Day || currentPhase == GameState.Revote) {
if (currentPhase == GameState.Revote) { delete playersInRevote[_gameId]; }
if (checkWinConditions(_gameId)) { return; }
transitionToNight(_gameId);
}
else if (currentPhase == GameState.Night) {
if (checkWinConditions(_gameId)) { return; }
gameToAdvance.currentState = GameState.Day;
gameToAdvance.phaseStartTime = block.timestamp;
emit DayPhaseStarted(_gameId, gameToAdvance.currentRound, address(0));
}
}
/**
* @notice Cancels unstarted game if Lobby phase times out. Refunds players.
* @dev Allowed by any player.
* @param _gameId The ID of the game stuck in the Lobby state.
*/
function triggerLobbyTimeout(uint256 _gameId)
external
whenNotPaused
nonReentrant
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Lobby)
{
GameSettings storage gameToCancel = games[_gameId];
if (block.timestamp < gameToCancel.createdAt + _lobbyTimeoutDuration) revert TimeoutNotReached();
adminCancelAndRefund(_gameId, "Cancelled due to Lobby phase timeout (public call).");
}
// =================================================================================================
// Internal Functions (Helper Logic - Renamed)
// =================================================================================================
/**
* @dev Internal core logic for processing Witch's night action. Helper for voteAndCurse.
* @param _gameId The ID of the game.
* @param _targetAddress The address targeted by the Witch.
*/
function processNightInternal(uint256 _gameId, address _targetAddress) internal {
GameSettings storage currentGame = games[_gameId];
uint256 currentRoundNumber = currentGame.currentRound;
if (_targetAddress != address(0) &&
gamePlayers[_gameId][_targetAddress].playerAddress != address(0) &&
gamePlayers[_gameId][_targetAddress].isAlive) {
gamePlayers[_gameId][_targetAddress].isAlive = false;
emit PlayerCursed(_gameId, currentRoundNumber, _targetAddress);
}
uint aliveTownfolkCount = 0;
uint aliveWitchesCount = 0;
address lastLivingTownfolk = address(0);
address[] storage currentPlayersInGame = playerList[_gameId];
for (uint i = 0; i < currentPlayersInGame.length; i++) {
Player storage p = gamePlayers[_gameId][currentPlayersInGame[i]];
if (p.isAlive) {
if (p.role == Role.Witch) { aliveWitchesCount++; }
else if (p.role == Role.Townfolk) { aliveTownfolkCount++; lastLivingTownfolk = currentPlayersInGame[i]; }
}
}
if (_targetAddress != address(0) &&
gamePlayers[_gameId][_targetAddress].playerAddress != address(0) &&
!gamePlayers[_gameId][_targetAddress].isAlive &&
gamePlayers[_gameId][_targetAddress].role == Role.Townfolk &&
aliveWitchesCount == WITCH_COUNT &&
aliveTownfolkCount == 1 &&
lastLivingTownfolk != address(0) && // Ensure a last one exists
lastLivingTownfolk != _targetAddress) { // Ensure the one killed wasn't the last one already (though redundant due to aliveTownfolkCount == 1 logic)
gamePlayers[_gameId][lastLivingTownfolk].isAlive = false;
emit WitchKilledLastTownfolk(_gameId, currentRoundNumber, lastLivingTownfolk);
}
if (checkWinConditions(_gameId)) { return; }
currentGame.currentState = GameState.Day;
currentGame.phaseStartTime = block.timestamp;
emit DayPhaseStarted(_gameId, currentRoundNumber, _targetAddress);
}
/**
* @dev Internal core logic for processing Day phase vote results. Helper for voteAndBurn.
* @param _gameId The ID of the game.
* @param _voters Array of voters provided by the backend.
* @param _votedFor Parallel array of targets voted for by the backend.
*/
function processDayInternal(uint256 _gameId, address[] memory _voters, address[] memory _votedFor) internal {
if (_voters.length != _votedFor.length) revert("Internal: Vote array length mismatch.");
GameSettings storage currentGame = games[_gameId];
uint256 currentRoundNumber = currentGame.currentRound;
VoteLogic.VoteOutcome memory outcome = VoteLogic.tallyVotesAndDetermineOutcomeInLibrary(
_voters,
_votedFor,
playerList[_gameId],
gamePlayers[_gameId]
);
if (outcome.tieCount == 1 && outcome.playerToBurn != address(0) && outcome.maxVoteCount > 0) {
gamePlayers[_gameId][outcome.playerToBurn].isAlive = false;
emit PlayerBurned(_gameId, currentRoundNumber, outcome.playerToBurn, outcome.maxVoteCount);
if (checkWinConditions(_gameId)) { return; }
transitionToNight(_gameId);
} else if (outcome.tieCount > 1 && outcome.maxVoteCount > 0) {
// outcome.tiedPlayers is already correctly sized
emit VoteTied(_gameId, currentRoundNumber, outcome.tiedPlayers);
delete playersInRevote[_gameId];
address[] storage revoteParticipants = playersInRevote[_gameId];
uint validPlayersForRevoteRound = 0;
for (uint i = 0; i < outcome.tiedPlayers.length; i++) {
// Ensure players added to revote are still alive (though tallyVotes should only consider alive)
if (gamePlayers[_gameId][outcome.tiedPlayers[i]].isAlive) {
revoteParticipants.push(outcome.tiedPlayers[i]);
validPlayersForRevoteRound++;
}
}
if (validPlayersForRevoteRound < 2) {
emit RevoteTied(_gameId, currentRoundNumber);
delete playersInRevote[_gameId]; // Clear again if revote is not possible
if (checkWinConditions(_gameId)) { return; }
transitionToNight(_gameId);
} else {
currentGame.currentState = GameState.Revote;
currentGame.phaseStartTime = block.timestamp;
// Ensure revoteParticipants is correctly passed if it was re-filtered
address[] memory finalRevoteParticipants = new address[](validPlayersForRevoteRound);
for(uint i = 0; i < validPlayersForRevoteRound; i++){
finalRevoteParticipants[i] = revoteParticipants[i];
}
emit RevotePhaseStarted(_gameId, currentRoundNumber, finalRevoteParticipants);
}
} else { // No one burned, no tie leading to revote (e.g. all abstain or no valid votes)
emit VoteTied(_gameId, currentRoundNumber, new address[](0));
if (checkWinConditions(_gameId)) { return; }
transitionToNight(_gameId);
}
}
/**
* @dev Internal core logic for processing Revote phase results. Helper for voteAgain.
* @param _gameId The ID of the game.
* @param _voters Array of voters from backend.
* @param _votedFor Parallel array of targets from backend (must be among revote candidates).
*/
function processRevoteInternal(uint256 _gameId, address[] memory _voters, address[] memory _votedFor) internal {
if (_voters.length != _votedFor.length) revert("Internal: Input array length mismatch.");
GameSettings storage currentGame = games[_gameId];
uint256 currentRoundNumber = currentGame.currentRound;
// Note: The VoteLogic library expects the full playerList for context,
// but the actual voting targets should be implicitly filtered by the backend to only include playersInRevote.
// The library itself will check if targets are alive and in the playerList.
// The `playersInRevote[_gameId]` list is primarily for knowing WHO can be voted for.
// The backend should ensure `_votedFor` targets are from this list.
// `tallyVotesAndDetermineOutcomeInLibrary` will use `playerList[_gameId]` to map votes.
if (playersInRevote[_gameId].length == 0) { // Should not happen if revote phase started correctly
delete playersInRevote[_gameId]; // Ensure cleanup
if (checkWinConditions(_gameId)) { return; }
transitionToNight(_gameId);
return;
}
VoteLogic.VoteOutcome memory outcome = VoteLogic.tallyVotesAndDetermineOutcomeInLibrary(
_voters,
_votedFor,
playerList[_gameId], // Full player list for indexing votes
gamePlayers[_gameId]
);
// After tallying, we must ensure the playerToBurn (if any) was actually part of the revote candidates.
// This is an important validation step if the library is generic.
bool isValidRevoteTargetBurned = false;
if (outcome.playerToBurn != address(0)) {
for (uint i = 0; i < playersInRevote[_gameId].length; i++) {
if (playersInRevote[_gameId][i] == outcome.playerToBurn) {
isValidRevoteTargetBurned = true;
break;
}
}
}
if (outcome.tieCount == 1 && outcome.playerToBurn != address(0) && outcome.maxVoteCount > 0 && isValidRevoteTargetBurned) {
gamePlayers[_gameId][outcome.playerToBurn].isAlive = false;
emit PlayerBurnedAfterRevote(_gameId, currentRoundNumber, outcome.playerToBurn, outcome.maxVoteCount);
} else {
// This includes cases where:
// - No one got votes (maxVoteCount == 0)
// - Tie was > 1 (outcome.tieCount > 1)
// - The playerToBurn was not a valid revote candidate (isValidRevoteTargetBurned == false)
emit RevoteTied(_gameId, currentRoundNumber);
}
delete playersInRevote[_gameId];
if (checkWinConditions(_gameId)) { return; }
transitionToNight(_gameId);
}
/**
* @dev Internal: Cancels game and refunds all players their buy-in. Helper for admin cancellation functions.
* @param _gameId The ID of the game to cancel.
* @param _reason A string describing the reason for cancellation, logged in the `GameCancelled` event.
*/
function adminCancelAndRefund(uint256 _gameId, string memory _reason) internal {
GameSettings storage gameToCancel = games[_gameId];
address[] storage playersToRefund = playerList[_gameId];
uint256 totalPotToRefund = gameToCancel.totalPot;
if (totalPotToRefund > _totalPlayerFundsLocked) {
_totalPlayerFundsLocked = 0;
} else {
_totalPlayerFundsLocked -= totalPotToRefund;
}
if (totalPotToRefund > 0 && playersToRefund.length > 0) {
if (address(this).balance < totalPotToRefund) revert InsufficientContractBalance();
uint256 refundAmountPerPlayer = gameToCancel.gameBuyIn;
if (refundAmountPerPlayer > 0) {
for (uint i = 0; i < playersToRefund.length; i++) {
address payable playerAddressToRefund = payable(playersToRefund[i]);
if(gamePlayers[_gameId][playerAddressToRefund].playerAddress != address(0)) {
gamePlayers[_gameId][playerAddressToRefund].claimedPayout = true;
}
safeNativeTransfer(playerAddressToRefund, refundAmountPerPlayer);
emit PlayerRefunded(_gameId, playerAddressToRefund, refundAmountPerPlayer);
}
}
}
gameToCancel.currentState = GameState.Cancelled;
gameToCancel.totalPot = 0;
emit GameCancelled(_gameId, msg.sender, _reason);
}
/**
* @dev Internal: Assigns Witch role using weighted randomness based on cached NFT boost. Core logic for role assignment.
* @param _gameId The ID of the game for which roles are being assigned.
* @param _randomNumber The secure random number obtained from the Pyth Network oracle.
*/
// function assignRolesWeighted(uint256 _gameId, uint256 _randomNumber) internal { ... } // Moved to RoleLogic.sol
// function calculateTieredNftBoost(address _playerAddress) internal view returns (uint256 calculatedBoostBps) { ... } // Moved to RoleLogic.sol
/**
* @dev Internal: Checks win conditions finalizes game transfers fees prepares payout data. Core game end logic.
* @param _gameId The ID of the game to check for win conditions.
* @return isGameOver True if the game has ended as a result of this check, false otherwise.
*/
function checkWinConditions(uint256 _gameId) internal returns (bool isGameOver) {
uint aliveTownfolkCount = 0;
uint aliveWitchesCount = 0;
address[] storage currentPlayersInGame = playerList[_gameId];
uint totalPlayerCountInList = currentPlayersInGame.length;
for (uint i = 0; i < totalPlayerCountInList; i++) {
Player storage p = gamePlayers[_gameId][currentPlayersInGame[i]];
if (p.isAlive) {
if (p.role == Role.Witch) { aliveWitchesCount++; }
else if (p.role == Role.Townfolk) { aliveTownfolkCount++; }
}
}
Role gameWinningTeam = Role.Unassigned;
if (aliveTownfolkCount == 0 && aliveWitchesCount >= WITCH_COUNT) {
gameWinningTeam = Role.Witch;
isGameOver = true;
}
else if (aliveWitchesCount == 0 && aliveTownfolkCount > 0) {
gameWinningTeam = Role.Townfolk;
isGameOver = true;
}
if (isGameOver) {
GameSettings storage finishedGame = games[_gameId];
// uint256 initialTotalPotForPayoutCalcs = finishedGame.totalPot; // This local var is no longer used directly here
finishedGame.currentState = GameState.Finished;
finishedGame.winningTeam = gameWinningTeam;
delete winners[_gameId];
// finishedGame.deadTownfolkRefundPool = 0; // Will be set by PayoutLogic calculations if needed
address[] storage gameWinnersListStorage = winners[_gameId];
uint actualWinnerCount = 0;
// Call PayoutLogic to get fee and refund pool calculations
PayoutLogic.FeeAndRefundCalculationsOutput memory payoutCalcs = PayoutLogic.calculateFeesAndRefundPoolInLibrary(
finishedGame,
currentPlayersInGame, // playerList[_gameId]
gamePlayers[_gameId]
);
uint256 platformFeeAmount = payoutCalcs.platformFeeAmount;
uint256 potRemainingAfterFee = payoutCalcs.potRemainingAfterFee;
finishedGame.deadTownfolkRefundPool = payoutCalcs.totalAllocatedForDeadTownfolkRefunds; // Set based on library calculation
if (platformFeeAmount > 0) {
address currentTownHallWallet = townHallWallet;
if (currentTownHallWallet == address(0)) { revert TownHallWalletNotSet(); }
if (platformFeeAmount <= _totalPlayerFundsLocked) {
_totalPlayerFundsLocked -= platformFeeAmount;
} else {
_totalPlayerFundsLocked = 0;
}
safeNativeTransfer(payable(currentTownHallWallet), platformFeeAmount);
}
uint256 potAvailableForPlayerDistribution = potRemainingAfterFee;
// Adjust potAvailableForPlayerDistribution based on the calculated deadTownfolkRefundPool
if (finishedGame.deadTownfolkRefundPool <= potAvailableForPlayerDistribution) {
potAvailableForPlayerDistribution -= finishedGame.deadTownfolkRefundPool;
} else {
// This case implies refunds exceed pot after fee, should ideally not happen with correct pot management
potAvailableForPlayerDistribution = 0;
}
for (uint i = 0; i < totalPlayerCountInList; i++) {
Player storage p = gamePlayers[_gameId][currentPlayersInGame[i]];
if (p.isAlive && p.role == gameWinningTeam) {
gameWinnersListStorage.push(currentPlayersInGame[i]);
actualWinnerCount++;
}
}
uint256 payoutAmountPerLivingWinner = 0;
if (actualWinnerCount > 0) {
payoutAmountPerLivingWinner = potAvailableForPlayerDistribution / actualWinnerCount;
}
if (gameWinningTeam == Role.Townfolk) {
payoutPerWinnerTownfolk[_gameId] = payoutAmountPerLivingWinner;
}
// Update game's totalPot to reflect amount after fee and distributed to players (or available for claims)
// It would be sum of (payoutAmountPerLivingWinner * actualWinnerCount) + finishedGame.deadTownfolkRefundPool
// This line might not be necessary if totalPot is implicitly handled by _totalPlayerFundsLocked decrements
// finishedGame.totalPot = (payoutAmountPerLivingWinner * actualWinnerCount) + finishedGame.deadTownfolkRefundPool;
emit GameFinished(_gameId, gameWinningTeam, gameWinnersListStorage, payoutAmountPerLivingWinner);
}
return isGameOver;
}
/**
* @dev Internal: Transitions game to the next Night phase. Helper for phase management.
* @param _gameId The ID of the game to transition.
*/
function transitionToNight(uint256 _gameId) internal {
GameSettings storage gameToTransition = games[_gameId];
gameToTransition.currentState = GameState.Night;
gameToTransition.currentRound++;
gameToTransition.phaseStartTime = block.timestamp;
emit NightPhaseStarted(_gameId, gameToTransition.currentRound);
}
/**
* @dev Internal: Safely transfers native $APE with balance check and revert on failure.
* @param _to The payable address of the recipient.
* @param _amount The amount of native $APE (in Wei) to send.
*/
function safeNativeTransfer(address payable _to, uint256 _amount) internal {
if (_amount == 0) { return; }
if (address(this).balance < _amount) revert InsufficientContractBalance();
(bool success, ) = _to.call{value: _amount}("");
if (!success) revert NativeTransferFailed();
}
/**
* @dev Internal: Calculates payout/refund for a player in a finished game. Core logic for determining player rewards.
* @param _gameId Game ID.
* @param _playerAddress Address of the player.
* @param game A storage reference to the game's settings.
* @param player A storage reference to the player's data.
* @return playerPayoutAmount The amount of native $APE (in Wei) due to the player.
* @return platformFeeAmountForInfo The total platform fee (in Wei) collected from this specific game's prize pool.
*/
// function calculatePayoutInternal(...) is now moved to PayoutLogic.sol
// =================================================================================================
// Pyth Network Required Functions
// =================================================================================================
/**
* @notice Returns the configured Pyth Entropy contract address.
* @dev This function is a requirement of the `IEntropyConsumer` interface of the Pyth SDK integration.
* @return The deployed blockchain address of the Pyth Entropy oracle.
*/
function getEntropy() internal view override returns (address) {
return address(PYTH_ENTROPY);
}
/**
* @notice This is the callback function invoked by the Pyth Network Entropy oracle to deliver a secure random number.
* @dev An internal override called automatically by the Pyth Entropy contract. Assigns roles to players and transitions the game to the first Night phase.
* @param _sequenceNumber The unique ID of the randomness request that this callback corresponds to.
* @param _randomNumber The cryptographically secure random number (as a `bytes32` hash) delivered by the Pyth Network oracle.
*/
function entropyCallback(uint64 _sequenceNumber, address /*_unusedProviderParam*/, bytes32 _randomNumber)
internal
override
nonReentrant
whenNotPaused
{
if (msg.sender != address(PYTH_ENTROPY)) { revert InvalidCallbackOrigin(address(PYTH_ENTROPY), msg.sender); }
uint256 gameIdForCallback = sequenceNumberToGameId[_sequenceNumber];
if (gameIdForCallback >= _nextGameId && _nextGameId > 0) { return; }
GameSettings storage gameForRoles = games[gameIdForCallback];
if (gameForRoles.host == address(0)) { return; }
if (gameForRoles.pythSequenceNumber != _sequenceNumber) { revert InvalidSequenceNumber(_sequenceNumber); }
if (gameForRoles.currentState != GameState.WaitingForRandomness) {
return;
}
if (gameForRoles.isRandomnessFulfilled) { return; }
gameForRoles.isRandomnessFulfilled = true;
emit RandomnessReceived(gameIdForCallback, _sequenceNumber);
// Call the library function for assigning roles
RoleLogic.assignRolesWeightedInLibrary(
gamePlayers[gameIdForCallback],
playerList[gameIdForCallback],
uint256(_randomNumber)
);
delete sequenceNumberToGameId[_sequenceNumber];
gameForRoles.currentState = GameState.Night;
gameForRoles.currentRound = 1;
gameForRoles.phaseStartTime = block.timestamp;
emit RolesAssigned(gameIdForCallback);
emit NightPhaseStarted(gameIdForCallback, gameForRoles.currentRound);
}
// =================================================================================================
// View Functions (Publicly Accessible Data Retrievers)
// =================================================================================================
/**
* @notice Retrieve current lobby/game settings players and winners.
* @param _gameId The ID of the game to query.
* @return settings A `GameSettings` struct containing all configuration and current state data for the requested game.
* @return currentPlayers An array of addresses of all players who have joined this game.
* @return gameWinners An array of addresses of players who were declared winners. Empty if game not finished.
*/
function getGameSettings(uint256 _gameId)
external view
onlyWhenGameExists(_gameId)
returns (GameSettings memory settings, address[] memory currentPlayers, address[] memory gameWinners)
{
settings = games[_gameId];
currentPlayers = playerList[_gameId];
if (settings.currentState == GameState.Finished) {
gameWinners = winners[_gameId];
} else {
gameWinners = new address[](0);
}
}
/**
* @notice Retrieve information about an active player (address, status, reward, claim, NFT boost).
* @param _gameId The ID of the game in which to retrieve player details.
* @param _playerAddress The wallet address of the player whose details are requested.
* @return publicInfo A `PublicPlayerInfo` struct containing the public data for the specified player.
*/
function getPlayerInfo(uint256 _gameId, address _playerAddress)
external view
onlyWhenGameExists(_gameId)
playerExists(_gameId, _playerAddress)
returns (PublicPlayerInfo memory publicInfo)
{
Player storage p = gamePlayers[_gameId][_playerAddress];
publicInfo = PublicPlayerInfo({
playerAddress: p.playerAddress,
isAlive: p.isAlive,
claimedPayout: p.claimedPayout,
nftBoostBps: p.nftBoostBps
});
}
/**
* @notice Retrieve your role. 0 for unassigned 1 for townfolk 2 for witch.
* @param _gameId The ID of the game to check the role in.
* @return playerRole The calling player's role in the specified game.
*/
function getMyRole(uint256 _gameId)
external view
returns (Role playerRole)
{
if (games[_gameId].host == address(0)) { return Role.Unassigned; }
if (gamePlayers[_gameId][msg.sender].playerAddress != msg.sender) { return Role.Unassigned; }
GameSettings storage gameToCheck = games[_gameId];
if (!gameToCheck.isRandomnessFulfilled ||
gameToCheck.currentState == GameState.Lobby ||
gameToCheck.currentState == GameState.WaitingForRandomness) {
return Role.Unassigned;
}
return gamePlayers[_gameId][msg.sender].role;
}
/**
* @notice Retrieve the potential reward for a player if the game were to end right now.
* @dev Returns potential payout.
* @param _gameId The ID of the game, which must be in the `Finished` state.
* @param _playerAddress The address of the player whose potential reward is being checked.
* @return payoutAmount The amount of native $APE (in Wei) that the player is eligible to claim.
* @return platformFeeForGame The total platform fee (in Wei) collected from this game's prize pool.
*/
function getPotentialReward(uint256 _gameId, address _playerAddress)
external view
onlyWhenGameExists(_gameId)
inState(_gameId, GameState.Finished)
playerExists(_gameId, _playerAddress)
returns (uint256 payoutAmount, uint256 platformFeeForGame)
{
GameSettings storage finishedGame = games[_gameId];
Player storage targetPlayer = gamePlayers[_gameId][_playerAddress];
return PayoutLogic.calculatePayoutInLibrary(
_gameId,
_playerAddress,
finishedGame,
targetPlayer,
playerList[_gameId],
winners[_gameId],
payoutPerWinnerTownfolk[_gameId]
);
}
/**
* @notice Retrieve the contract addresses of any NFTcollections receiving a boost, increasing hodlers liklihood of being the Witch.
* @return collection1 The address of the first boosted NFT collection.
* @return collection2 The address of the second boosted NFT collection.
*/
function getBoostedCollections() external view returns (address collection1, address collection2) {
collection1 = _boostedCollection1;
collection2 = _boostedCollection2;
}
/**
* @notice This query returns big butts.
* @return A string literal representing the meme.
*/
function getBigButts() external pure returns (string memory) {
return unicode"(‿ˠ‿)";
}
/**
* @notice Retrieves the current NFT boost tier configuration.
* @return counts Array of NFT count thresholds.
* @return bps Array of boost BPS values for the tiers.
*/
function nftBoost() external view returns (uint8[] memory counts, uint256[] memory bps) {
return (_nftBoostTiersCounts, _nftBoostTiersBps);
}
// --- Custom Getters for Internalized State Variables ---
/**
* @notice The ID of the next game to be created.
* @return The numerical ID of the next game lobby.
*/
function nextGameID() external view returns (uint256) { return _nextGameId; }
/**
* @notice The currently set platform fee displayed as a percentage (in Basis Points).
* @return The current default platform fee in basis points.
*/
function fee() external view returns (uint256) { return _feeBps; }
/**
* @notice The current set Pyth Provider address.
* @return The address of the Pyth provider.
*/
function pythProviderAddress() external view returns (address) { return _pythProviderAddress; }
/**
* @notice This is the current backend address that controls off-chain mechanics.
* @dev Returns backend address.
* @return The address of the trusted backend.
*/
function backendAddress() external view returns (address) { return _backendAddress; }
/**
* @notice The address of the configured game validator.
* @return The address designated as the game validator.
*/
function getValidatorAddress() external view returns (address) { return _validatorAddress; }
/**
* @notice The designated wallet for platform fees.
* @return The address of the town hall (treasury) wallet.
*/
function getTownHallWallet() external view returns (address) { return townHallWallet; }
/**
* @notice Total player funds currently locked in active game pots or pending claims.
* @dev Returns total locked funds.
* @return The total amount of player funds locked in Wei
*/
function getPlayersFundsLocked() external view returns (uint256) { return _totalPlayerFundsLocked; }
/**
* @notice The address of the first NFT collection that receives a boost, Increasing their likelihood of being assigned the Witch role.
* @return The address of the first boosted NFT collection (0x0 if not set).
*/
function boostedCollection1() external view returns (address) { return _boostedCollection1; }
/**
* @notice The address of the second NFT collection that receives a boost, Increasing their likelihood of being assigned the Witch role.
* @return The address of the second boosted NFT collection (0x0 if not set).
*/
function boostedCollection2() external view returns (address) { return _boostedCollection2; }
/**
* @notice The set duration of each night phase.
* @return The duration of each Night phase in seconds.
*/
function nightDuration() external view returns (uint256) { return _nightDuration; }
/**
* @notice The duration of each Day phase displayed in seconds.
* @return The duration of each Day phase in seconds.
*/
function dayDuration() external view returns (uint256) { return _dayDuration; }
/**
* @notice The current set duration for each revote phase.
* @return The duration of each Revote phase in seconds.
*/
function revoteDuration() external view returns (uint256) { return _revoteDuration; }
/**
* @notice The Pyth Entropy Address
* @return The address of the Pyth Entropy oracle.
*/
function pythEntropyAddress() external view returns (address) { return address(PYTH_ENTROPY); }
/**
* @notice Retrieve all information about any game.
* @dev Allowed by all players.
* @param _gameId The unique ID of the game to query.
* @return settings A `GameSettings` struct containing the complete settings and current state for the requested game.
*/
function getGameInfo(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (GameSettings memory settings) { return games[_gameId]; }
/**
* @notice Retrieve a player's wallet/address for a certain game based on their ID.
* @param _gameId The ID of the game.
* @param _playerIndex The 0-based index of the player in the game's internal player list.
* @return The wallet address of the player at the specified index.
*/
function getPlayerAddress(uint256 _gameId, uint256 _playerIndex) external view onlyWhenGameExists(_gameId) returns (address) {
if (_playerIndex >= playerList[_gameId].length) revert("Index out of bounds for player list.");
return playerList[_gameId][_playerIndex];
}
/**
* @notice The Tied Wallets (players involved) during the Revote phase.
* @dev Returns tied players.
* @param _gameId The ID of the game.
* @return An array of addresses of players who are currently part of the revote. Empty if no revote active.
*/
function getTiedWallets(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (address[] memory) {
if(games[_gameId].currentState == GameState.Revote) {
return playersInRevote[_gameId];
}
return new address[](0);
}
/**
* @notice Retrieves Game ID from Pyth sequence number.
* @param _sequenceNumber The unique sequence number generated by a Pyth randomness request.
* @return The game ID corresponding to the given Pyth sequence number, or 0 if no game is associated.
*/
function getGamefromSequence(uint64 _sequenceNumber) external view returns (uint256) { return sequenceNumberToGameId[_sequenceNumber]; }
/**
* @notice Retrieve the wallet of winning addresses from the Game ID.
* @param _gameId The ID of the finished game.
* @return An array of addresses of the winning players for the specified game.
*/
function getWinnerAddress(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (address[] memory) {
if (games[_gameId].currentState != GameState.Finished) revert GameNotFinished(_gameId, games[_gameId].currentState);
return winners[_gameId];
}
/**
* @notice The amount that the townfolk has won as a reward for any queried game.
* @param _gameId The ID of the finished game where the Townfolk team won.
* @return The payout amount in Wei for each living Townfolk player on the winning team.
*/
function townfolkReward(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (uint256) {
GameSettings storage game = games[_gameId];
if (game.currentState != GameState.Finished) revert GameNotFinished(_gameId, game.currentState);
if (game.winningTeam != Role.Townfolk) revert WrongGameStateForAction(_gameId, game.currentState);
return payoutPerWinnerTownfolk[_gameId];
}
/**
* @notice Retrieves whether or not the game is in a Paused status.
* @dev Returns pause status by checking the Pausable state.
* @return True if the contract is currently paused, false otherwise.
*/
function getPauseStatus() external view returns (bool) { return paused(); }
// Add the internal helper
function _adminSetAddresses(address newBackend, address newValidator, address newPythProvider, address newTownHallWallet) internal {
if (newBackend != address(0)) {
_backendAddress = newBackend;
emit BackendAddressSet(newBackend);
}
if (newValidator != address(0)) {
address oldValidator = _validatorAddress;
_validatorAddress = newValidator;
emit ValidatorSet(oldValidator, newValidator);
}
if (newPythProvider != address(0)) {
_pythProviderAddress = newPythProvider;
emit PythProviderSet(newPythProvider);
}
if (newTownHallWallet != address(0)) {
townHallWallet = newTownHallWallet;
emit TownHallWalletSet(newTownHallWallet);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import "./EntropyStructs.sol";
interface EntropyEvents {
event Registered(EntropyStructs.ProviderInfo provider);
event Requested(EntropyStructs.Request request);
event RequestedWithCallback(
address indexed provider,
address indexed requestor,
uint64 indexed sequenceNumber,
bytes32 userRandomNumber,
EntropyStructs.Request request
);
event Revealed(
EntropyStructs.Request request,
bytes32 userRevelation,
bytes32 providerRevelation,
bytes32 blockHash,
bytes32 randomNumber
);
event RevealedWithCallback(
EntropyStructs.Request request,
bytes32 userRandomNumber,
bytes32 providerRevelation,
bytes32 randomNumber
);
event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee);
event ProviderUriUpdated(address provider, bytes oldUri, bytes newUri);
event ProviderFeeManagerUpdated(
address provider,
address oldFeeManager,
address newFeeManager
);
event Withdrawal(
address provider,
address recipient,
uint128 withdrawnAmount
);
}// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
contract EntropyStructs {
struct ProviderInfo {
uint128 feeInWei;
uint128 accruedFeesInWei;
// The commitment that the provider posted to the blockchain, and the sequence number
// where they committed to this. This value is not advanced after the provider commits,
// and instead is stored to help providers track where they are in the hash chain.
bytes32 originalCommitment;
uint64 originalCommitmentSequenceNumber;
// Metadata for the current commitment. Providers may optionally use this field to help
// manage rotations (i.e., to pick the sequence number from the correct hash chain).
bytes commitmentMetadata;
// Optional URI where clients can retrieve revelations for the provider.
// Client SDKs can use this field to automatically determine how to retrieve random values for each provider.
// TODO: specify the API that must be implemented at this URI
bytes uri;
// The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index).
// The contract maintains the invariant that sequenceNumber <= endSequenceNumber.
// If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values.
uint64 endSequenceNumber;
// The sequence number that will be assigned to the next inbound user request.
uint64 sequenceNumber;
// The current commitment represents an index/value in the provider's hash chain.
// These values are used to verify requests for future sequence numbers. Note that
// currentCommitmentSequenceNumber < sequenceNumber.
//
// The currentCommitment advances forward through the provider's hash chain as values
// are revealed on-chain.
bytes32 currentCommitment;
uint64 currentCommitmentSequenceNumber;
// An address that is authorized to set / withdraw fees on behalf of this provider.
address feeManager;
}
struct Request {
// Storage slot 1 //
address provider;
uint64 sequenceNumber;
// The number of hashes required to verify the provider revelation.
uint32 numHashes;
// Storage slot 2 //
// The commitment is keccak256(userCommitment, providerCommitment). Storing the hash instead of both saves 20k gas by
// eliminating 1 store.
bytes32 commitment;
// Storage slot 3 //
// The number of the block where this request was created.
// Note that we're using a uint64 such that we have an additional space for an address and other fields in
// this storage slot. Although block.number returns a uint256, 64 bits should be plenty to index all of the
// blocks ever generated.
uint64 blockNumber;
// The address that requested this random number.
address requester;
// If true, incorporate the blockhash of blockNumber into the generated random value.
bool useBlockhash;
// If true, the requester will be called back with the generated random value.
bool isRequestWithCallback;
// There are 2 remaining bytes of free space in this slot.
}
}// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./EntropyEvents.sol";
interface IEntropy is EntropyEvents {
// Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
// and initial commitment. Re-registering the same provider rotates the provider's commitment (and updates
// the feeInWei).
//
// chainLength is the number of values in the hash chain *including* the commitment, that is, chainLength >= 1.
function register(
uint128 feeInWei,
bytes32 commitment,
bytes calldata commitmentMetadata,
uint64 chainLength,
bytes calldata uri
) external;
// Withdraw a portion of the accumulated fees for the provider msg.sender.
// Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
// balance of fees in the contract).
function withdraw(uint128 amount) external;
// Withdraw a portion of the accumulated fees for provider. The msg.sender must be the fee manager for this provider.
// Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
// balance of fees in the contract).
function withdrawAsFeeManager(address provider, uint128 amount) external;
// As a user, request a random number from `provider`. Prior to calling this method, the user should
// generate a random number x and keep it secret. The user should then compute hash(x) and pass that
// as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
//
// This method returns a sequence number. The user should pass this sequence number to
// their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
// number. The user should then call fulfillRequest to construct the final random number.
//
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
function request(
address provider,
bytes32 userCommitment,
bool useBlockHash
) external payable returns (uint64 assignedSequenceNumber);
// Request a random number. The method expects the provider address and a secret random number
// in the arguments. It returns a sequence number.
//
// The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
// The `entropyCallback` method on that interface will receive a callback with the generated random number.
//
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
function requestWithCallback(
address provider,
bytes32 userRandomNumber
) external payable returns (uint64 assignedSequenceNumber);
// Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
// against the corresponding commitments in the in-flight request. If both values are validated, this function returns
// the corresponding random number.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
function reveal(
address provider,
uint64 sequenceNumber,
bytes32 userRevelation,
bytes32 providerRevelation
) external returns (bytes32 randomNumber);
// Fulfill a request for a random number. This method validates the provided userRandomness
// and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated
// and the requestor address is a contract address, this function calls the requester's entropyCallback method with the
// sequence number, provider address and the random number as arguments. Else if the requestor is an EOA, it won't call it.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
//
// Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
function revealWithCallback(
address provider,
uint64 sequenceNumber,
bytes32 userRandomNumber,
bytes32 providerRevelation
) external;
function getProviderInfo(
address provider
) external view returns (EntropyStructs.ProviderInfo memory info);
function getDefaultProvider() external view returns (address provider);
function getRequest(
address provider,
uint64 sequenceNumber
) external view returns (EntropyStructs.Request memory req);
function getFee(address provider) external view returns (uint128 feeAmount);
function getAccruedPythFees()
external
view
returns (uint128 accruedPythFeesInWei);
function setProviderFee(uint128 newFeeInWei) external;
function setProviderFeeAsFeeManager(
address provider,
uint128 newFeeInWei
) external;
function setProviderUri(bytes calldata newUri) external;
// Set manager as the fee manager for the provider msg.sender.
// After calling this function, manager will be able to set the provider's fees and withdraw them.
// Only one address can be the fee manager for a provider at a time -- calling this function again with a new value
// will override the previous value. Call this function with the all-zero address to disable the fee manager role.
function setFeeManager(address manager) external;
function constructUserCommitment(
bytes32 userRandomness
) external pure returns (bytes32 userCommitment);
function combineRandomValues(
bytes32 userRandomness,
bytes32 providerRandomness,
bytes32 blockHash
) external pure returns (bytes32 combinedRandomness);
}// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
abstract contract IEntropyConsumer {
// This method is called by Entropy to provide the random number to the consumer.
// It asserts that the msg.sender is the Entropy contract. It is not meant to be
// override by the consumer.
function _entropyCallback(
uint64 sequence,
address provider,
bytes32 randomNumber
) external {
address entropy = getEntropy();
require(entropy != address(0), "Entropy address not set");
require(msg.sender == entropy, "Only Entropy can call this function");
entropyCallback(sequence, provider, randomNumber);
}
// getEntropy returns Entropy contract address. The method is being used to check that the
// callback is indeed from Entropy contract. The consumer is expected to implement this method.
// Entropy address can be found here - https://docs.pyth.network/entropy/contract-addresses
function getEntropy() internal view virtual returns (address);
// This method is expected to be implemented by the consumer to handle the random number.
// It will be called by _entropyCallback after _entropyCallback ensures that the call is
// indeed from Entropy contract.
function entropyCallback(
uint64 sequence,
address provider,
bytes32 randomNumber
) internal virtual;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @notice Defines the possible secret roles a player can be assigned within the game.
*/
enum Role { Unassigned, Townfolk, Witch }
/**
* @notice Represents the different phases or operational states a game can transition through.
*/
enum GameState { Lobby, WaitingForRandomness, Night, Day, Revote, Finished, Cancelled }
/**
* @notice Stores critical information about each player participating in a specific game.
*/
struct Player {
address playerAddress;
Role role;
bool isAlive;
bool claimedPayout;
uint256 nftBoostBps;
}
/**
* @notice Holds all configuration settings and dynamic state for a single game instance.
*/
struct GameSettings {
address host;
uint256 gameBuyIn;
uint8 maxPlayers;
uint256 totalPot;
uint256 platformFeeBasisPoints;
GameState currentState;
uint256 createdAt;
uint256 currentRound;
uint256 phaseStartTime;
uint64 pythSequenceNumber;
bool isRandomnessFulfilled;
uint256 deadTownfolkRefundPool;
Role winningTeam;
}
// Denominator for all basis point (BPS) calculations. 100% = 10,000 BPS.
uint256 constant BASIS_POINTS = 10000;
// The total number of witches set for all games (fixed to 1).
uint8 constant WITCH_COUNT = 1;
// The reward for a dead Townfolk when their team wins, as a percentage of their buy-in (BPS).
uint256 constant DEAD_TOWNFOLK_REWARD = 5000;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../GameDefinitions.sol";
library PayoutLogic {
struct FeeAndRefundCalculationsOutput {
uint256 platformFeeAmount;
uint256 potRemainingAfterFee;
uint256 totalAllocatedForDeadTownfolkRefunds;
}
/**
* @dev Internal: Calculates payout/refund for a player in a finished game. Core logic for determining player rewards.
* @param _playerAddress Address of the player.
* @param game A storage reference to the game's settings.
* @param player A storage reference to the player's data.
* @param playerListForGame Array of player addresses in the game.
* @param winnersForGame Array of winner addresses for the game.
* @param payoutPerWinnerTownfolkForGame Payout amount for each winning townfolk.
* @return playerPayoutAmount The amount of native $APE (in Wei) due to the player.
* @return platformFeeAmountForInfo The total platform fee (in Wei) collected from this specific game's prize pool.
*/
function calculatePayoutInLibrary(
uint256 /*_gameId*/, // Unused in current logic but kept for context if library expands
address _playerAddress,
GameSettings storage game,
Player storage player,
address[] storage playerListForGame,
address[] storage winnersForGame,
uint256 payoutPerWinnerTownfolkForGame
)
internal view
returns (uint256 playerPayoutAmount, uint256 platformFeeAmountForInfo)
{
// Suppress unused _gameId warning if truly not needed by direct logic here
// bytes32 temp = keccak256(abi.encode(_gameId)); // Example to use _gameId if needed
uint256 originalGamePotSize = game.gameBuyIn * playerListForGame.length;
platformFeeAmountForInfo = (originalGamePotSize * game.platformFeeBasisPoints) / BASIS_POINTS;
playerPayoutAmount = 0;
bool isAnActualWinner = false;
for (uint i = 0; i < winnersForGame.length; i++) {
if (winnersForGame[i] == _playerAddress) {
isAnActualWinner = true;
break;
}
}
bool isEligibleForDeadTownfolkRefund = !player.isAlive && player.role == Role.Townfolk && game.winningTeam == Role.Townfolk;
if (isAnActualWinner) {
if (game.winningTeam == Role.Witch) {
uint256 potAfterFee = originalGamePotSize - platformFeeAmountForInfo;
if (winnersForGame.length > 0) {
playerPayoutAmount = potAfterFee / winnersForGame.length;
}
} else if (game.winningTeam == Role.Townfolk) {
playerPayoutAmount = payoutPerWinnerTownfolkForGame;
}
} else if (isEligibleForDeadTownfolkRefund) {
// DEAD_TOWNFOLK_REWARD needs to be accessible.
// For now, assuming it's a global constant or passed in.
// If it's from DemWitches, it needs to be passed or GameDefinitions.
// Let's assume it's available via GameDefinitions or passed if this library is standalone.
// For this step, we'll hardcode it to keep the library self-contained for now,
// or ideally, it should be imported from GameDefinitions if it's moved there.
// uint256 DEAD_TOWNFOLK_REWARD_CONST = 5000; // Placeholder if not in GameDefinitions
// For now, let's assume DEAD_TOWNFOLK_REWARD is accessible globally or via GameDefinitions
// If DemWitches.DEAD_TOWNFOLK_REWARD is used, this library would need to know about DemWitches.
// The best is to pass it or make it a constant in GameDefinitions.
// For this specific step, I will assume it's accessible.
// If DemWitches.DEAD_TOWNFOLK_REWARD is a public constant, it can be accessed.
// Let's assume it's defined in GameDefinitions.sol for now.
// If not, this will cause a compile error, which we'll fix.
// DEAD_TOWNFOLK_REWARD is now imported from GameDefinitions.sol
playerPayoutAmount = (game.gameBuyIn * DEAD_TOWNFOLK_REWARD) / BASIS_POINTS;
}
return (playerPayoutAmount, platformFeeAmountForInfo);
}
/**
* @dev Calculates platform fees and the dead townfolk refund pool.
* @param game A storage reference to the game's settings.
* @param playerListForGame Array of player addresses in the game.
* @param gamePlayersForGame Mapping of player addresses to Player structs for the game.
* @return output A FeeAndRefundCalculationsOutput struct containing calculated fee, remaining pot, and refund pool.
*/
function calculateFeesAndRefundPoolInLibrary(
GameSettings storage game,
address[] storage playerListForGame, // Needed for original pot size if not directly available
mapping(address => Player) storage gamePlayersForGame // Needed for dead townfolk role check
) internal view returns (FeeAndRefundCalculationsOutput memory output) {
uint256 initialTotalPot;
// If game.totalPot is already the pre-fee amount from DemWitches, use it.
// Otherwise, if it's the sum of buy-ins, calculate from there.
// Assuming game.totalPot is the sum of all buyIns for this calculation.
// If DemWitches passes the original pot (sum of buyIns) as game.totalPot for this call, this is fine.
// Alternatively, calculate originalGamePotSize = game.gameBuyIn * playerListForGame.length;
// For now, let's assume game.totalPot IS the initial total pot before any deductions in checkWinConditions.
initialTotalPot = game.totalPot;
output.platformFeeAmount = (initialTotalPot * game.platformFeeBasisPoints) / BASIS_POINTS;
output.potRemainingAfterFee = initialTotalPot - output.platformFeeAmount;
output.totalAllocatedForDeadTownfolkRefunds = 0;
if (game.winningTeam == Role.Townfolk) { // Only calculate refund pool if Townfolk win
for (uint i = 0; i < playerListForGame.length; i++) {
Player storage p = gamePlayersForGame[playerListForGame[i]];
if (!p.isAlive && p.role == Role.Townfolk) {
output.totalAllocatedForDeadTownfolkRefunds += (game.gameBuyIn * DEAD_TOWNFOLK_REWARD) / BASIS_POINTS;
}
}
}
// Note: This function does not modify game.deadTownfolkRefundPool directly.
// It also does not modify game.totalPot. DemWitches.sol will use these return values.
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../GameDefinitions.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; // For balanceOf
library RoleLogic {
/**
* @dev Calculates tiered NFT boost based on holdings in 1-2 collections using configurable tiers.
* @param _playerAddress The address of the player whose NFT holdings are being checked.
* @param _boostedCollection1 Address of the first boosted NFT collection.
* @param _boostedCollection2 Address of the second boosted NFT collection.
* @param _nftBoostTiersCounts Array of NFT count thresholds for tiers.
* @param _nftBoostTiersBps Array of BPS values for tiers.
* @return calculatedBoostBps The calculated boost percentage in BPS.
*/
function calculateTieredNftBoostInLibrary(
address _playerAddress,
address _boostedCollection1,
address _boostedCollection2,
uint8[] storage _nftBoostTiersCounts, // Pass storage reference
uint256[] storage _nftBoostTiersBps // Pass storage reference
) internal view returns (uint256 calculatedBoostBps) {
uint256 totalEligibleNftCount = 0;
if (_boostedCollection1 != address(0)) {
try IERC721(_boostedCollection1).balanceOf(_playerAddress) returns (uint256 balanceInCollection1) {
totalEligibleNftCount += balanceInCollection1;
} catch { /* Silently ignore failures */ }
}
if (_boostedCollection2 != address(0)) {
try IERC721(_boostedCollection2).balanceOf(_playerAddress) returns (uint256 balanceInCollection2) {
totalEligibleNftCount += balanceInCollection2;
} catch { /* Silently ignore failures */ }
}
if (_nftBoostTiersBps.length == 0) return 0;
if (_nftBoostTiersCounts.length == 0) {
return totalEligibleNftCount > 0 ? _nftBoostTiersBps[0] : 0;
}
if (_nftBoostTiersBps.length != _nftBoostTiersCounts.length + 1) return 0;
calculatedBoostBps = 0;
for (uint i = 0; i < _nftBoostTiersCounts.length; i++) {
if (totalEligibleNftCount > 0 && totalEligibleNftCount <= _nftBoostTiersCounts[i]) {
bool appliesToThisTier = (i == 0) || (totalEligibleNftCount > _nftBoostTiersCounts[i-1]);
if (appliesToThisTier) {
calculatedBoostBps = _nftBoostTiersBps[i];
return calculatedBoostBps;
}
}
}
if (_nftBoostTiersCounts.length > 0 && totalEligibleNftCount > _nftBoostTiersCounts[_nftBoostTiersCounts.length - 1]) {
calculatedBoostBps = _nftBoostTiersBps[_nftBoostTiersCounts.length];
}
return calculatedBoostBps;
}
/**
* @dev Assigns Witch role using weighted randomness based on cached NFT boost.
* @param gamePlayersForGame Mapping of game ID to player address to Player struct (effectively gamePlayers[_gameId]).
* @param currentPlayersInGame Array of player addresses in the current game.
* @param _randomNumber The secure random number from Pyth.
* @notice This function modifies the `role` within the Player structs in `gamePlayersForGame`.
*/
function assignRolesWeightedInLibrary(
mapping(address => Player) storage gamePlayersForGame, // This is gamePlayers[_gameId]
address[] storage currentPlayersInGame,
uint256 _randomNumber
) internal {
uint playerCount = currentPlayersInGame.length;
if (playerCount < WITCH_COUNT) {
// Cannot revert with custom error from library directly if it's not defined here
// Revert with a generic string or handle error appropriately if this library were more complex
revert("RoleLogic: Not enough players for witch assignment");
}
uint[] memory playerWeights = new uint[](playerCount);
uint totalCombinedWeight = 0;
uint baseWeightPerPlayer = 100;
for (uint i = 0; i < playerCount; i++) {
uint currentCalculatedWeight = baseWeightPerPlayer;
uint256 playerCachedNftBoostBps = gamePlayersForGame[currentPlayersInGame[i]].nftBoostBps;
if (playerCachedNftBoostBps > 0) {
currentCalculatedWeight += (baseWeightPerPlayer * playerCachedNftBoostBps) / BASIS_POINTS;
}
playerWeights[i] = currentCalculatedWeight;
totalCombinedWeight += currentCalculatedWeight;
}
if (totalCombinedWeight == 0) {
revert("RoleLogic: Total weight is zero, cannot assign roles");
}
uint randomNumberForSelection = uint(keccak256(abi.encodePacked(_randomNumber, "DEMWITCHES_ROLE_ASSIGNMENT_SALT_V3.8"))) % totalCombinedWeight;
uint cumulativeWeightTracker = 0;
address assignedWitchAddress = address(0);
for (uint i = 0; i < playerCount; i++) {
cumulativeWeightTracker += playerWeights[i];
if (randomNumberForSelection < cumulativeWeightTracker) {
assignedWitchAddress = currentPlayersInGame[i];
break;
}
}
if (assignedWitchAddress == address(0)) {
assignedWitchAddress = currentPlayersInGame[uint(keccak256(abi.encodePacked(_randomNumber, "DEMWITCHES_WITCH_FALLBACK_SALT_V3.8"))) % playerCount];
}
for (uint i = 0; i < playerCount; i++) {
gamePlayersForGame[currentPlayersInGame[i]].role = (currentPlayersInGame[i] == assignedWitchAddress) ? Role.Witch : Role.Townfolk;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../GameDefinitions.sol";
library VoteLogic {
struct VoteOutcome {
address playerToBurn;
uint256 maxVoteCount;
uint tieCount;
address[] tiedPlayers; // For event emission primarily
bool actionTaken; // Indicates if a burn or tie leading to revote occurred
}
/**
* @dev Tallies votes and determines the outcome (player to burn, tie, or no majority).
* @param voters Array of voter addresses.
* @param votedFor Array of addresses voted for (parallel to voters).
* @param playerListForGame Array of all player addresses in the game.
* @param gamePlayersForGame Mapping of player addresses to Player structs for the game.
* @return outcome A VoteOutcome struct detailing the result of the vote.
*/
function tallyVotesAndDetermineOutcomeInLibrary(
address[] memory voters,
address[] memory votedFor,
address[] storage playerListForGame,
mapping(address => Player) storage gamePlayersForGame
) internal view returns (VoteOutcome memory outcome) {
outcome.playerToBurn = address(0);
outcome.maxVoteCount = 0;
outcome.tieCount = 0;
outcome.actionTaken = false; // Default to no decisive action
uint totalPlayerCountInList = playerListForGame.length;
if (totalPlayerCountInList == 0) {
// No players, should not happen in a real game scenario during voting
return outcome;
}
uint[] memory voteCounts = new uint[](totalPlayerCountInList);
// Tally votes
for (uint j = 0; j < voters.length; j++) {
address voter = voters[j];
address targetPlayer = votedFor[j];
if (gamePlayersForGame[voter].playerAddress != address(0) && // Voter must exist
gamePlayersForGame[voter].isAlive && // Voter must be alive
targetPlayer != address(0) && // Not an abstain
gamePlayersForGame[targetPlayer].playerAddress != address(0) && // Target must exist
gamePlayersForGame[targetPlayer].isAlive) { // Target must be alive
for (uint k = 0; k < totalPlayerCountInList; k++) {
if (playerListForGame[k] == targetPlayer) {
voteCounts[k]++;
break;
}
}
}
}
// Temporary array for tied players, max possible size
address[] memory tempTiedPlayersList = new address[](totalPlayerCountInList);
uint actualTiedPlayerCount = 0;
// Determine player with most votes / ties
for (uint i = 0; i < totalPlayerCountInList; i++) {
address candidatePlayer = playerListForGame[i];
// Only consider alive players who could have received votes
if (gamePlayersForGame[candidatePlayer].playerAddress == address(0) || !gamePlayersForGame[candidatePlayer].isAlive) {
continue;
}
uint votesForThisCandidate = voteCounts[i];
if (votesForThisCandidate > 0) {
if (votesForThisCandidate > outcome.maxVoteCount) {
outcome.maxVoteCount = votesForThisCandidate;
outcome.playerToBurn = candidatePlayer;
outcome.tieCount = 1;
tempTiedPlayersList[0] = candidatePlayer; // Start new list of tied players
actualTiedPlayerCount = 1;
} else if (votesForThisCandidate == outcome.maxVoteCount) {
outcome.tieCount++;
if (actualTiedPlayerCount < totalPlayerCountInList) { // Bound check
tempTiedPlayersList[actualTiedPlayerCount++] = candidatePlayer;
}
// If actualTiedPlayerCount >= totalPlayerCountInList, it's an overflow,
// but this implies all players tied, which is fine.
}
}
}
// Finalize tiedPlayers array
outcome.tiedPlayers = new address[](actualTiedPlayerCount);
for(uint i = 0; i < actualTiedPlayerCount; i++) {
outcome.tiedPlayers[i] = tempTiedPlayersList[i];
}
// Determine if an action (burn or tie leading to revote) was taken
if (outcome.playerToBurn != address(0) && outcome.maxVoteCount > 0) {
outcome.actionTaken = true;
}
return outcome;
}
}{
"optimizer": {
"enabled": true,
"runs": 1
},
"viaIR": true,
"evmVersion": "paris",
"debug": {
"revertStrings": "strip"
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_pythEntropyAddr","type":"address"},{"internalType":"address","name":"_initialContractOwner","type":"address"},{"internalType":"uint256","name":"_initialDefaultFeeBps","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"AlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"AlreadyJoined","type":"error"},{"inputs":[],"name":"BackendAddressNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"CancellationFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"CannotTargetSelf","type":"error"},{"inputs":[],"name":"DurationTooShort","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[{"internalType":"uint256","name":"feeBps","type":"uint256"},{"internalType":"uint256","name":"maxBps","type":"uint256"}],"name":"FeeTooHigh","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameAlreadyStartedOrFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"uint8","name":"maxPlayers","type":"uint8"}],"name":"GameFull","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameIsFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"GameNotFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"IncorrectBuyInAmount","type":"error"},{"inputs":[],"name":"InsufficientContractBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"},{"internalType":"uint256","name":"balance","type":"uint256"}],"name":"InsufficientFundsForPythFee","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"InternalAssignmentError","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidBoostTierConfiguration","type":"error"},{"inputs":[{"internalType":"address","name":"expected","type":"address"},{"internalType":"address","name":"actual","type":"address"}],"name":"InvalidCallbackOrigin","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"expected","type":"uint8"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"InvalidGameState","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"target","type":"address"}],"name":"InvalidRevoteTarget","type":"error"},{"inputs":[{"internalType":"uint64","name":"sequenceNum","type":"uint64"}],"name":"InvalidSequenceNumber","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"target","type":"address"}],"name":"InvalidVoteTarget","type":"error"},{"inputs":[{"internalType":"uint8","name":"maxPlayers","type":"uint8"}],"name":"MaxPlayersInvalid","type":"error"},{"inputs":[],"name":"NativeTransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"sender","type":"address"}],"name":"NotAWitch","type":"error"},{"inputs":[],"name":"NotAuthorized","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"uint8","name":"current","type":"uint8"},{"internalType":"uint8","name":"required","type":"uint8"}],"name":"NotEnoughPlayers","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"host","type":"address"}],"name":"NotGameHost","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"PayoutRetryInvalid","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"PlayerNotAlive","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"PlayerNotInGame","type":"error"},{"inputs":[],"name":"PythProviderAddressNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"RandomnessNotReady","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"TimeoutNotReached","type":"error"},{"inputs":[],"name":"TownHallWalletNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"WrongGameStateForAction","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"enum GameState","name":"oldState","type":"uint8"},{"indexed":false,"internalType":"enum GameState","name":"newState","type":"uint8"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"AdminForcedState","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"backendAddress","type":"address"}],"name":"BackendAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address","name":"killedInNight","type":"address"}],"name":"DayPhaseStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"cancelledBy","type":"address"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"GameCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"host","type":"address"},{"indexed":false,"internalType":"uint256","name":"gameBuyIn","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"maxPlayers","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"feeBps","type":"uint256"}],"name":"GameCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"lobby","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"randomness","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"night","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"day","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"revote","type":"uint256"}],"name":"GameDurationsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"enum Role","name":"winningTeam","type":"uint8"},{"indexed":false,"internalType":"address[]","name":"winners","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"payoutPerWinner","type":"uint256"}],"name":"GameFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"GamePaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"host","type":"address"},{"indexed":false,"internalType":"uint64","name":"sequenceNumber","type":"uint64"}],"name":"GameStartRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"GameUnpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8[]","name":"nftCounts","type":"uint8[]"},{"indexed":false,"internalType":"uint256[]","name":"boostBps","type":"uint256[]"}],"name":"NftBoostTiersSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"index","type":"uint8"},{"indexed":true,"internalType":"address","name":"collection","type":"address"}],"name":"NftCollectionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"}],"name":"NightPhaseStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PayoutClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"}],"name":"PayoutRetryFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PayoutRetrySucceeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"enum GameState","name":"timedOutPhase","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"PhaseForcedByTimeout","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFeeBasisPoints","type":"uint256"}],"name":"PlatformFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"votes","type":"uint256"}],"name":"PlayerBurned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"votes","type":"uint256"}],"name":"PlayerBurnedAfterRevote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"}],"name":"PlayerCursed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"}],"name":"PlayerInactiveEliminated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftBoostAppliedBps","type":"uint256"}],"name":"PlayerJoined","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PlayerRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"providerAddress","type":"address"}],"name":"PythProviderSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"sequenceNumber","type":"uint64"}],"name":"RandomnessReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"playersInRevote","type":"address[]"}],"name":"RevotePhaseStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"}],"name":"RevoteTied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"RolesAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newTownHallWallet","type":"address"}],"name":"TownHallWalletSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldValidator","type":"address"},{"indexed":true,"internalType":"address","name":"newValidator","type":"address"}],"name":"ValidatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"tiedPlayers","type":"address[]"}],"name":"VoteTied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"lastTownfolk","type":"address"}],"name":"WitchKilledLastTownfolk","type":"event"},{"inputs":[],"name":"GRACE_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_FEE","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLAYERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_PLAYERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PYTH_ENTROPY","outputs":[{"internalType":"contract IEntropy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"sequence","type":"uint64"},{"internalType":"address","name":"provider","type":"address"},{"internalType":"bytes32","name":"randomNumber","type":"bytes32"}],"name":"_entropyCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"adminCancelGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"adminKill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"adminPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"adminRetryReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newBackendAddress","type":"address"}],"name":"adminSetBackend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"nftCounts_","type":"uint8[]"},{"internalType":"uint256[]","name":"boostBps_","type":"uint256[]"}],"name":"adminSetBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collectionAddress","type":"address"}],"name":"adminSetBoostCollection1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collectionAddress","type":"address"}],"name":"adminSetBoostCollection2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lobby_","type":"uint256"},{"internalType":"uint256","name":"randomness_","type":"uint256"},{"internalType":"uint256","name":"night_","type":"uint256"},{"internalType":"uint256","name":"day_","type":"uint256"},{"internalType":"uint256","name":"revote_","type":"uint256"}],"name":"adminSetDurations","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newFeeBps","type":"uint256"}],"name":"adminSetFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"enum GameState","name":"_newState","type":"uint8"}],"name":"adminSetPhase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_providerAddress","type":"address"}],"name":"adminSetPythProvider","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newTownHallWallet","type":"address"}],"name":"adminSetTownHallWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newValidatorAddress","type":"address"}],"name":"adminSetValidatorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"adminUnpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"backendAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"backendCancelGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"boostedCollection1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boostedCollection2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"checkTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameBuyIn","type":"uint256"},{"internalType":"uint8","name":"_maxPlayers","type":"uint8"}],"name":"createLobby","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"dayDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBigButts","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getBoostedCollections","outputs":[{"internalType":"address","name":"collection1","type":"address"},{"internalType":"address","name":"collection2","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getGameInfo","outputs":[{"components":[{"internalType":"address","name":"host","type":"address"},{"internalType":"uint256","name":"gameBuyIn","type":"uint256"},{"internalType":"uint8","name":"maxPlayers","type":"uint8"},{"internalType":"uint256","name":"totalPot","type":"uint256"},{"internalType":"uint256","name":"platformFeeBasisPoints","type":"uint256"},{"internalType":"enum GameState","name":"currentState","type":"uint8"},{"internalType":"uint256","name":"createdAt","type":"uint256"},{"internalType":"uint256","name":"currentRound","type":"uint256"},{"internalType":"uint256","name":"phaseStartTime","type":"uint256"},{"internalType":"uint64","name":"pythSequenceNumber","type":"uint64"},{"internalType":"bool","name":"isRandomnessFulfilled","type":"bool"},{"internalType":"uint256","name":"deadTownfolkRefundPool","type":"uint256"},{"internalType":"enum Role","name":"winningTeam","type":"uint8"}],"internalType":"struct GameSettings","name":"settings","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getGameSettings","outputs":[{"components":[{"internalType":"address","name":"host","type":"address"},{"internalType":"uint256","name":"gameBuyIn","type":"uint256"},{"internalType":"uint8","name":"maxPlayers","type":"uint8"},{"internalType":"uint256","name":"totalPot","type":"uint256"},{"internalType":"uint256","name":"platformFeeBasisPoints","type":"uint256"},{"internalType":"enum GameState","name":"currentState","type":"uint8"},{"internalType":"uint256","name":"createdAt","type":"uint256"},{"internalType":"uint256","name":"currentRound","type":"uint256"},{"internalType":"uint256","name":"phaseStartTime","type":"uint256"},{"internalType":"uint64","name":"pythSequenceNumber","type":"uint64"},{"internalType":"bool","name":"isRandomnessFulfilled","type":"bool"},{"internalType":"uint256","name":"deadTownfolkRefundPool","type":"uint256"},{"internalType":"enum Role","name":"winningTeam","type":"uint8"}],"internalType":"struct GameSettings","name":"settings","type":"tuple"},{"internalType":"address[]","name":"currentPlayers","type":"address[]"},{"internalType":"address[]","name":"gameWinners","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"_sequenceNumber","type":"uint64"}],"name":"getGamefromSequence","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getMyRole","outputs":[{"internalType":"enum Role","name":"playerRole","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPauseStatus","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"uint256","name":"_playerIndex","type":"uint256"}],"name":"getPlayerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"getPlayerInfo","outputs":[{"components":[{"internalType":"address","name":"playerAddress","type":"address"},{"internalType":"bool","name":"isAlive","type":"bool"},{"internalType":"bool","name":"claimedPayout","type":"bool"},{"internalType":"uint256","name":"nftBoostBps","type":"uint256"}],"internalType":"struct DemWitches.PublicPlayerInfo","name":"publicInfo","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPlayersFundsLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"getPotentialReward","outputs":[{"internalType":"uint256","name":"payoutAmount","type":"uint256"},{"internalType":"uint256","name":"platformFeeForGame","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getTiedWallets","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTownHallWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidatorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getWinnerAddress","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"hostCancelGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"joinLobby","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"nextGameID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nftBoost","outputs":[{"internalType":"uint8[]","name":"counts","type":"uint8[]"},{"internalType":"uint256[]","name":"bps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nightDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pythEntropyAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pythProviderAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revoteDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"startGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"townHallWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"townfolkReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"triggerLobbyTimeout","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address[]","name":"_voters","type":"address[]"},{"internalType":"address[]","name":"_votedFor","type":"address[]"}],"name":"voteAgain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address[]","name":"_voters","type":"address[]"},{"internalType":"address[]","name":"_votedFor","type":"address[]"}],"name":"voteAndBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_targetAddress","type":"address"}],"name":"voteAndCurse","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
60a034620009ea57601f62006a1638819003918201601f19168301916001600160401b03831184841017620008fd57808492606094604052833981010312620009ea576200004d81620009ef565b60406200005d60208401620009ef565b920151916001600160a01b03908116918215620009d157600080546001600160a01b03198116851782556040519491908416907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001805516908115620009c257506103e88211620009a157608052600881905560b4600c81905561012c600d55600e55610e10600f819055601055604051606081016001600160401b03811182821017620008fd576040526001815260056020820152600a604082015260115460036011558060031062000938575b506011600052602060002060009160005b600381106200091357505055604051608081016001600160401b03811182821017620008fd5760405261012c81526103e860208201526107d06040820152610dac60608201526012549060049182601255808310620008bf575b506012600052602060002060005b838110620008a6577fdc4e4be378c228e1b7b13787e1d0620609304c2f71ed3a6ee69e4aae99d58cd9602086604051908152a17f2621620528f26fb75529114de7b19d9f01b11bb4a9bd0ad57997a33cf8141ec060a0600f54601054600c54600d5490600e54926040519485526020850152604084015260608301526080820152a16040516040810160408252601154809152606082019060116000526020600020906000915b81601f84011062000703575491818110620006ee575b818110620006d6575b818110620006be575b818110620006a6575b8181106200068f575b81811062000677575b8181106200065f575b81811062000647575b8181106200062f575b81811062000617575b818110620005ff575b818110620005e7575b818110620005cf575b818110620005b7575b8181106200059f575b81811062000587575b8181106200056f575b81811062000557575b8181106200053f575b81811062000527575b8181106200050f575b818110620004f7575b818110620004df575b818110620004c7575b818110620004af575b81811062000497575b8181106200047f575b81811062000467575b8181106200044f575b81811062000437575b8181106200041f575b1062000410575b5081810360208301526020601254918281520190601260005260206000209060005b818110620003f9577f12955a64e0444022667406e8e5e263bdac7d6c8246e16b5c3f28215f09c51cdb85850386a1604051615ff8908162000a1e8239608051818181610c6e01528181611532015281816130fb01526152d30152f35b82548452602090930192600192830192016200039d565b60f81c8152602001826200037b565b92602060019160ff8560f01c16815201930162000374565b92602060019160ff8560e81c1681520193016200036b565b92602060019160ff8560e01c16815201930162000362565b92602060019160ff8560d81c16815201930162000359565b92602060019160ff8560d01c16815201930162000350565b92602060019160ff8560c81c16815201930162000347565b92602060019160ff8560c01c1681520193016200033e565b92602060019160ff8560b81c16815201930162000335565b92602060019160ff8560b01c1681520193016200032c565b92602060019160ff8560a81c16815201930162000323565b92602060019160ff8560a01c1681520193016200031a565b92602060019160ff8560981c16815201930162000311565b92602060019160ff8560901c16815201930162000308565b92602060019160ff8560881c168152019301620002ff565b92602060019160ff8560801c168152019301620002f6565b92602060019160ff8560781c168152019301620002ed565b92602060019160ff8560701c168152019301620002e4565b92602060019160ff8560681c168152019301620002db565b92602060019160ff8560601c168152019301620002d2565b92602060019160ff8560581c168152019301620002c9565b92602060019160ff8560501c168152019301620002c0565b92602060019160ff8560481c168152019301620002b7565b92602060019160ff8560401c168152019301620002ae565b92602060019160ff8560381c168152019301620002a5565b92602060019160ff8560301c1681520193016200029c565b92602060019160ff8560281c16815201930162000293565b92602060019160ff85831c1681520193016200028a565b92602060019160ff8560181c16815201930162000281565b92602060019160ff8560101c16815201930162000278565b92602060019160ff8560081c1681520193016200026f565b92602060019160ff8516815201930162000266565b926001610400602092865460ff8116825260ff8160081c168583015260ff8160101c16604083015260ff8160181c16606083015260ff81861c16608083015260ff8160281c1660a083015260ff8160301c1660c083015260ff8160381c1660e083015260ff8160401c1661010083015260ff8160481c1661012083015260ff8160501c1661014083015260ff8160581c1661016083015260ff8160601c1661018083015260ff8160681c166101a083015260ff8160701c166101c083015260ff8160781c166101e083015260ff8160801c1661020083015260ff8160881c1661022083015260ff8160901c1661024083015260ff8160981c1661026083015260ff8160a01c1661028083015260ff8160a81c166102a083015260ff8160b01c166102c083015260ff8160b81c166102e083015260ff8160c01c1661030083015260ff8160c81c1661032083015260ff8160d01c1661034083015260ff8160d81c1661036083015260ff8160e01c1661038083015260ff8160e81c166103a083015260ff8160f01c166103c083015260f81c6103e082015201940192019162000250565b600190602061ffff8551169401938184015501620001a9565b6012600052620008f6907fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344490810190840162000a04565b386200019b565b634e487b7160e01b600052604160045260246000fd5b9092602060019160ff9081875116918560031b92831b921b1916179401910162000141565b60116000527f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68805462ffffff1681556200099a91601f0160051c017f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6962000a04565b3862000130565b6040516373ab893560e11b8152600481018390526103e86024820152604490fd5b63d92e233d60e01b8152600490fd5b604051631e4fbdf760e01b815260006004820152602490fd5b600080fd5b51906001600160a01b0382168203620009ea57565b81811062000a10575050565b6000815560010162000a0456fe608080604052600436101561001d575b50361561001b57600080fd5b005b60e0600035811c91826202640714613b1a575081630947d30214613ac35781630d72d57f14613a9a5781631154d19014613a715781631562c16b14613a5457816315ec12cd146139eb5781631d9023cb146130b457816322e1dcd01461073157816327bebe88146139c257816330041ba81461370b57816332f6dc7c146136b4578163352ce424146134c55781633ccd10e9146133ac5781634411b3eb1461339057816347e1d550146132815781634f2c0aec1461320b578163515371a31461314c57816352a5f1f8146130d75781635c975abb146130b457816361841cd614613020578163673ee37c146130025781636878b93914612ef35781636919f4d11461151c5781636a6354e914612e295781636bf4da7414612ab7578163715018a614612a705781637cea0c75146128bc5781638267660f1461285357816385f07bbe14612837578163867b49f21461275957816388902d691461258b57816389fc892d1461256d5781638da5cb5b1461254457816390de9f121461250a578163917e7964146124dd578163919cc6a01461236b578163922395b81461230057816392f9c0671461227757816398ddf5841461220e5781639f3e7e4214611b8357508063ad2dfc6514611981578063adcf8110146118c4578063ae169a5014611561578063afb218f51461151c578063b8ca266c1461144b578063bc063e1a1461142e578063c073343b146113c5578063c20860af14611339578063c6b00c7b14610f94578063d18658cd14610f76578063d3780db114610f42578063daef359914610eed578063ddca3f4314610ecf578063e474ee6114610ea6578063e5ed1d5914610baa578063e9173b3a14610954578063ee9c1c7e146108d3578063f0c6fc45146107ec578063f2fde38b14610778578063f6f071f11461075a578063f9e7ad8514610731578063fc9fefb014610713578063fcead4e1146105c75763fe32b0ba146102eb573861000f565b346105c25760403660031901126105c257600435610307613b54565b90806000526020906013825260018060a01b03918260406000205416156105a957816000526013815260ff600560406000200154166007811015610486576005810361057b5750816000526014815260406000209383811694856000528252836040600020541615610559575081600094939452601381526040600020601482526040600020846000528252604060002092600052601582526040600020946018835260406000209460198452604060002054926001976103cd89830154915482614010565b9261271090816103e1600486015487614010565b049860009a6000926000988254995b8a8110610521575b50505050549060ff8260a81c16159182610506575b50816104eb575b1561049c57505050600b015460ff169060038210156104865760409786926002810361046d575050610447929350614003565b8161045a575b50505b8351928352820152f35b610465929450614023565b91388061044d565b92509250501461047e575b50610450565b925038610478565b634e487b7160e01b600052602160045260246000fd5b94509450975050506104b3575b5060409350610450565b909250611388938481029481860414901517156104d5576040930491386104a9565b634e487b7160e01b600052601160045260246000fd5b905060ff600b850154166003811015610486578b1490610414565b60ff91925060a01c166003811015610486578b14903861040d565b828261052d8387613f29565b90549060031b1c16146105485761054390613eb9565b6103f0565b5050505090508a90388080806103f8565b6040516379a6671160e01b8152908190610577908560048401613fca565b0390fd5b604051639cd1128160e01b815260048101849052600560248201526064916105a7906044830190613b6a565bfd5b60405163d13b267760e01b815260048101839052602490fd5b600080fd5b346105c25760203660031901126105c2576004356105e3613e8d565b6105eb613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600181036106d0575080600052601360205261064d60086040600020015460105490613f7c565b42106106be576040516106b89161066382613d1c565b603e82527f43616e63656c6c65642062792061646d696e3a2050797468204e6574776f726b60208301527f2072616e646f6d6e65737320726571756573742074696d6564206f75742e00006040830152614b72565b60018055005b6040516326c015ab60e21b8152600490fd5b906105a760649260405192639cd1128160e01b84526004840152600160248401526044830190613b6a565b6024906040519063d13b267760e01b82526004820152fd5b346105c25760003660031901126105c2576020600c54604051908152f35b346105c25760003660031901126105c2576007546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600d54604051908152f35b346105c25760203660031901126105c257610791613b3e565b610799613e8d565b6001600160a01b039081169081156107d3576000548260018060a01b031982161760005516600080516020615f23833981519152600080a3005b604051631e4fbdf760e01b815260006004820152602490fd5b346105c2576107fa36613dfa565b90610803613f89565b61080b613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff600560406000200154166007811015610486576004810361088f575080600554163314159081610880575b5061086e576106b8926149b5565b60405163ea8e4eb560e01b8152600490fd5b90506000541633141584610860565b604051639cd1128160e01b8152600480820187905260248201526064916105a7906044830190613b6a565b60405163d13b267760e01b815260048101859052602490fd5b346105c25760203660031901126105c2576108ec613b3e565b6108f4613e8d565b6001600160a01b0390811690811561094257600680546001600160a01b031981168417909155167fae4e8ea4dbc2ca24cbdb325a1b328d6a2401757cb7e2be71fbc628f262771cd5600080a3005b60405163d92e233d60e01b8152600490fd5b6020806003193601126105c25760043561096c613f89565b610974613fa7565b60008181526013835260409020546001600160a01b03908116156105a957816000526013835260ff6005604060002001541660078110156104865780610b7e575081600052601383526040600020926015815260406000205460ff60028601541680911015610b605750826000526014815260406000203360005281528160406000205416610b44576001840154803403610b265750610a1e82600a541683600b541690336158e3565b9360405190610a2c82613d01565b3382528282019160008352604081019460018652606082019360008552608083019189835288600052601487526040600020336000528752604060002093511660018060a01b03198454161783555190600382101561048657610ae0600395610ac5600080516020615f0383398151915299610aaa60019688613ed4565b51865460ff60a81b191690151560a81b60ff60a81b16178655565b51845460ff60b01b191690151560b01b60ff60b01b16178455565b519101558460005260158352610afa336040600020613f41565b01610b06348254613f7c565b9055610b1434600954613f7c565b6009556040519384523393a360018055005b6044906040519063fa36c55360e01b82523460048301526024820152fd5b604051631a0af6eb60e21b815280610577338660048401613fca565b836044916040519163d9e1ab7360e01b835260048301526024820152fd5b604051639cd1128160e01b815260048101849052600060248201526064916105a7906044830190613b6a565b346105c2576020806003193601126105c257600435610bc7613f89565b610bcf613fa7565b60008181526013835260409020546001600160a01b03908116156105a95781600052601383528060406000205416803303610e875750816000526013835260ff6005604060002001541660078110156104865780610b7e57508160005260138352604060002092600984019360018060401b0380865416610e6e57846000526015835260406000205460058110610e46578460045416948515610e34577f0000000000000000000000000000000000000000000000000000000000000000169460405191631711922960e31b835281600484015285836024818a5afa928315610dca57600093610df5575b506001600160801b0390921691478311610dd657859291604491604051858101918b835242604083015260608201523360601b608082015260748152610cff81613d01565b5190209260405198899485936319cb825f60e01b8552600485015260248401525af18015610dca57600090610d84575b600080516020615f63833981519152945016948560018060401b031982541617905584600052601782528360406000205560058101600160ff198254161790556008429101556040519384523393a360018055005b50928281813d8311610dc3575b610d9b8183613d52565b810103126105c257519280841684036105c257600080516020615f6383398151915293610d2f565b503d610d91565b6040513d6000823e3d90fd5b6040516322f2dfd560e11b815260048101849052476024820152604490fd5b9092508581813d8311610e2d575b610e0d8183613d52565b810103126105c257516001600160801b03811681036105c2579189610cba565b503d610e03565b604051630b08b4ef60e41b8152600490fd5b8560ff60649260405192631480153360e31b8452600484015216602482015260056044820152fd5b6040516354dd172960e01b815260048101869052602490fd5b6040516317a9a4f960e21b815290819061057790338660048501613fe3565b346105c25760003660031901126105c2576006546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600854604051908152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57610f2a610f3e9161584a565b604051918291602083526020830190613c2e565b0390f35b346105c25760003660031901126105c257600a54600b54604051918291610f3e916001600160a01b03918216911683613e73565b346105c25760003660031901126105c2576020600e54604051908152f35b346105c25760403660031901126105c2576001600160401b036004358181116105c257610fc5903690600401613e43565b9091602480358281116105c257610fe0903690600401613e43565b91610fe9613e8d565b60019081860180871161132457840361127d578561128f575b60005b84811061125a575084861161115657600160401b80871161116a576011548760115580881061120b575b5087956011600052602096876000208960051c9060005b8281106111d15750601f198b168b038061117f575b50505050851161116a578411611156575060125483601255808410611138575b5093816012600052846000209060005b85811061112757505050604051948160408701604088525260608601969160005b8181106111035750505050838503838501528185526001600160fb1b0382116105c2577f12955a64e0444022667406e8e5e263bdac7d6c8246e16b5c3f28215f09c51cdb94849260051b80928583013701030190a1005b9091929788359060ff82168092036105c257908152860197860192919082016110ac565b81358382015590860190870161108b565b61115090601260005284866000209182019101614061565b8661107b565b634e487b7160e01b60009081526041600452fd5b50634e487b7160e01b60009081526041600452fd5b9260009360005b898d83831061119f57505050505001558980808061105b565b90919293966111c790846111b28a614053565b919060ff809160031b9316831b921b19161790565b9601929101611186565b87906000805b8d8082106111eb5750508184015501611046565b90969293916111fe90886111b286614053565b92019501908992916111d7565b6112399060116000526020600020601f808b0160051c820192818c168061123f575b500160051c0190614061565b8861102f565b600019908186019182549160200360031b1c1690558d61122d565b620186a0611269828787614043565b351161127d5761127890613eb9565b611005565b604051630140299b60e71b8152600490fd5b60005b86811061129f5750611002565b60ff806112b56112b0848b8d614043565b614053565b161561127d5781151590816112d8575b5061127d576112d390613eb9565b611292565b90506112e86112b0838a8c614043565b90600019830183811161130f576113046112b083928c8e614043565b1691161115896112c5565b84634e487b7160e01b60005260116004526000fd5b50634e487b7160e01b60009081526011600452fd5b346105c25760003660031901126105c257611352613e8d565b61135a613f89565b611362613f89565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a17ff633f37eb96c51f3eee538b18ab2c8be9023a46d535102a54e986e94a52542066020604051338152a1005b346105c25760203660031901126105c2576113de613b3e565b6113e6613e8d565b6001600160a01b0316801561094257600780546001600160a01b031916821790557f1af232b023641b0b27419a06769dde7a101bdee010ec36c40109696e933761a0600080a2005b346105c25760003660031901126105c25760206040516103e88152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57806000526013602052604060002060ff60058201541690600782101561048657600582036114f857600b015460ff166003811015610486576001036114d4575060005260196020526020604060002054604051908152f35b906105a760449260405192638a8fcb0760e01b845260048401526024830190613b6a565b604051638e84d22560e01b8152600481018490526044906105a76024820185613b6a565b346105c25760003660031901126105c2576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346105c25760203660031901126105c25761157a613f89565b611582613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e57600435600052601460205260406000203360005260205260018060a01b036040600020541615611860576004356000526014602052604060002033600052602052604060002080549060ff8260b01c166118425760043560005260136020526040600020916015602052604060002092601860205260406000206019602052604060002054916116606001820154965487614010565b612710611671600484015483614010565b0491600097600080958054965b878110611806575b505060ff8860a81c161590816117e5575b816117c9575b15611795575050600b015460ff166003811015610486576002810361177a57506116c8929350614003565b81611767575b50505b82156117495760ff60b01b1916600160b01b179055600954811161173f576116fb81600954614003565b6009555b6117098133615230565b60405190815233907fe97cee5a4c0549d3fdc81e322b718ddf0aeb3418ec87dce4f9a7fb28d117c312602060043592a360018055005b60006009556116ff565b60405163f54d5f5360e01b8152806105773360048035908401613fca565b611772929450614023565b9183806116ce565b915050600191501461178d575b506116d1565b925083611787565b945094505050506117a657506116d1565b909250611388908181029181830414901517156104d55761271090049183611787565b905060ff600b840154166003811015610486576001149061169d565b9050600360ff8960a01c16101561048657600160ff8960a01c161490611697565b6118108183613f29565b905460039190911b1c6001600160a01b031633146118365761183190613eb9565b61167e565b50505060018a80611686565b6040516302a4851560e61b8152806105773360048035908401613fca565b6040516379a6671160e01b8152806105773360048035908401613fca565b604051639cd1128160e01b81526004803590820152600560248201526064916105a7906044830190613b6a565b602460405163d13b267760e01b81526004356004820152fd5b346105c2576118d236613dfa565b906118db613f89565b6118e3613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff6005604060002001541660078110156104865760038103611955575080600554163314159081611946575b5061086e576106b8926146a0565b90506000541633141584611938565b604051639cd1128160e01b815260048101869052600360248201526064916105a7906044830190613b6a565b60403660031901126105c2576004356024359060ff82168092036105c2576119a7613f89565b6119af613fa7565b8015611b6357600582108015611b59575b611b4057803403610b2657600354906119d882613eb9565b60035560085490826000526020906013825260406000209260018060a01b03199333858254161781558260018201556002810160ff199088828254161790553460038301558260048301556005820190815416905560064291015560018060a01b0395611a4f87600a541688600b541690336158e3565b9660405190611a5d82613d01565b3382528582019060008252604083019160018352606084019860008a5260808501928c84528b60005260148a526040600020336000528a526040600020955116908554161784555191600383101561048657600080516020615f0383398151915298610ac5611ad292610aaa60019688613ed4565b519101558560005260158452611aec336040600020613f41565b611af883600954613f7c565b600955604051928352838301526040820152837fb03d4415051b223aac81e9cf0c228cff5c476e00dde5d1970fbfe34e72f82f6260603393a36040519384523393a360018055005b60405163d666109b60e01b815260048101839052602490fd5b50600c82116119c0565b60405163fa36c55360e01b81526000600482015260016024820152604490fd5b346105c25760003660031901126105c25760405180916011548083528360208080950180946011600052826000209460005b81601f8201106120815784611cd497549383831061206c575b838310612054575b83831061203c575b838310612024575b83831061200d575b838310611ff5575b838310611fdd575b838310611fc5575b838310611fad575b838310611f95575b838310611f7d575b838310611f65575b838310611f4d575b838310611f35575b838310611f1d575b838310611f05575b838310611eed575b838310611ed5575b838310611ebd575b838310611ea5575b838310611e8d575b838310611e75575b838310611e5d575b838310611e45575b838310611e2d575b838310611e15575b838310611dfd575b838310611de5575b838310611dcf575b50828210611db9575b828210611da3575b5010611d95575b509050949392940383613d52565b60405190819381601254938481520180936012600052836000209060005b818110611d815750505085611d08910386613d52565b6040519485946040860190604087525180915260608601929060005b818110611d67575050508482038584015251808252908201929160005b828110611d5057505050500390f35b835185528695509381019392810192600101611d41565b825160ff1685528897509385019391850191600101611d24565b825484529285019260019283019201611cf2565b60f81c815201849087611cc6565b6001919460ff8560f01c16815201930184611cbf565b6001919460ff8560e81c16815201930184611cb7565b9460ff85600194971c168152019301848b611cae565b91948160019160ff8760d81c16815201950191611ca6565b91948160019160ff8760d01c16815201950191611c9e565b91948160019160ff8760c81c16815201950191611c96565b91948160019160ff8760c01c16815201950191611c8e565b91948160019160ff8760b81c16815201950191611c86565b91948160019160ff8760b01c16815201950191611c7e565b91948160019160ff8760a81c16815201950191611c76565b91948160019160ff8760a01c16815201950191611c6e565b91948160019160ff8760981c16815201950191611c66565b91948160019160ff8760901c16815201950191611c5e565b91948160019160ff8760881c16815201950191611c56565b91948160019160ff8760801c16815201950191611c4e565b91948160019160ff8760781c16815201950191611c46565b91948160019160ff8760701c16815201950191611c3e565b91948160019160ff8760681c16815201950191611c36565b91948160019160ff8760601c16815201950191611c2e565b91948160019160ff8760581c16815201950191611c26565b91948160019160ff8760501c16815201950191611c1e565b91948160019160ff8760481c16815201950191611c16565b91948160019160ff8760401c16815201950191611c0e565b91948160019160ff8760381c16815201950191611c06565b91948160019160ff8760301c16815201950191611bfe565b91948160019160ff8760281c16815201950191611bf6565b91948160019160ff87831c16815201950191611bee565b91948160019160ff8760181c16815201950191611be6565b91948160019160ff8760101c16815201950191611bde565b91948160019160ff8760081c16815201950191611bd6565b91948160019160ff8716815201950191611bce565b926001919492955061040090875460ff8082168352808260081c1686840152808260101c16604084015280828a82828782828d82826060828260181c168188015282826080951c168488015260a096838360281c168882015260c09a848460301c168c830152848460381c1690820152838360401c16610100820152838360481c16610120820152838360501c16610140820152610160848460581c169101521c166101808d0152828260681c166101a08d0152828260701c166101c08d0152828260781c166101e08d01521c166102008a0152828260881c166102208a0152828260901c166102408a0152828260981c166102608a01521c16610280870152828260a81c166102a0870152828260b01c166102c0870152828260b81c166102e08701521c16610300840152808260c81c16610320840152808260d01c16610340840152808260d81c1661036084015280828a1c16610380840152808260e81c166103a08401528160f01c166103c083015260f81c6103e082015201950191019286928894959295611bb5565b346105c25760203660031901126105c257612227613b3e565b61222f613e8d565b6001600160a01b0316801561094257600480546001600160a01b031916821790557f1988eae50e3c966bb15e25079f549aabc769fff8405390f7914b211a9ca67972600080a2005b346105c25760003660031901126105c257612290613e8d565b612298614097565b6122a0614097565b60ff19600254166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a17fb5a73b054ac6db0c0e6429d3bb998734a660c422d158b35e9cd088b30d42f9e06020604051338152a1005b346105c25760003660031901126105c25760408051908101906001600160401b0382118183101761235557610f3e91604052600a81526928e280bfcba0e280bf2960b01b602082015260405191829182613c81565b634e487b7160e01b600052604160045260246000fd5b346105c2576020806003193601126105c257600435612388613fa7565b60008181526013835260409020546001600160a01b0392908316156105a957816000526013815260ff6005604060002001541660078110156104865780610b7e5750816000526013815282604060002054163314928060005416331490806005541633149060065416331490859286156124d5575b5082156124cd575b5081156124c5575b501561086e576106b89215612467577f43616e63656c6c656420627920686f7374207768696c6520696e204c6f6262796040519161244a83613d1c565b60278352820152661039ba30ba329760c91b604082015290614b72565b7f43616e63656c6c65642062792061646d696e2f6261636b656e642f76616c69646040519161249583613d1c565b603a83528201527930ba37b9103bb434b6329034b7102637b1313c9039ba30ba329760311b604082015290614b72565b90508461240d565b915085612405565b9250866123fd565b346105c25760203660031901126105c25760206124fb600435615771565b6125086040518092613b77565bf35b346105c25760203660031901126105c2576001600160401b0361252b613c6b565b1660005260176020526020604060002054604051908152f35b346105c25760003660031901126105c2576000546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600954604051908152f35b346105c25760403660031901126105c2576004356125a7613b54565b90806000526020916013835260018060a01b0390816040600020541615612740578260005260148452604060002091808216928360005285528060406000205416156127215780600054163314159081612711575b81612702575b5061086e57826000526013845260ff60056040600020015416600781101561048657600581149081156126f7575b506126de578260005260148452604060002082600052845260ff60406000205460a81c16156126c05750817f56361dafe421b3aba201873229b0c108cb63c45289b37d3d30b68d7ffa876dfe61001b9482600052601481526040600020846000528152604060002060ff60a81b1981541690558260005260138152600760406000200154604051908152a3614d32565b604051638c5355ab60e01b8152908190610577908560048401613fca565b60405163712286e160e11b815260048101849052602490fd5b600691501485612630565b90506006541633141585612602565b80915060055416331415906125fc565b506040516379a6671160e01b8152908190610577908560048401613fca565b60405163d13b267760e01b815260048101849052602490fd5b346105c25760a03660031901126105c257604435602435600435608435606435612781613e8d565b603c8510801561282d575b8015612823575b8015612818575b801561280d575b6127fb577f2621620528f26fb75529114de7b19d9f01b11bb4a9bd0ad57997a33cf8141ec09460a09484600f558060105581600c5582600d5583600e556040519485526020850152604084015260608301526080820152a1005b6040516325c3636760e01b8152600490fd5b5061012c84106127a1565b5061012c831061279a565b50601e8210612793565b50603c811061278c565b346105c25760003660031901126105c257602060405160058152f35b346105c25760203660031901126105c25761286c613b3e565b612874613e8d565b6001600160a01b0316801561094257600580546001600160a01b031916821790557f9177ce6b43739f0c598396fcbb3deeeddefeee033f92b4fa36b1c5ec1afdd14e600080a2005b346105c257602090816003193601126105c2576004356128da615710565b5060008181526013845260409020546001600160a01b0392908316156105a95781600052601384526040600020906040519361291585613ce5565b825416845260018201548585015260ff6002830154166040850152600382015460608501526004820154608085015260ff6005830154169160a0850191600784101561048657600b6129c19260ff926129dd968652600682015460c08a0152600782015490890152600881015461010089015282600982015460018060401b0381166101208b015260401c161515610140890152600a8101546101608901520154166101808601613ec8565b82600052601585526129e4604060002060405193848092614cec565b0383613d52565b5191600783101561048657612a3494610f3e93600503612a555760189160005252612a46612a1f612a26604060002060405192838092614cec565b0382613d52565b915b60405195868096613b84565b6101e0806101a0870152850190613c2e565b908382036101c0850152613c2e565b5050612a46604051612a6681613d37565b6000815291612a28565b346105c25760003660031901126105c257612a89613e8d565b600080546001600160a01b0319811682556001600160a01b0316600080516020615f238339815191528280a3005b346105c25760403660031901126105c257612ad0613b54565b612ad8613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e575060043560009081526014602090815260408083206001600160a01b0385811685529252909120541615612e09576000546001600160a01b031633141580612df4575b80612ddf575b61086e576004356000526014602052604060002060018060a01b03821660005260205260406000209081549160ff8360b01c16612ce15760043560005260136020526040600020601560205260406000206018602052604060002090601960205260406000205492612be16001820154925483614010565b90612710612bf3600483015484614010565b0492600098600080968054975b888110612d9f575b505060ff8260a81c16159182612d7d575b5081612d61575b15612d2d575050600b015460ff1660038110156104865760028103612d125750612c4b929350614003565b81612cff575b50505b8215612ce1576009548311612cd757612c6f83600954614003565b6009555b612c86836001600160a01b038416615230565b805460ff60b01b1916600160b01b1790556040519182526001600160a01b031690600435907f6aab79494515e500f72558d888a9426fa23041c0bf39b592fdd8809c98eabef590602090a360018055005b6000600955612c73565b60405163f54d5f5360e01b8152806105778460048035908401613fca565b612d0a929450614023565b918380612c51565b9150506001915014612d25575b50612c54565b925083612d1f565b94509450505050612d3e5750612c54565b909250611388908181029181830414901517156104d55761271090049183612d1f565b905060ff600b8401541660038110156104865760011490612c20565b909150600360ff8260a01c1610156104865760a01c60ff16600114908b612c19565b612da98183613f29565b905460039190911b1c6001600160a01b03908116908d1614612dd357612dce90613eb9565b612c00565b50505060018b80612c08565b506006546001600160a01b0316331415612b69565b506005546001600160a01b0316331415612b63565b6040516379a6671160e01b81529081906105779060048035908401613fca565b346105c25760403660031901126105c257600435612e45613b54565b612e4d613f89565b612e55613fa7565b6000828152601360205260409020546001600160a01b03908116156127405782600052601360205260ff6005604060002001541660078110156104865760028103612ec7575080600554163314159081612eb8575b5061086e576106b891614307565b90506000541633141583612eaa565b604051639cd1128160e01b815260048101859052600260248201526064916105a7906044830190613b6a565b346105c25760203660031901126105c257600435612f0f613f89565b612f17613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff6005604060002001541660078110156104865780612fd75750806000526013602052612f76600660406000200154600f5490613f7c565b42106106be576040516106b891612f8c82613d1c565b603382527f43616e63656c6c65642064756520746f204c6f6262792070686173652074696d60208301527232b7baba1014383ab13634b19031b0b636149760691b6040830152614b72565b906105a760649260405192639cd1128160e01b84526004840152600060248401526044830190613b6a565b346105c25760003660031901126105c2576020600354604051908152f35b346105c25760203660031901126105c25760043561303c613f89565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600581149081156130a9575b506130915761001b906140b6565b6024906040519063712286e160e11b82526004820152fd5b600691501482613083565b346105c25760003660031901126105c257602060ff600254166040519015158152f35b346105c25760603660031901126105c2576130f0613c6b565b6130f8613b54565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680156105c25733036105c2576106b89061313b613fa7565b613143613f89565b604435906152d0565b346105c2576020806003193601126105c25760043560008181526013835260409020546001600160a01b0316156106fb57806000526013825260ff600560406000200154166007811015610486576005036131d557906129dd91600052601881526131c1604060002060405193848092614cec565b610f3e604051928284938452830190613c2e565b601360449282600052526105a760ff6005604060002001541660405192638e84d22560e01b845260048401526024830190613b6a565b346105c25760203660031901126105c257600435613227613e8d565b6103e8808211613262577fdc4e4be378c228e1b7b13787e1d0620609304c2f71ed3a6ee69e4aae99d58cd960208380600855604051908152a1005b6040516373ab893560e11b815260048101929092526024820152604490fd5b346105c25760203660031901126105c25760043561329d615710565b506000818152601360205260409020546001600160a01b0391908216156106fb5760005260136020526040600020604051916132d883613ce5565b81541682526001810154602083015260ff6002820154166040830152600381015460608301526004810154608083015260ff60058201541692600784101561048657600b6133839260ff926101a09660a0870152600682015460c0870152600782015490860152600881015461010086015282600982015460018060401b03811661012088015260401c161515610140860152600a8101546101608601520154166101808301613ec8565b6125086040518092613b84565b346105c25760003660031901126105c2576020604051600c8152f35b346105c25760403660031901126105c2576004356133c8613b54565b906040516133d581613cca565b600081526000606060209282848201528260408201520152816000526013815260018060a01b03918260406000205416156106fb578060005260148252604060002090838516918260005283528360406000205416156134a65760809450600052601482526040600020906000528152604060002091600183549182169301549160405161346281613cca565b84815260608282019160ff8560a81c161515835260ff604082019560b01c161515855201938452604051948552511515908401525115156040830152516060820152f35b6040516379a6671160e01b815290819061057790879060048401613fca565b346105c25760403660031901126105c2576024803560043560078210156105c2576134ee613e8d565b6000818152601360205260409020546001600160a01b03161561369d57806000526013602052604060002091600583019283549360ff8516948315613678575b60018414613664575b6005841480613637575b6105c25760ff191660ff841617905560028214600081801561362a575b818115613605575b506135b2575b50505061358a906135806040518095613b6a565b6020840190613b6a565b7f4c2908af6339a7cecca8470b17951a83d590ff59671ec6a761871d3c69fb76ce60403393a3005b4260088401556135f05761358a93949550806135e4575b6135d7575b8493928161356c565b60076001910155846135ce565b506007810154156135c9565b85634e487b7160e01b60005260216004526000fd5b9050613615576004841481613566565b86634e487b7160e01b60005260216004526000fd5b505060006003841461355e565b5060ff600b84015416600381101561364f5715613541565b87634e487b7160e01b60005260216004526000fd5b600786101561361557851561353757600080fd5b60078610156136155785158015613693575b61352e57600080fd5b506006861461368a565b90506040519063d13b267760e01b82526004820152fd5b346105c25760203660031901126105c2576136cd613b3e565b6136d5613e8d565b600b80546001600160a01b0319166001600160a01b039290921691821790556002600080516020615fa3833981519152600080a3005b346105c2576020806003193601126105c25760043590613729613f89565b613731613fa7565b60008281526013825260409020546001600160a01b0392908316156106fb57806000526013825282604060002054168033036139a35750806000526013825260406000206005810160ff81541660078110156104865760058114908115613998575b506126de5760038083019283549160006004830154908115158061398b575b613942575b6137c2915084614003565b6009549093908111613937576137da90600954614003565b6009555b856000526015875260406000209260019889809401926138018454875490614010565b9586613881575b858b8b60008c8c600660ff19825416179055556033604051928084528301527f43616e63656c6c656420627920686f73743b206665652064656475637465642c604083015272103932b33ab7323990383937b1b2b9b9b2b21760691b6060830152600080516020615ea383398151915260803393a38055005b6000865b613890575b50613808565b81548110156139325780868d8d876138ab6139029688613f29565b905490891b1c1691816000526014815260406000208360005281526138df8d6138da8c60406000209754614010565b614023565b9081613908575b5050825460ff60b01b1916600160b01b1790925550613eb99050565b86613885565b81613922600080516020615ee38339815191529386615230565b604051908152a38f8d8f806138e6565b61388a565b5060006009556137de565b506139506127109185614010565b0480151580613981575b1561397657806139716137c2928b60075416615230565b6137b7565b506137c260006137b7565b508381111561395a565b50896007541615156137b2565b600691501486613793565b6040516317a9a4f960e21b815291829161057791339060048501613fe3565b346105c25760003660031901126105c2576004546040516001600160a01b039091168152602090f35b346105c25760403660031901126105c257600435600081815260136020526040902054602435916001600160a01b03918216156106fb576000526015602052604060002080548310156105c257602092613a4491613f29565b9190546040519260031b1c168152f35b346105c25760003660031901126105c257602060405161012c8152f35b346105c25760003660031901126105c257600a546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576005546040516001600160a01b039091168152602090f35b346105c25760203660031901126105c257613adc613b3e565b613ae4613e8d565b600a80546001600160a01b0319166001600160a01b039290921691821790556001600080516020615fa3833981519152600080a3005b346105c25760003660031901126105c257600b546001600160a01b03168152602090f35b600435906001600160a01b03821682036105c257565b602435906001600160a01b03821682036105c257565b9060078210156104865752565b9060038210156104865752565b90613c2c9160018060a01b0381511682526020810151602083015260ff60408201511660408301526060810151606083015260808101516080830152613bd260a082015160a0840190613b6a565b60c0818101519083015260e080820151908301526101008082015190830152610120808201516001600160401b03169083015261014080820151151590830152610160808201519083015261018090810151910190613b77565b565b90815180825260208080930193019160005b828110613c4e575050505090565b83516001600160a01b031685529381019392810192600101613c40565b600435906001600160401b03821682036105c257565b6020808252825181830181905290939260005b828110613cb657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501613c94565b608081019081106001600160401b0382111761235557604052565b6101a081019081106001600160401b0382111761235557604052565b60a081019081106001600160401b0382111761235557604052565b606081019081106001600160401b0382111761235557604052565b602081019081106001600160401b0382111761235557604052565b601f909101601f19168101906001600160401b0382119082101761235557604052565b6001600160401b0381116123555760051b60200190565b81601f820112156105c257803591613da383613d75565b92613db16040519485613d52565b808452602092838086019260051b8201019283116105c2578301905b828210613ddb575050505090565b81356001600160a01b03811681036105c2578152908301908301613dcd565b60606003198201126105c257600435916001600160401b036024358181116105c25783613e2991600401613d8c565b926044359182116105c257613e4091600401613d8c565b90565b9181601f840112156105c2578235916001600160401b0383116105c2576020808501948460051b0101116105c257565b6001600160a01b0391821681529116602082015260400190565b6000546001600160a01b03163303613ea157565b60405163118cdaa760e01b8152336004820152602490fd5b60001981146104d55760010190565b60038210156104865752565b90600381101561048657815460ff60a01b191660a09190911b60ff60a01b16179055565b601254811015613f1357601260005260206000200190600090565b634e487b7160e01b600052603260045260246000fd5b8054821015613f135760005260206000200190600090565b8054600160401b81101561235557613f5e91600182018155613f29565b819291549060031b9160018060a01b03809116831b921b1916179055565b919082018092116104d557565b60ff60025416613f9557565b60405163d93c066560e01b8152600490fd5b600260015414613fb8576002600155565b604051633ee5aeb560e01b8152600490fd5b9081526001600160a01b03909116602082015260400190565b9081526001600160a01b0391821660208201529116604082015260600190565b919082039182116104d557565b818102929181159184041417156104d557565b811561402d570490565b634e487b7160e01b600052601260045260246000fd5b9190811015613f135760051b0190565b3560ff811681036105c25790565b81811061406c575050565b60008155600101614061565b9080614082575050565b613c2c91600052602060002090810190614061565b60ff60025416156140a457565b604051638dfc202b60e01b8152600490fd5b60008181526013602052604090818120600581019060ff82541691600882018054906007851015938461427d57600286149283156142b657600c546140fa91613f7c565b61012c81018091116142a2574210614291576007019184897fb5cab6c1716e65f60a857bf7351124136e99963a75d21d778e7727144e35231360c086548c5190614144828d613b6a565b602082015260608d820152602860608201527f5075626c69632074696d656f75742066616c6c6261636b20696e766f6b656420608082015267313c903ab9b2b91760c11b60a0820152a261427d57600386148015614270575b1561420057505050506141ec576004146141cd575b50506141bd81614d32565b6141ca57613c2c906151e1565b50565b6141e591838252601660205281208054918155614078565b38806141b2565b634e487b7160e01b82526021600452602482fd5b919390929695945061425c57614219575b505050505050565b61422286614d32565b61421157600080516020615f8383398151915294600360ff19825416179055429055549082519182526020820152a2388080808080614211565b634e487b7160e01b84526021600452602484fd5b508694506004861461419d565b634e487b7160e01b87526021600452602487fd5b87516326c015ab60e21b8152600490fd5b634e487b7160e01b88526011600452602488fd5b879550600387036142cd57600d546140fa91613f7c565b879550600487036142e457600e546140fa91613f7c565b8851638a8fcb0760e01b8152600481018b90526044906105a7602482018a613b6a565b906000908282526020601381526040928381209060078201549260018060a01b039081861692831515808091614640575b80614621575b6145d5575b8189918b959493968297839888855260158752858520858154905b8181106144f957505050836144db575b836144ba575b8361446b575b83614460575b5082614455575b5081614449575b8161443c575b506143eb575b505050506143a89150614d32565b6143e457600080516020615f83833981519152938160056143df9301600360ff198254161790556008429101555192839283613fca565b0390a2565b5050505050565b7fcb5e8d6d1fd0c2bd9eca2e2546d3d92f27a2b10728927512cb00ce7bdc8738ad938582526014845282822096169586825283522060ff60a81b1981541690558751868152a338858180878161439a565b9050848716141538614394565b8786161515915061438e565b600114915038614387565b600114925038614380565b94509150919394958452601485528a8420828552855260ff8b85205460a01c1660038110156144a657918b939160018e98979694149261437a565b634e487b7160e01b85526021600452602485fd5b8885526014875285852083865287528585205460a81c60ff16159350614374565b8885526014875285852083865287528585205488161515935061436e565b919395978a9b889a9b929496985260148b52808a206145188487613f29565b939054600394851b1c168b528b5289205460ff90818160a81c16614554575b50505061454390613eb9565b918f9998979593918f97959361435e565b60a09693961c16818110156145c1576002810361458357505061457961454391613eb9565b935b903880614537565b919491600114614598575b506145439061457b565b94614543919b506145a98b91613eb9565b956145b48d86613f29565b9054911b1c169a9061458e565b634e487b7160e01b8a52602160045260248afd5b89825260148352888220858352835288822060ff60a81b198154169055848a7f8cc6a126ca79d9271b80277e31507795e87602b1d7ce66e4451363f929f4811c858c518b8152a3614343565b5089825260148352888220858352835260ff8983205460a81c1661433e565b506014835288822085835283528389832054161515614338565b9061466482613d75565b6146716040519182613d52565b8281528092614682601f1991613d75565b0190602036910137565b8051821015613f135760209160051b010190565b90929183518151036105c257600082815260209460138652604091828120936146de60078601549360158a52858420601493848c5287862092615b74565b948486015190600191828114806149a2575b80614996575b1561476957505050968082879899887f47e734b70f4e742ea488db9e70df12c13e01b4d6cc4a500191e3b724b6a83bdd979695999899528252848082209160018060a01b0392838b5116825284522060ff60a81b198154169055875116960151908351928352820152a36141bd81614d32565b918094979192118061498a575b15614942576060019287600080516020615e83833981519152878c6147aa885183519384938c85528401528b830190613c2e565b0390a287875260168a526147c5868820805490898155614078565b87875260168a5285872093879384835b6148c6575b5050505060028210600014614825575050508584600080516020615ec383398151915261481c9697986016948651908152a28584525281208054918155614078565b6141bd81614d32565b60058199969495939901600460ff198254161790556008429101556148498161465a565b925b8181106148915750507f9058488d8f4ca0c7ed50681193e9b6dddd19f1cb87c905f3fc1e73fb6cb6af80949596509081806143df93519586958652850152830190613c2e565b8061489f6148c1928b613f29565b905460039190911b1c6001600160a01b03166148bb828761468c565b52613eb9565b61484b565b82515181101561493d578a8a52818d528c898b2060018060a01b0391826148ee85885161468c565b51168d525260ff8a8c205460a81c16614912575b5061490c90613eb9565b836147d5565b81966149316149369261492961490c95885161468c565b51168a613f41565b613eb9565b9590614902565b6147da565b5050505061497e600080516020615e83833981519152938386979894519161496983613d37565b82528080519586958652850152830190613c2e565b0390a26141bd81614d32565b50898101511515614776565b508a88015115156146f6565b5087516001600160a01b031615156146f0565b909182518151036105c2576000908282526020906013825260409160078385200154956016928383528486208054908115614b5d57505090614a099187875260158452858720906014855286882092615b74565b8051949687956001600160a01b03908116614af0575b87989184879261481c9a94600185880151149081614ae3575b81614ad6575b81614ace575b5015614ab057907fbc2df99d70b88b7387193e2ee99061e3b292151b21724b0b34f9a7fd6aa88d09939291858b5260148252838b20818851168c528252838b2060ff60a81b198154169055865116950151825191825286820152a35b8584525281208054918155614078565b5091600080516020615ec383398151915293945051908152a2614aa0565b905038614a44565b8784015115159150614a3e565b8751831615159150614a38565b865b8888528585528688208054821015614b4b57614b0f828492613f29565b919054818751169260031b1c1614614b2f57614b2a90613eb9565b614af2565b5087985061481c979184879260015b9b50925050919750614a1f565b505087989184879261481c9a94614b3e565b955095935050505061481c9394508155614078565b600081815260209060138252604091828220601582528383209160039283830193845460095481818111600014614cdb575050866009555b80151580614cd1575b614bf0575b50505050906005600080516020615ea383398151915294939201600660ff19825416179055555180614beb339582613c81565b0390a3565b4710614cc057600191828501549081614c0a575b50614bb8565b87845b614c18575b50614c04565b8a8254821015614cba579089614c8d92600080516020615ee3833981519152898e80614c44878a613f29565b90548688526014808652838920928e1b9190911c6001600160a01b03908116808a529286528389205492989216614c93575b505050614c838986615230565b51888152a3613eb9565b84614c0d565b8682528452818120878252845220805460ff60b01b1916600160b01b179055808f38614c76565b50614c12565b865163786e0a9960e01b8152600490fd5b5082541515614bb3565b614ce491614003565b600955614baa565b90815480825260208092019260005281600020916000905b828210614d12575050505090565b83546001600160a01b031685529384019360019384019390910190614d04565b6000818152601560205260408120805492918080805b86811061513957506000911590818061512e575b156151035750505050600292600193845b614d79575b5050505090565b836000526013602052604060002091600583019060ff1991600583825416179055600b84019160038410156104865782541660ff8416178255856000526018602052614dcf604060002080549060008155614078565b8560005260186020526040600020936000926014602052604060002060405191606083019083821060018060401b038311176123555760ff91604052600084526000602085015260006040850152614e406003860154612710614e36600489015483614010565b0490818752614003565b602085015260006040850152541660038110156104865760011461504b575b508051600a60406020840151930151930192835580614ff4575b509054818111614fea57614e8c91614003565b945b60005b828110614f385750505060009381614f25575b50506003811015610486577f4fd7802900f80ee62d07cf9efe951278d8c503d4cbb86ac8ea4d2820d9be00a592614efe614eed9360018414614f10575b60405194858095613b77565b606060208501526060840190614cec565b9060408301520390a238808080614d72565b86600052601960205282604060002055614ee1565b614f30929450614023565b913880614ea4565b8760005260146020526040600020614f508284613f29565b60018060a01b0391549060031b1c1660005260205260406000205460ff8160a81c169081614fc2575b50614f8d575b614f8890613eb9565b614e91565b92614fba614f8891614931614fa28786613f29565b905460039190911b1c6001600160a01b031689613f41565b939050614f7f565b9050600386101561048657600360ff8260a01c1610156104865760a01c60ff16851438614f79565b5050600094614e8e565b6007546001600160a01b0316908115615039576009546150299290821161502f5761502182600954614003565b600955615230565b38614e79565b6000600955615230565b604051637728d5eb60e01b8152600490fd5b87549060005b82811061505f575050614e5f565b615069818b613f29565b60018060a01b0391549060031b1c166000528160205260406000205460ff8160a81c161590816150e3575b506150a8575b6150a390613eb9565b615051565b600185015490611388918281029281840414901517156104d5576150d76127106150a393046040870151613f7c565b6040860152905061509a565b9050600360ff8260a01c1610156104865760a01c60ff1660011438615094565b959295159081615125575b5061511a575b84614d6d565b506001935083615114565b9050153861510e565b506001811015614d5c565b85600052601460205260406000206151518287613f29565b9190549160039260018060a01b0391841b1c1660005260205260406000205460ff90818160a81c1661518f575b50505061518a90613eb9565b614d48565b60a01c169081101561048657600281036151bb5750906151b161518a91613eb9565b915b90388061517e565b6001146151cc575b61518a906151b3565b916151d961518a91613eb9565b9290506151c3565b806000526013602052600080516020615f438339815191526020604060002060058101600260ff19825416179055600781019061521e8254613eb9565b809255600842910155604051908152a2565b81156152cc578147106152ba57600080808094819460018060a01b03165af1903d156152b4573d906001600160401b0382116152a0576040519161527e601f8201601f191660200184613d52565b825260203d92013e5b1561528e57565b604051633d2cec6f60e21b8152600490fd5b634e487b7160e01b81526041600452602490fd5b50615287565b60405163786e0a9960e01b8152600490fd5b5050565b907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316338190036156f1575060018060401b038083166000526017602052604060002054926003548085101590816156e7575b506156e157600084815260136020526040902080549093906001600160a01b0316156143e457600984018054848416858216036156c75760ff6005870154166007811015610486576001036156be5760ff8160401c166156be5760ff60401b1916600160401b179055818316857fae7cceb24659f070aa32d8aa9ced46f64e3ce6a1bcbb5ac917ee33d3188abf7d600080a384600052601460205260406000206015602052604060002090815492600184106105c2576153ea8461465a565b906000805b86811061563a575080156105c25790615456869260405160208101908482527f44454d574954434845535f524f4c455f41535349474e4d454e545f53414c545f6040820152630ac665c760e31b60608201526044815261544e81613cca565b519020615b6a565b926000936000805b8582106155d6575b505050506001600160a01b03831615615564575b505060005b8481106154fa57505050505016600052601760205260006040812055600260ff1960058301541617600582015560016007820155600842910155600080516020615f438339815191526020604051837fb607ccb3d7ced88cd923d2c5f091149c8ecc5fb24209afa25a43ddf94253801d600080a260018152a2565b806155086155559286613f29565b90546001600160a01b0385811660039390931b9190911c160361555a5761493160025b6155358388613f29565b60018060a01b0391549060031b1c16600052856020526040600020613ed4565b61547f565b614931600161552b565b6155bd9250906155b791604051602081019182527f44454d574954434845535f57495443485f46414c4c4241434b5f53414c545f566040820152620665c760eb1b60608201526043815261544e81613cca565b83613f29565b905460039190911b1c6001600160a01b0316833861547a565b839495506155e9826155f093949561468c565b5190613f7c565b908882841061560d575061560390613eb9565b889493929161545e565b94955091505061561e915085613f29565b905460039190911b1c6001600160a01b03169138808080615466565b906064906156488388613f29565b60018060a01b0391549060031b1c166000528560205260016040600020015480615692575b50816156879161568d93615681868961468c565b52613f7c565b91613eb9565b6153ef565b9081830291838304036104d5576127108204830183116104d5576127109091049091019061568761566d565b50505050505050565b60405163f7db5e9d60e01b81528585166004820152602490fd5b50505050565b905015153861532b565b60405163b16a0a0b60e01b815290819061057790339060048401613e73565b6040519061571d82613ce5565b816101806000918281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e0820152826101008201528261012082015282610140820152826101608201520152565b600081815260136020908152604080832054909291906001600160a01b03908116156158425760148252838320338452825233908484205416036157f2578382526013815282822060ff6009820154851c1615908115615828575b81156157f9575b506157f25760ff938252601481528282209033835252205460a01c1690565b5091505090565b60ff91506005015416600781101561581457600114386157d3565b634e487b7160e01b83526021600452602483fd5b905060ff600582015416600781101561425c5715906157cc565b505091505090565b600090808252601360205260ff60056040842001541660078110156158145760041461588757506040519061587e82613d37565b80825236813790565b604082613e4092612a1f945260166020522060405192838092614cec565b90601154821015613f13576011600052600582901c7f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680191601f1690565b6000926001600160a01b0392839190821680615af1575b50169182615a77575b5050506012548015615a70576011918254918215615a39576001830190818411615a245703615a1c5760009260005b83811061598c5750600019830190838211615978575061595360ff916158a5565b90549060031b1c1610615964575090565b61596e9150613ef8565b90549060031b1c90565b634e487b7160e01b60005260045260246000fd5b82151580615a00575b6159a8575b6159a390613eb9565b615932565b801580156159c4575b1561599a579250505061596e9150613ef8565b5060001981018181116159eb576159dc60ff916158a5565b90549060031b1c1683116159b1565b82634e487b7160e01b60005260045260246000fd5b5060ff615a0c826158a5565b90549060031b1c16831115615995565b505050600090565b84634e487b7160e01b60005260045260246000fd5b5060009250159050613e40575060126000527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34445490565b5050600090565b602460209260405194859384926370a0823160e01b84521660048301525afa60009181615abb575b50615aac575b8080615903565b615ab591613f7c565b38615aa5565b90916020823d8211615ae9575b81615ad560209383613d52565b81010312615ae65750519038615a9f565b80fd5b3d9150615ac8565b6040516370a0823160e01b81528486166004820152919250602090829060249082905afa859181615b33575b50615b2b575b9083916158fa565b935082615b23565b9091506020813d8211615b62575b81615b4e60209383613d52565b81010312615b5e57519038615b1d565b8580fd5b3d9150615b41565b811561402d570690565b9390604092835194615b8586613d01565b6060808701528594600091828852602083818a015283838a01528360808a01528454988915615e745750615bb88961465a565b96845b8b51811015615cd0576001600160a01b0380615bd7838f61468c565b51169080615be5848c61468c565b51169188528585528688205481811615159081615cc2575b5080615cb9575b80615ca8575b80615c95575b615c25575b5050615c2090613eb9565b615bbb565b878b8a8f5b8310615c39575b505050615c15565b83615c45848793613f29565b90549060031b1c1614615c645750615c5c90613eb9565b8b8a8f615c2a565b615c20949350819250615c7d615c8a92615c839261468c565b51613eb9565b918c61468c565b529038808b8a615c31565b5081885260ff8789205460a81c16615c10565b508188528087892054161515615c0a565b50811515615c04565b60ff915060a81c1638615bfd565b509950969793929450615ce28461465a565b938596865b828110615d6e57505050505050615cfd8361465a565b91606086019283525b838110615d4657505083516001600160a01b03161515929150829050615d39575b50615d2f5790565b6001608082015290565b9050810151151538615d27565b615d69906001600160a01b03615d5c828561468c565b51166148bb82865161468c565b615d06565b615d788187613f29565b90546001600160a01b0360039290921b1c8116808a52858c52868a20549091811615908115615e65575b50615e5b57615db1828461468c565b5180615dc8575b5050615dc390613eb9565b615ce7565b9091998b8d01908151808411600014615e1557505052808b5260019081868d0152875115615e0157878b015297615dc3905b9038615db8565b634e487b7160e01b89526032600452602489fd5b909b92615dc394925014615e2a575b50615dfa565b868d01615e378151613eb9565b9052848b1015615e2457615e54615e4d8c613eb9565b9b8a61468c565b5238615e24565b50615dc390613eb9565b60ff915060a81c161538615da2565b98505050505050509250509056fe61c0b92f778d9580f61c1368f619231971929502139a169537859d147a3d8db17aaa838aec7a66b4c97d35cf9ef5095d74305ac4327c0bd09378674920a44e58ca623bc6d60c4de3c68b9587a6f35819c7232f5f79176c981fd7835daa19f812ff3116559cac6c47442d1f2083dc842de1f35c5f87fd25d7978d509fa59c1b1803dfbe1fcb4e2d61f3b4a0c93d8f814c92250618b3387f396cc9fc9fafe2c2038be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e06b40fcd6b8efc2e628b2efa06aea41f9d48edcf9f787184c307080c83cd22084481df23c457d78a89f0f5fabd340eb449a09d8690c045c3d8d567988ef019540a2361be2ee66c1a30168107b5157b30d03384356ba97fffc7ab6aeabdc6d211824e30ae881d6fa40479d2f3a64728e211e5620dab64f70e0043787baacd89f0ea2646970667358221220f8f18cc8a587b1d483db5026deda406d10c7898296efb1196cb2ec3d65de9c4d64736f6c6343000814003300000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e32000000000000000000000000041c5c4a0e112717101ff1da950f6ca7ae4f9291900000000000000000000000000000000000000000000000000000000000001f4
Deployed Bytecode
0x608080604052600436101561001d575b50361561001b57600080fd5b005b60e0600035811c91826202640714613b1a575081630947d30214613ac35781630d72d57f14613a9a5781631154d19014613a715781631562c16b14613a5457816315ec12cd146139eb5781631d9023cb146130b457816322e1dcd01461073157816327bebe88146139c257816330041ba81461370b57816332f6dc7c146136b4578163352ce424146134c55781633ccd10e9146133ac5781634411b3eb1461339057816347e1d550146132815781634f2c0aec1461320b578163515371a31461314c57816352a5f1f8146130d75781635c975abb146130b457816361841cd614613020578163673ee37c146130025781636878b93914612ef35781636919f4d11461151c5781636a6354e914612e295781636bf4da7414612ab7578163715018a614612a705781637cea0c75146128bc5781638267660f1461285357816385f07bbe14612837578163867b49f21461275957816388902d691461258b57816389fc892d1461256d5781638da5cb5b1461254457816390de9f121461250a578163917e7964146124dd578163919cc6a01461236b578163922395b81461230057816392f9c0671461227757816398ddf5841461220e5781639f3e7e4214611b8357508063ad2dfc6514611981578063adcf8110146118c4578063ae169a5014611561578063afb218f51461151c578063b8ca266c1461144b578063bc063e1a1461142e578063c073343b146113c5578063c20860af14611339578063c6b00c7b14610f94578063d18658cd14610f76578063d3780db114610f42578063daef359914610eed578063ddca3f4314610ecf578063e474ee6114610ea6578063e5ed1d5914610baa578063e9173b3a14610954578063ee9c1c7e146108d3578063f0c6fc45146107ec578063f2fde38b14610778578063f6f071f11461075a578063f9e7ad8514610731578063fc9fefb014610713578063fcead4e1146105c75763fe32b0ba146102eb573861000f565b346105c25760403660031901126105c257600435610307613b54565b90806000526020906013825260018060a01b03918260406000205416156105a957816000526013815260ff600560406000200154166007811015610486576005810361057b5750816000526014815260406000209383811694856000528252836040600020541615610559575081600094939452601381526040600020601482526040600020846000528252604060002092600052601582526040600020946018835260406000209460198452604060002054926001976103cd89830154915482614010565b9261271090816103e1600486015487614010565b049860009a6000926000988254995b8a8110610521575b50505050549060ff8260a81c16159182610506575b50816104eb575b1561049c57505050600b015460ff169060038210156104865760409786926002810361046d575050610447929350614003565b8161045a575b50505b8351928352820152f35b610465929450614023565b91388061044d565b92509250501461047e575b50610450565b925038610478565b634e487b7160e01b600052602160045260246000fd5b94509450975050506104b3575b5060409350610450565b909250611388938481029481860414901517156104d5576040930491386104a9565b634e487b7160e01b600052601160045260246000fd5b905060ff600b850154166003811015610486578b1490610414565b60ff91925060a01c166003811015610486578b14903861040d565b828261052d8387613f29565b90549060031b1c16146105485761054390613eb9565b6103f0565b5050505090508a90388080806103f8565b6040516379a6671160e01b8152908190610577908560048401613fca565b0390fd5b604051639cd1128160e01b815260048101849052600560248201526064916105a7906044830190613b6a565bfd5b60405163d13b267760e01b815260048101839052602490fd5b600080fd5b346105c25760203660031901126105c2576004356105e3613e8d565b6105eb613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600181036106d0575080600052601360205261064d60086040600020015460105490613f7c565b42106106be576040516106b89161066382613d1c565b603e82527f43616e63656c6c65642062792061646d696e3a2050797468204e6574776f726b60208301527f2072616e646f6d6e65737320726571756573742074696d6564206f75742e00006040830152614b72565b60018055005b6040516326c015ab60e21b8152600490fd5b906105a760649260405192639cd1128160e01b84526004840152600160248401526044830190613b6a565b6024906040519063d13b267760e01b82526004820152fd5b346105c25760003660031901126105c2576020600c54604051908152f35b346105c25760003660031901126105c2576007546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600d54604051908152f35b346105c25760203660031901126105c257610791613b3e565b610799613e8d565b6001600160a01b039081169081156107d3576000548260018060a01b031982161760005516600080516020615f23833981519152600080a3005b604051631e4fbdf760e01b815260006004820152602490fd5b346105c2576107fa36613dfa565b90610803613f89565b61080b613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff600560406000200154166007811015610486576004810361088f575080600554163314159081610880575b5061086e576106b8926149b5565b60405163ea8e4eb560e01b8152600490fd5b90506000541633141584610860565b604051639cd1128160e01b8152600480820187905260248201526064916105a7906044830190613b6a565b60405163d13b267760e01b815260048101859052602490fd5b346105c25760203660031901126105c2576108ec613b3e565b6108f4613e8d565b6001600160a01b0390811690811561094257600680546001600160a01b031981168417909155167fae4e8ea4dbc2ca24cbdb325a1b328d6a2401757cb7e2be71fbc628f262771cd5600080a3005b60405163d92e233d60e01b8152600490fd5b6020806003193601126105c25760043561096c613f89565b610974613fa7565b60008181526013835260409020546001600160a01b03908116156105a957816000526013835260ff6005604060002001541660078110156104865780610b7e575081600052601383526040600020926015815260406000205460ff60028601541680911015610b605750826000526014815260406000203360005281528160406000205416610b44576001840154803403610b265750610a1e82600a541683600b541690336158e3565b9360405190610a2c82613d01565b3382528282019160008352604081019460018652606082019360008552608083019189835288600052601487526040600020336000528752604060002093511660018060a01b03198454161783555190600382101561048657610ae0600395610ac5600080516020615f0383398151915299610aaa60019688613ed4565b51865460ff60a81b191690151560a81b60ff60a81b16178655565b51845460ff60b01b191690151560b01b60ff60b01b16178455565b519101558460005260158352610afa336040600020613f41565b01610b06348254613f7c565b9055610b1434600954613f7c565b6009556040519384523393a360018055005b6044906040519063fa36c55360e01b82523460048301526024820152fd5b604051631a0af6eb60e21b815280610577338660048401613fca565b836044916040519163d9e1ab7360e01b835260048301526024820152fd5b604051639cd1128160e01b815260048101849052600060248201526064916105a7906044830190613b6a565b346105c2576020806003193601126105c257600435610bc7613f89565b610bcf613fa7565b60008181526013835260409020546001600160a01b03908116156105a95781600052601383528060406000205416803303610e875750816000526013835260ff6005604060002001541660078110156104865780610b7e57508160005260138352604060002092600984019360018060401b0380865416610e6e57846000526015835260406000205460058110610e46578460045416948515610e34577f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e320169460405191631711922960e31b835281600484015285836024818a5afa928315610dca57600093610df5575b506001600160801b0390921691478311610dd657859291604491604051858101918b835242604083015260608201523360601b608082015260748152610cff81613d01565b5190209260405198899485936319cb825f60e01b8552600485015260248401525af18015610dca57600090610d84575b600080516020615f63833981519152945016948560018060401b031982541617905584600052601782528360406000205560058101600160ff198254161790556008429101556040519384523393a360018055005b50928281813d8311610dc3575b610d9b8183613d52565b810103126105c257519280841684036105c257600080516020615f6383398151915293610d2f565b503d610d91565b6040513d6000823e3d90fd5b6040516322f2dfd560e11b815260048101849052476024820152604490fd5b9092508581813d8311610e2d575b610e0d8183613d52565b810103126105c257516001600160801b03811681036105c2579189610cba565b503d610e03565b604051630b08b4ef60e41b8152600490fd5b8560ff60649260405192631480153360e31b8452600484015216602482015260056044820152fd5b6040516354dd172960e01b815260048101869052602490fd5b6040516317a9a4f960e21b815290819061057790338660048501613fe3565b346105c25760003660031901126105c2576006546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600854604051908152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57610f2a610f3e9161584a565b604051918291602083526020830190613c2e565b0390f35b346105c25760003660031901126105c257600a54600b54604051918291610f3e916001600160a01b03918216911683613e73565b346105c25760003660031901126105c2576020600e54604051908152f35b346105c25760403660031901126105c2576001600160401b036004358181116105c257610fc5903690600401613e43565b9091602480358281116105c257610fe0903690600401613e43565b91610fe9613e8d565b60019081860180871161132457840361127d578561128f575b60005b84811061125a575084861161115657600160401b80871161116a576011548760115580881061120b575b5087956011600052602096876000208960051c9060005b8281106111d15750601f198b168b038061117f575b50505050851161116a578411611156575060125483601255808410611138575b5093816012600052846000209060005b85811061112757505050604051948160408701604088525260608601969160005b8181106111035750505050838503838501528185526001600160fb1b0382116105c2577f12955a64e0444022667406e8e5e263bdac7d6c8246e16b5c3f28215f09c51cdb94849260051b80928583013701030190a1005b9091929788359060ff82168092036105c257908152860197860192919082016110ac565b81358382015590860190870161108b565b61115090601260005284866000209182019101614061565b8661107b565b634e487b7160e01b60009081526041600452fd5b50634e487b7160e01b60009081526041600452fd5b9260009360005b898d83831061119f57505050505001558980808061105b565b90919293966111c790846111b28a614053565b919060ff809160031b9316831b921b19161790565b9601929101611186565b87906000805b8d8082106111eb5750508184015501611046565b90969293916111fe90886111b286614053565b92019501908992916111d7565b6112399060116000526020600020601f808b0160051c820192818c168061123f575b500160051c0190614061565b8861102f565b600019908186019182549160200360031b1c1690558d61122d565b620186a0611269828787614043565b351161127d5761127890613eb9565b611005565b604051630140299b60e71b8152600490fd5b60005b86811061129f5750611002565b60ff806112b56112b0848b8d614043565b614053565b161561127d5781151590816112d8575b5061127d576112d390613eb9565b611292565b90506112e86112b0838a8c614043565b90600019830183811161130f576113046112b083928c8e614043565b1691161115896112c5565b84634e487b7160e01b60005260116004526000fd5b50634e487b7160e01b60009081526011600452fd5b346105c25760003660031901126105c257611352613e8d565b61135a613f89565b611362613f89565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a17ff633f37eb96c51f3eee538b18ab2c8be9023a46d535102a54e986e94a52542066020604051338152a1005b346105c25760203660031901126105c2576113de613b3e565b6113e6613e8d565b6001600160a01b0316801561094257600780546001600160a01b031916821790557f1af232b023641b0b27419a06769dde7a101bdee010ec36c40109696e933761a0600080a2005b346105c25760003660031901126105c25760206040516103e88152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57806000526013602052604060002060ff60058201541690600782101561048657600582036114f857600b015460ff166003811015610486576001036114d4575060005260196020526020604060002054604051908152f35b906105a760449260405192638a8fcb0760e01b845260048401526024830190613b6a565b604051638e84d22560e01b8152600481018490526044906105a76024820185613b6a565b346105c25760003660031901126105c2576040517f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e3206001600160a01b03168152602090f35b346105c25760203660031901126105c25761157a613f89565b611582613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e57600435600052601460205260406000203360005260205260018060a01b036040600020541615611860576004356000526014602052604060002033600052602052604060002080549060ff8260b01c166118425760043560005260136020526040600020916015602052604060002092601860205260406000206019602052604060002054916116606001820154965487614010565b612710611671600484015483614010565b0491600097600080958054965b878110611806575b505060ff8860a81c161590816117e5575b816117c9575b15611795575050600b015460ff166003811015610486576002810361177a57506116c8929350614003565b81611767575b50505b82156117495760ff60b01b1916600160b01b179055600954811161173f576116fb81600954614003565b6009555b6117098133615230565b60405190815233907fe97cee5a4c0549d3fdc81e322b718ddf0aeb3418ec87dce4f9a7fb28d117c312602060043592a360018055005b60006009556116ff565b60405163f54d5f5360e01b8152806105773360048035908401613fca565b611772929450614023565b9183806116ce565b915050600191501461178d575b506116d1565b925083611787565b945094505050506117a657506116d1565b909250611388908181029181830414901517156104d55761271090049183611787565b905060ff600b840154166003811015610486576001149061169d565b9050600360ff8960a01c16101561048657600160ff8960a01c161490611697565b6118108183613f29565b905460039190911b1c6001600160a01b031633146118365761183190613eb9565b61167e565b50505060018a80611686565b6040516302a4851560e61b8152806105773360048035908401613fca565b6040516379a6671160e01b8152806105773360048035908401613fca565b604051639cd1128160e01b81526004803590820152600560248201526064916105a7906044830190613b6a565b602460405163d13b267760e01b81526004356004820152fd5b346105c2576118d236613dfa565b906118db613f89565b6118e3613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff6005604060002001541660078110156104865760038103611955575080600554163314159081611946575b5061086e576106b8926146a0565b90506000541633141584611938565b604051639cd1128160e01b815260048101869052600360248201526064916105a7906044830190613b6a565b60403660031901126105c2576004356024359060ff82168092036105c2576119a7613f89565b6119af613fa7565b8015611b6357600582108015611b59575b611b4057803403610b2657600354906119d882613eb9565b60035560085490826000526020906013825260406000209260018060a01b03199333858254161781558260018201556002810160ff199088828254161790553460038301558260048301556005820190815416905560064291015560018060a01b0395611a4f87600a541688600b541690336158e3565b9660405190611a5d82613d01565b3382528582019060008252604083019160018352606084019860008a5260808501928c84528b60005260148a526040600020336000528a526040600020955116908554161784555191600383101561048657600080516020615f0383398151915298610ac5611ad292610aaa60019688613ed4565b519101558560005260158452611aec336040600020613f41565b611af883600954613f7c565b600955604051928352838301526040820152837fb03d4415051b223aac81e9cf0c228cff5c476e00dde5d1970fbfe34e72f82f6260603393a36040519384523393a360018055005b60405163d666109b60e01b815260048101839052602490fd5b50600c82116119c0565b60405163fa36c55360e01b81526000600482015260016024820152604490fd5b346105c25760003660031901126105c25760405180916011548083528360208080950180946011600052826000209460005b81601f8201106120815784611cd497549383831061206c575b838310612054575b83831061203c575b838310612024575b83831061200d575b838310611ff5575b838310611fdd575b838310611fc5575b838310611fad575b838310611f95575b838310611f7d575b838310611f65575b838310611f4d575b838310611f35575b838310611f1d575b838310611f05575b838310611eed575b838310611ed5575b838310611ebd575b838310611ea5575b838310611e8d575b838310611e75575b838310611e5d575b838310611e45575b838310611e2d575b838310611e15575b838310611dfd575b838310611de5575b838310611dcf575b50828210611db9575b828210611da3575b5010611d95575b509050949392940383613d52565b60405190819381601254938481520180936012600052836000209060005b818110611d815750505085611d08910386613d52565b6040519485946040860190604087525180915260608601929060005b818110611d67575050508482038584015251808252908201929160005b828110611d5057505050500390f35b835185528695509381019392810192600101611d41565b825160ff1685528897509385019391850191600101611d24565b825484529285019260019283019201611cf2565b60f81c815201849087611cc6565b6001919460ff8560f01c16815201930184611cbf565b6001919460ff8560e81c16815201930184611cb7565b9460ff85600194971c168152019301848b611cae565b91948160019160ff8760d81c16815201950191611ca6565b91948160019160ff8760d01c16815201950191611c9e565b91948160019160ff8760c81c16815201950191611c96565b91948160019160ff8760c01c16815201950191611c8e565b91948160019160ff8760b81c16815201950191611c86565b91948160019160ff8760b01c16815201950191611c7e565b91948160019160ff8760a81c16815201950191611c76565b91948160019160ff8760a01c16815201950191611c6e565b91948160019160ff8760981c16815201950191611c66565b91948160019160ff8760901c16815201950191611c5e565b91948160019160ff8760881c16815201950191611c56565b91948160019160ff8760801c16815201950191611c4e565b91948160019160ff8760781c16815201950191611c46565b91948160019160ff8760701c16815201950191611c3e565b91948160019160ff8760681c16815201950191611c36565b91948160019160ff8760601c16815201950191611c2e565b91948160019160ff8760581c16815201950191611c26565b91948160019160ff8760501c16815201950191611c1e565b91948160019160ff8760481c16815201950191611c16565b91948160019160ff8760401c16815201950191611c0e565b91948160019160ff8760381c16815201950191611c06565b91948160019160ff8760301c16815201950191611bfe565b91948160019160ff8760281c16815201950191611bf6565b91948160019160ff87831c16815201950191611bee565b91948160019160ff8760181c16815201950191611be6565b91948160019160ff8760101c16815201950191611bde565b91948160019160ff8760081c16815201950191611bd6565b91948160019160ff8716815201950191611bce565b926001919492955061040090875460ff8082168352808260081c1686840152808260101c16604084015280828a82828782828d82826060828260181c168188015282826080951c168488015260a096838360281c168882015260c09a848460301c168c830152848460381c1690820152838360401c16610100820152838360481c16610120820152838360501c16610140820152610160848460581c169101521c166101808d0152828260681c166101a08d0152828260701c166101c08d0152828260781c166101e08d01521c166102008a0152828260881c166102208a0152828260901c166102408a0152828260981c166102608a01521c16610280870152828260a81c166102a0870152828260b01c166102c0870152828260b81c166102e08701521c16610300840152808260c81c16610320840152808260d01c16610340840152808260d81c1661036084015280828a1c16610380840152808260e81c166103a08401528160f01c166103c083015260f81c6103e082015201950191019286928894959295611bb5565b346105c25760203660031901126105c257612227613b3e565b61222f613e8d565b6001600160a01b0316801561094257600480546001600160a01b031916821790557f1988eae50e3c966bb15e25079f549aabc769fff8405390f7914b211a9ca67972600080a2005b346105c25760003660031901126105c257612290613e8d565b612298614097565b6122a0614097565b60ff19600254166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a17fb5a73b054ac6db0c0e6429d3bb998734a660c422d158b35e9cd088b30d42f9e06020604051338152a1005b346105c25760003660031901126105c25760408051908101906001600160401b0382118183101761235557610f3e91604052600a81526928e280bfcba0e280bf2960b01b602082015260405191829182613c81565b634e487b7160e01b600052604160045260246000fd5b346105c2576020806003193601126105c257600435612388613fa7565b60008181526013835260409020546001600160a01b0392908316156105a957816000526013815260ff6005604060002001541660078110156104865780610b7e5750816000526013815282604060002054163314928060005416331490806005541633149060065416331490859286156124d5575b5082156124cd575b5081156124c5575b501561086e576106b89215612467577f43616e63656c6c656420627920686f7374207768696c6520696e204c6f6262796040519161244a83613d1c565b60278352820152661039ba30ba329760c91b604082015290614b72565b7f43616e63656c6c65642062792061646d696e2f6261636b656e642f76616c69646040519161249583613d1c565b603a83528201527930ba37b9103bb434b6329034b7102637b1313c9039ba30ba329760311b604082015290614b72565b90508461240d565b915085612405565b9250866123fd565b346105c25760203660031901126105c25760206124fb600435615771565b6125086040518092613b77565bf35b346105c25760203660031901126105c2576001600160401b0361252b613c6b565b1660005260176020526020604060002054604051908152f35b346105c25760003660031901126105c2576000546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600954604051908152f35b346105c25760403660031901126105c2576004356125a7613b54565b90806000526020916013835260018060a01b0390816040600020541615612740578260005260148452604060002091808216928360005285528060406000205416156127215780600054163314159081612711575b81612702575b5061086e57826000526013845260ff60056040600020015416600781101561048657600581149081156126f7575b506126de578260005260148452604060002082600052845260ff60406000205460a81c16156126c05750817f56361dafe421b3aba201873229b0c108cb63c45289b37d3d30b68d7ffa876dfe61001b9482600052601481526040600020846000528152604060002060ff60a81b1981541690558260005260138152600760406000200154604051908152a3614d32565b604051638c5355ab60e01b8152908190610577908560048401613fca565b60405163712286e160e11b815260048101849052602490fd5b600691501485612630565b90506006541633141585612602565b80915060055416331415906125fc565b506040516379a6671160e01b8152908190610577908560048401613fca565b60405163d13b267760e01b815260048101849052602490fd5b346105c25760a03660031901126105c257604435602435600435608435606435612781613e8d565b603c8510801561282d575b8015612823575b8015612818575b801561280d575b6127fb577f2621620528f26fb75529114de7b19d9f01b11bb4a9bd0ad57997a33cf8141ec09460a09484600f558060105581600c5582600d5583600e556040519485526020850152604084015260608301526080820152a1005b6040516325c3636760e01b8152600490fd5b5061012c84106127a1565b5061012c831061279a565b50601e8210612793565b50603c811061278c565b346105c25760003660031901126105c257602060405160058152f35b346105c25760203660031901126105c25761286c613b3e565b612874613e8d565b6001600160a01b0316801561094257600580546001600160a01b031916821790557f9177ce6b43739f0c598396fcbb3deeeddefeee033f92b4fa36b1c5ec1afdd14e600080a2005b346105c257602090816003193601126105c2576004356128da615710565b5060008181526013845260409020546001600160a01b0392908316156105a95781600052601384526040600020906040519361291585613ce5565b825416845260018201548585015260ff6002830154166040850152600382015460608501526004820154608085015260ff6005830154169160a0850191600784101561048657600b6129c19260ff926129dd968652600682015460c08a0152600782015490890152600881015461010089015282600982015460018060401b0381166101208b015260401c161515610140890152600a8101546101608901520154166101808601613ec8565b82600052601585526129e4604060002060405193848092614cec565b0383613d52565b5191600783101561048657612a3494610f3e93600503612a555760189160005252612a46612a1f612a26604060002060405192838092614cec565b0382613d52565b915b60405195868096613b84565b6101e0806101a0870152850190613c2e565b908382036101c0850152613c2e565b5050612a46604051612a6681613d37565b6000815291612a28565b346105c25760003660031901126105c257612a89613e8d565b600080546001600160a01b0319811682556001600160a01b0316600080516020615f238339815191528280a3005b346105c25760403660031901126105c257612ad0613b54565b612ad8613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e575060043560009081526014602090815260408083206001600160a01b0385811685529252909120541615612e09576000546001600160a01b031633141580612df4575b80612ddf575b61086e576004356000526014602052604060002060018060a01b03821660005260205260406000209081549160ff8360b01c16612ce15760043560005260136020526040600020601560205260406000206018602052604060002090601960205260406000205492612be16001820154925483614010565b90612710612bf3600483015484614010565b0492600098600080968054975b888110612d9f575b505060ff8260a81c16159182612d7d575b5081612d61575b15612d2d575050600b015460ff1660038110156104865760028103612d125750612c4b929350614003565b81612cff575b50505b8215612ce1576009548311612cd757612c6f83600954614003565b6009555b612c86836001600160a01b038416615230565b805460ff60b01b1916600160b01b1790556040519182526001600160a01b031690600435907f6aab79494515e500f72558d888a9426fa23041c0bf39b592fdd8809c98eabef590602090a360018055005b6000600955612c73565b60405163f54d5f5360e01b8152806105778460048035908401613fca565b612d0a929450614023565b918380612c51565b9150506001915014612d25575b50612c54565b925083612d1f565b94509450505050612d3e5750612c54565b909250611388908181029181830414901517156104d55761271090049183612d1f565b905060ff600b8401541660038110156104865760011490612c20565b909150600360ff8260a01c1610156104865760a01c60ff16600114908b612c19565b612da98183613f29565b905460039190911b1c6001600160a01b03908116908d1614612dd357612dce90613eb9565b612c00565b50505060018b80612c08565b506006546001600160a01b0316331415612b69565b506005546001600160a01b0316331415612b63565b6040516379a6671160e01b81529081906105779060048035908401613fca565b346105c25760403660031901126105c257600435612e45613b54565b612e4d613f89565b612e55613fa7565b6000828152601360205260409020546001600160a01b03908116156127405782600052601360205260ff6005604060002001541660078110156104865760028103612ec7575080600554163314159081612eb8575b5061086e576106b891614307565b90506000541633141583612eaa565b604051639cd1128160e01b815260048101859052600260248201526064916105a7906044830190613b6a565b346105c25760203660031901126105c257600435612f0f613f89565b612f17613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff6005604060002001541660078110156104865780612fd75750806000526013602052612f76600660406000200154600f5490613f7c565b42106106be576040516106b891612f8c82613d1c565b603382527f43616e63656c6c65642064756520746f204c6f6262792070686173652074696d60208301527232b7baba1014383ab13634b19031b0b636149760691b6040830152614b72565b906105a760649260405192639cd1128160e01b84526004840152600060248401526044830190613b6a565b346105c25760003660031901126105c2576020600354604051908152f35b346105c25760203660031901126105c25760043561303c613f89565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600581149081156130a9575b506130915761001b906140b6565b6024906040519063712286e160e11b82526004820152fd5b600691501482613083565b346105c25760003660031901126105c257602060ff600254166040519015158152f35b346105c25760603660031901126105c2576130f0613c6b565b6130f8613b54565b507f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e3206001600160a01b031680156105c25733036105c2576106b89061313b613fa7565b613143613f89565b604435906152d0565b346105c2576020806003193601126105c25760043560008181526013835260409020546001600160a01b0316156106fb57806000526013825260ff600560406000200154166007811015610486576005036131d557906129dd91600052601881526131c1604060002060405193848092614cec565b610f3e604051928284938452830190613c2e565b601360449282600052526105a760ff6005604060002001541660405192638e84d22560e01b845260048401526024830190613b6a565b346105c25760203660031901126105c257600435613227613e8d565b6103e8808211613262577fdc4e4be378c228e1b7b13787e1d0620609304c2f71ed3a6ee69e4aae99d58cd960208380600855604051908152a1005b6040516373ab893560e11b815260048101929092526024820152604490fd5b346105c25760203660031901126105c25760043561329d615710565b506000818152601360205260409020546001600160a01b0391908216156106fb5760005260136020526040600020604051916132d883613ce5565b81541682526001810154602083015260ff6002820154166040830152600381015460608301526004810154608083015260ff60058201541692600784101561048657600b6133839260ff926101a09660a0870152600682015460c0870152600782015490860152600881015461010086015282600982015460018060401b03811661012088015260401c161515610140860152600a8101546101608601520154166101808301613ec8565b6125086040518092613b84565b346105c25760003660031901126105c2576020604051600c8152f35b346105c25760403660031901126105c2576004356133c8613b54565b906040516133d581613cca565b600081526000606060209282848201528260408201520152816000526013815260018060a01b03918260406000205416156106fb578060005260148252604060002090838516918260005283528360406000205416156134a65760809450600052601482526040600020906000528152604060002091600183549182169301549160405161346281613cca565b84815260608282019160ff8560a81c161515835260ff604082019560b01c161515855201938452604051948552511515908401525115156040830152516060820152f35b6040516379a6671160e01b815290819061057790879060048401613fca565b346105c25760403660031901126105c2576024803560043560078210156105c2576134ee613e8d565b6000818152601360205260409020546001600160a01b03161561369d57806000526013602052604060002091600583019283549360ff8516948315613678575b60018414613664575b6005841480613637575b6105c25760ff191660ff841617905560028214600081801561362a575b818115613605575b506135b2575b50505061358a906135806040518095613b6a565b6020840190613b6a565b7f4c2908af6339a7cecca8470b17951a83d590ff59671ec6a761871d3c69fb76ce60403393a3005b4260088401556135f05761358a93949550806135e4575b6135d7575b8493928161356c565b60076001910155846135ce565b506007810154156135c9565b85634e487b7160e01b60005260216004526000fd5b9050613615576004841481613566565b86634e487b7160e01b60005260216004526000fd5b505060006003841461355e565b5060ff600b84015416600381101561364f5715613541565b87634e487b7160e01b60005260216004526000fd5b600786101561361557851561353757600080fd5b60078610156136155785158015613693575b61352e57600080fd5b506006861461368a565b90506040519063d13b267760e01b82526004820152fd5b346105c25760203660031901126105c2576136cd613b3e565b6136d5613e8d565b600b80546001600160a01b0319166001600160a01b039290921691821790556002600080516020615fa3833981519152600080a3005b346105c2576020806003193601126105c25760043590613729613f89565b613731613fa7565b60008281526013825260409020546001600160a01b0392908316156106fb57806000526013825282604060002054168033036139a35750806000526013825260406000206005810160ff81541660078110156104865760058114908115613998575b506126de5760038083019283549160006004830154908115158061398b575b613942575b6137c2915084614003565b6009549093908111613937576137da90600954614003565b6009555b856000526015875260406000209260019889809401926138018454875490614010565b9586613881575b858b8b60008c8c600660ff19825416179055556033604051928084528301527f43616e63656c6c656420627920686f73743b206665652064656475637465642c604083015272103932b33ab7323990383937b1b2b9b9b2b21760691b6060830152600080516020615ea383398151915260803393a38055005b6000865b613890575b50613808565b81548110156139325780868d8d876138ab6139029688613f29565b905490891b1c1691816000526014815260406000208360005281526138df8d6138da8c60406000209754614010565b614023565b9081613908575b5050825460ff60b01b1916600160b01b1790925550613eb99050565b86613885565b81613922600080516020615ee38339815191529386615230565b604051908152a38f8d8f806138e6565b61388a565b5060006009556137de565b506139506127109185614010565b0480151580613981575b1561397657806139716137c2928b60075416615230565b6137b7565b506137c260006137b7565b508381111561395a565b50896007541615156137b2565b600691501486613793565b6040516317a9a4f960e21b815291829161057791339060048501613fe3565b346105c25760003660031901126105c2576004546040516001600160a01b039091168152602090f35b346105c25760403660031901126105c257600435600081815260136020526040902054602435916001600160a01b03918216156106fb576000526015602052604060002080548310156105c257602092613a4491613f29565b9190546040519260031b1c168152f35b346105c25760003660031901126105c257602060405161012c8152f35b346105c25760003660031901126105c257600a546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576005546040516001600160a01b039091168152602090f35b346105c25760203660031901126105c257613adc613b3e565b613ae4613e8d565b600a80546001600160a01b0319166001600160a01b039290921691821790556001600080516020615fa3833981519152600080a3005b346105c25760003660031901126105c257600b546001600160a01b03168152602090f35b600435906001600160a01b03821682036105c257565b602435906001600160a01b03821682036105c257565b9060078210156104865752565b9060038210156104865752565b90613c2c9160018060a01b0381511682526020810151602083015260ff60408201511660408301526060810151606083015260808101516080830152613bd260a082015160a0840190613b6a565b60c0818101519083015260e080820151908301526101008082015190830152610120808201516001600160401b03169083015261014080820151151590830152610160808201519083015261018090810151910190613b77565b565b90815180825260208080930193019160005b828110613c4e575050505090565b83516001600160a01b031685529381019392810192600101613c40565b600435906001600160401b03821682036105c257565b6020808252825181830181905290939260005b828110613cb657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501613c94565b608081019081106001600160401b0382111761235557604052565b6101a081019081106001600160401b0382111761235557604052565b60a081019081106001600160401b0382111761235557604052565b606081019081106001600160401b0382111761235557604052565b602081019081106001600160401b0382111761235557604052565b601f909101601f19168101906001600160401b0382119082101761235557604052565b6001600160401b0381116123555760051b60200190565b81601f820112156105c257803591613da383613d75565b92613db16040519485613d52565b808452602092838086019260051b8201019283116105c2578301905b828210613ddb575050505090565b81356001600160a01b03811681036105c2578152908301908301613dcd565b60606003198201126105c257600435916001600160401b036024358181116105c25783613e2991600401613d8c565b926044359182116105c257613e4091600401613d8c565b90565b9181601f840112156105c2578235916001600160401b0383116105c2576020808501948460051b0101116105c257565b6001600160a01b0391821681529116602082015260400190565b6000546001600160a01b03163303613ea157565b60405163118cdaa760e01b8152336004820152602490fd5b60001981146104d55760010190565b60038210156104865752565b90600381101561048657815460ff60a01b191660a09190911b60ff60a01b16179055565b601254811015613f1357601260005260206000200190600090565b634e487b7160e01b600052603260045260246000fd5b8054821015613f135760005260206000200190600090565b8054600160401b81101561235557613f5e91600182018155613f29565b819291549060031b9160018060a01b03809116831b921b1916179055565b919082018092116104d557565b60ff60025416613f9557565b60405163d93c066560e01b8152600490fd5b600260015414613fb8576002600155565b604051633ee5aeb560e01b8152600490fd5b9081526001600160a01b03909116602082015260400190565b9081526001600160a01b0391821660208201529116604082015260600190565b919082039182116104d557565b818102929181159184041417156104d557565b811561402d570490565b634e487b7160e01b600052601260045260246000fd5b9190811015613f135760051b0190565b3560ff811681036105c25790565b81811061406c575050565b60008155600101614061565b9080614082575050565b613c2c91600052602060002090810190614061565b60ff60025416156140a457565b604051638dfc202b60e01b8152600490fd5b60008181526013602052604090818120600581019060ff82541691600882018054906007851015938461427d57600286149283156142b657600c546140fa91613f7c565b61012c81018091116142a2574210614291576007019184897fb5cab6c1716e65f60a857bf7351124136e99963a75d21d778e7727144e35231360c086548c5190614144828d613b6a565b602082015260608d820152602860608201527f5075626c69632074696d656f75742066616c6c6261636b20696e766f6b656420608082015267313c903ab9b2b91760c11b60a0820152a261427d57600386148015614270575b1561420057505050506141ec576004146141cd575b50506141bd81614d32565b6141ca57613c2c906151e1565b50565b6141e591838252601660205281208054918155614078565b38806141b2565b634e487b7160e01b82526021600452602482fd5b919390929695945061425c57614219575b505050505050565b61422286614d32565b61421157600080516020615f8383398151915294600360ff19825416179055429055549082519182526020820152a2388080808080614211565b634e487b7160e01b84526021600452602484fd5b508694506004861461419d565b634e487b7160e01b87526021600452602487fd5b87516326c015ab60e21b8152600490fd5b634e487b7160e01b88526011600452602488fd5b879550600387036142cd57600d546140fa91613f7c565b879550600487036142e457600e546140fa91613f7c565b8851638a8fcb0760e01b8152600481018b90526044906105a7602482018a613b6a565b906000908282526020601381526040928381209060078201549260018060a01b039081861692831515808091614640575b80614621575b6145d5575b8189918b959493968297839888855260158752858520858154905b8181106144f957505050836144db575b836144ba575b8361446b575b83614460575b5082614455575b5081614449575b8161443c575b506143eb575b505050506143a89150614d32565b6143e457600080516020615f83833981519152938160056143df9301600360ff198254161790556008429101555192839283613fca565b0390a2565b5050505050565b7fcb5e8d6d1fd0c2bd9eca2e2546d3d92f27a2b10728927512cb00ce7bdc8738ad938582526014845282822096169586825283522060ff60a81b1981541690558751868152a338858180878161439a565b9050848716141538614394565b8786161515915061438e565b600114915038614387565b600114925038614380565b94509150919394958452601485528a8420828552855260ff8b85205460a01c1660038110156144a657918b939160018e98979694149261437a565b634e487b7160e01b85526021600452602485fd5b8885526014875285852083865287528585205460a81c60ff16159350614374565b8885526014875285852083865287528585205488161515935061436e565b919395978a9b889a9b929496985260148b52808a206145188487613f29565b939054600394851b1c168b528b5289205460ff90818160a81c16614554575b50505061454390613eb9565b918f9998979593918f97959361435e565b60a09693961c16818110156145c1576002810361458357505061457961454391613eb9565b935b903880614537565b919491600114614598575b506145439061457b565b94614543919b506145a98b91613eb9565b956145b48d86613f29565b9054911b1c169a9061458e565b634e487b7160e01b8a52602160045260248afd5b89825260148352888220858352835288822060ff60a81b198154169055848a7f8cc6a126ca79d9271b80277e31507795e87602b1d7ce66e4451363f929f4811c858c518b8152a3614343565b5089825260148352888220858352835260ff8983205460a81c1661433e565b506014835288822085835283528389832054161515614338565b9061466482613d75565b6146716040519182613d52565b8281528092614682601f1991613d75565b0190602036910137565b8051821015613f135760209160051b010190565b90929183518151036105c257600082815260209460138652604091828120936146de60078601549360158a52858420601493848c5287862092615b74565b948486015190600191828114806149a2575b80614996575b1561476957505050968082879899887f47e734b70f4e742ea488db9e70df12c13e01b4d6cc4a500191e3b724b6a83bdd979695999899528252848082209160018060a01b0392838b5116825284522060ff60a81b198154169055875116960151908351928352820152a36141bd81614d32565b918094979192118061498a575b15614942576060019287600080516020615e83833981519152878c6147aa885183519384938c85528401528b830190613c2e565b0390a287875260168a526147c5868820805490898155614078565b87875260168a5285872093879384835b6148c6575b5050505060028210600014614825575050508584600080516020615ec383398151915261481c9697986016948651908152a28584525281208054918155614078565b6141bd81614d32565b60058199969495939901600460ff198254161790556008429101556148498161465a565b925b8181106148915750507f9058488d8f4ca0c7ed50681193e9b6dddd19f1cb87c905f3fc1e73fb6cb6af80949596509081806143df93519586958652850152830190613c2e565b8061489f6148c1928b613f29565b905460039190911b1c6001600160a01b03166148bb828761468c565b52613eb9565b61484b565b82515181101561493d578a8a52818d528c898b2060018060a01b0391826148ee85885161468c565b51168d525260ff8a8c205460a81c16614912575b5061490c90613eb9565b836147d5565b81966149316149369261492961490c95885161468c565b51168a613f41565b613eb9565b9590614902565b6147da565b5050505061497e600080516020615e83833981519152938386979894519161496983613d37565b82528080519586958652850152830190613c2e565b0390a26141bd81614d32565b50898101511515614776565b508a88015115156146f6565b5087516001600160a01b031615156146f0565b909182518151036105c2576000908282526020906013825260409160078385200154956016928383528486208054908115614b5d57505090614a099187875260158452858720906014855286882092615b74565b8051949687956001600160a01b03908116614af0575b87989184879261481c9a94600185880151149081614ae3575b81614ad6575b81614ace575b5015614ab057907fbc2df99d70b88b7387193e2ee99061e3b292151b21724b0b34f9a7fd6aa88d09939291858b5260148252838b20818851168c528252838b2060ff60a81b198154169055865116950151825191825286820152a35b8584525281208054918155614078565b5091600080516020615ec383398151915293945051908152a2614aa0565b905038614a44565b8784015115159150614a3e565b8751831615159150614a38565b865b8888528585528688208054821015614b4b57614b0f828492613f29565b919054818751169260031b1c1614614b2f57614b2a90613eb9565b614af2565b5087985061481c979184879260015b9b50925050919750614a1f565b505087989184879261481c9a94614b3e565b955095935050505061481c9394508155614078565b600081815260209060138252604091828220601582528383209160039283830193845460095481818111600014614cdb575050866009555b80151580614cd1575b614bf0575b50505050906005600080516020615ea383398151915294939201600660ff19825416179055555180614beb339582613c81565b0390a3565b4710614cc057600191828501549081614c0a575b50614bb8565b87845b614c18575b50614c04565b8a8254821015614cba579089614c8d92600080516020615ee3833981519152898e80614c44878a613f29565b90548688526014808652838920928e1b9190911c6001600160a01b03908116808a529286528389205492989216614c93575b505050614c838986615230565b51888152a3613eb9565b84614c0d565b8682528452818120878252845220805460ff60b01b1916600160b01b179055808f38614c76565b50614c12565b865163786e0a9960e01b8152600490fd5b5082541515614bb3565b614ce491614003565b600955614baa565b90815480825260208092019260005281600020916000905b828210614d12575050505090565b83546001600160a01b031685529384019360019384019390910190614d04565b6000818152601560205260408120805492918080805b86811061513957506000911590818061512e575b156151035750505050600292600193845b614d79575b5050505090565b836000526013602052604060002091600583019060ff1991600583825416179055600b84019160038410156104865782541660ff8416178255856000526018602052614dcf604060002080549060008155614078565b8560005260186020526040600020936000926014602052604060002060405191606083019083821060018060401b038311176123555760ff91604052600084526000602085015260006040850152614e406003860154612710614e36600489015483614010565b0490818752614003565b602085015260006040850152541660038110156104865760011461504b575b508051600a60406020840151930151930192835580614ff4575b509054818111614fea57614e8c91614003565b945b60005b828110614f385750505060009381614f25575b50506003811015610486577f4fd7802900f80ee62d07cf9efe951278d8c503d4cbb86ac8ea4d2820d9be00a592614efe614eed9360018414614f10575b60405194858095613b77565b606060208501526060840190614cec565b9060408301520390a238808080614d72565b86600052601960205282604060002055614ee1565b614f30929450614023565b913880614ea4565b8760005260146020526040600020614f508284613f29565b60018060a01b0391549060031b1c1660005260205260406000205460ff8160a81c169081614fc2575b50614f8d575b614f8890613eb9565b614e91565b92614fba614f8891614931614fa28786613f29565b905460039190911b1c6001600160a01b031689613f41565b939050614f7f565b9050600386101561048657600360ff8260a01c1610156104865760a01c60ff16851438614f79565b5050600094614e8e565b6007546001600160a01b0316908115615039576009546150299290821161502f5761502182600954614003565b600955615230565b38614e79565b6000600955615230565b604051637728d5eb60e01b8152600490fd5b87549060005b82811061505f575050614e5f565b615069818b613f29565b60018060a01b0391549060031b1c166000528160205260406000205460ff8160a81c161590816150e3575b506150a8575b6150a390613eb9565b615051565b600185015490611388918281029281840414901517156104d5576150d76127106150a393046040870151613f7c565b6040860152905061509a565b9050600360ff8260a01c1610156104865760a01c60ff1660011438615094565b959295159081615125575b5061511a575b84614d6d565b506001935083615114565b9050153861510e565b506001811015614d5c565b85600052601460205260406000206151518287613f29565b9190549160039260018060a01b0391841b1c1660005260205260406000205460ff90818160a81c1661518f575b50505061518a90613eb9565b614d48565b60a01c169081101561048657600281036151bb5750906151b161518a91613eb9565b915b90388061517e565b6001146151cc575b61518a906151b3565b916151d961518a91613eb9565b9290506151c3565b806000526013602052600080516020615f438339815191526020604060002060058101600260ff19825416179055600781019061521e8254613eb9565b809255600842910155604051908152a2565b81156152cc578147106152ba57600080808094819460018060a01b03165af1903d156152b4573d906001600160401b0382116152a0576040519161527e601f8201601f191660200184613d52565b825260203d92013e5b1561528e57565b604051633d2cec6f60e21b8152600490fd5b634e487b7160e01b81526041600452602490fd5b50615287565b60405163786e0a9960e01b8152600490fd5b5050565b907f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e3206001600160a01b0316338190036156f1575060018060401b038083166000526017602052604060002054926003548085101590816156e7575b506156e157600084815260136020526040902080549093906001600160a01b0316156143e457600984018054848416858216036156c75760ff6005870154166007811015610486576001036156be5760ff8160401c166156be5760ff60401b1916600160401b179055818316857fae7cceb24659f070aa32d8aa9ced46f64e3ce6a1bcbb5ac917ee33d3188abf7d600080a384600052601460205260406000206015602052604060002090815492600184106105c2576153ea8461465a565b906000805b86811061563a575080156105c25790615456869260405160208101908482527f44454d574954434845535f524f4c455f41535349474e4d454e545f53414c545f6040820152630ac665c760e31b60608201526044815261544e81613cca565b519020615b6a565b926000936000805b8582106155d6575b505050506001600160a01b03831615615564575b505060005b8481106154fa57505050505016600052601760205260006040812055600260ff1960058301541617600582015560016007820155600842910155600080516020615f438339815191526020604051837fb607ccb3d7ced88cd923d2c5f091149c8ecc5fb24209afa25a43ddf94253801d600080a260018152a2565b806155086155559286613f29565b90546001600160a01b0385811660039390931b9190911c160361555a5761493160025b6155358388613f29565b60018060a01b0391549060031b1c16600052856020526040600020613ed4565b61547f565b614931600161552b565b6155bd9250906155b791604051602081019182527f44454d574954434845535f57495443485f46414c4c4241434b5f53414c545f566040820152620665c760eb1b60608201526043815261544e81613cca565b83613f29565b905460039190911b1c6001600160a01b0316833861547a565b839495506155e9826155f093949561468c565b5190613f7c565b908882841061560d575061560390613eb9565b889493929161545e565b94955091505061561e915085613f29565b905460039190911b1c6001600160a01b03169138808080615466565b906064906156488388613f29565b60018060a01b0391549060031b1c166000528560205260016040600020015480615692575b50816156879161568d93615681868961468c565b52613f7c565b91613eb9565b6153ef565b9081830291838304036104d5576127108204830183116104d5576127109091049091019061568761566d565b50505050505050565b60405163f7db5e9d60e01b81528585166004820152602490fd5b50505050565b905015153861532b565b60405163b16a0a0b60e01b815290819061057790339060048401613e73565b6040519061571d82613ce5565b816101806000918281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e0820152826101008201528261012082015282610140820152826101608201520152565b600081815260136020908152604080832054909291906001600160a01b03908116156158425760148252838320338452825233908484205416036157f2578382526013815282822060ff6009820154851c1615908115615828575b81156157f9575b506157f25760ff938252601481528282209033835252205460a01c1690565b5091505090565b60ff91506005015416600781101561581457600114386157d3565b634e487b7160e01b83526021600452602483fd5b905060ff600582015416600781101561425c5715906157cc565b505091505090565b600090808252601360205260ff60056040842001541660078110156158145760041461588757506040519061587e82613d37565b80825236813790565b604082613e4092612a1f945260166020522060405192838092614cec565b90601154821015613f13576011600052600582901c7f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680191601f1690565b6000926001600160a01b0392839190821680615af1575b50169182615a77575b5050506012548015615a70576011918254918215615a39576001830190818411615a245703615a1c5760009260005b83811061598c5750600019830190838211615978575061595360ff916158a5565b90549060031b1c1610615964575090565b61596e9150613ef8565b90549060031b1c90565b634e487b7160e01b60005260045260246000fd5b82151580615a00575b6159a8575b6159a390613eb9565b615932565b801580156159c4575b1561599a579250505061596e9150613ef8565b5060001981018181116159eb576159dc60ff916158a5565b90549060031b1c1683116159b1565b82634e487b7160e01b60005260045260246000fd5b5060ff615a0c826158a5565b90549060031b1c16831115615995565b505050600090565b84634e487b7160e01b60005260045260246000fd5b5060009250159050613e40575060126000527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34445490565b5050600090565b602460209260405194859384926370a0823160e01b84521660048301525afa60009181615abb575b50615aac575b8080615903565b615ab591613f7c565b38615aa5565b90916020823d8211615ae9575b81615ad560209383613d52565b81010312615ae65750519038615a9f565b80fd5b3d9150615ac8565b6040516370a0823160e01b81528486166004820152919250602090829060249082905afa859181615b33575b50615b2b575b9083916158fa565b935082615b23565b9091506020813d8211615b62575b81615b4e60209383613d52565b81010312615b5e57519038615b1d565b8580fd5b3d9150615b41565b811561402d570690565b9390604092835194615b8586613d01565b6060808701528594600091828852602083818a015283838a01528360808a01528454988915615e745750615bb88961465a565b96845b8b51811015615cd0576001600160a01b0380615bd7838f61468c565b51169080615be5848c61468c565b51169188528585528688205481811615159081615cc2575b5080615cb9575b80615ca8575b80615c95575b615c25575b5050615c2090613eb9565b615bbb565b878b8a8f5b8310615c39575b505050615c15565b83615c45848793613f29565b90549060031b1c1614615c645750615c5c90613eb9565b8b8a8f615c2a565b615c20949350819250615c7d615c8a92615c839261468c565b51613eb9565b918c61468c565b529038808b8a615c31565b5081885260ff8789205460a81c16615c10565b508188528087892054161515615c0a565b50811515615c04565b60ff915060a81c1638615bfd565b509950969793929450615ce28461465a565b938596865b828110615d6e57505050505050615cfd8361465a565b91606086019283525b838110615d4657505083516001600160a01b03161515929150829050615d39575b50615d2f5790565b6001608082015290565b9050810151151538615d27565b615d69906001600160a01b03615d5c828561468c565b51166148bb82865161468c565b615d06565b615d788187613f29565b90546001600160a01b0360039290921b1c8116808a52858c52868a20549091811615908115615e65575b50615e5b57615db1828461468c565b5180615dc8575b5050615dc390613eb9565b615ce7565b9091998b8d01908151808411600014615e1557505052808b5260019081868d0152875115615e0157878b015297615dc3905b9038615db8565b634e487b7160e01b89526032600452602489fd5b909b92615dc394925014615e2a575b50615dfa565b868d01615e378151613eb9565b9052848b1015615e2457615e54615e4d8c613eb9565b9b8a61468c565b5238615e24565b50615dc390613eb9565b60ff915060a81c161538615da2565b98505050505050509250509056fe61c0b92f778d9580f61c1368f619231971929502139a169537859d147a3d8db17aaa838aec7a66b4c97d35cf9ef5095d74305ac4327c0bd09378674920a44e58ca623bc6d60c4de3c68b9587a6f35819c7232f5f79176c981fd7835daa19f812ff3116559cac6c47442d1f2083dc842de1f35c5f87fd25d7978d509fa59c1b1803dfbe1fcb4e2d61f3b4a0c93d8f814c92250618b3387f396cc9fc9fafe2c2038be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e06b40fcd6b8efc2e628b2efa06aea41f9d48edcf9f787184c307080c83cd22084481df23c457d78a89f0f5fabd340eb449a09d8690c045c3d8d567988ef019540a2361be2ee66c1a30168107b5157b30d03384356ba97fffc7ab6aeabdc6d211824e30ae881d6fa40479d2f3a64728e211e5620dab64f70e0043787baacd89f0ea2646970667358221220f8f18cc8a587b1d483db5026deda406d10c7898296efb1196cb2ec3d65de9c4d64736f6c63430008140033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e32000000000000000000000000041c5c4a0e112717101ff1da950f6ca7ae4f9291900000000000000000000000000000000000000000000000000000000000001f4
-----Decoded View---------------
Arg [0] : _pythEntropyAddr (address): 0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320
Arg [1] : _initialContractOwner (address): 0x41C5c4a0E112717101FF1dA950f6Ca7Ae4F92919
Arg [2] : _initialDefaultFeeBps (uint256): 500
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e320
Arg [1] : 00000000000000000000000041c5c4a0e112717101ff1da950f6ca7ae4f92919
Arg [2] : 00000000000000000000000000000000000000000000000000000000000001f4
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.