ETH Price: $2,802.89 (-4.96%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Create Hackathon236409512025-10-23 14:58:5994 days ago1761231539IN
0xbfb8D464...c5e523418
0.00260455 ETH0.000072012.00221059

Latest 4 internal transactions

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x60806040236409232025-10-23 14:53:1194 days ago1761231191
0xbfb8D464...c5e523418
 Contract Creation0 ETH
0x60806040236409232025-10-23 14:53:1194 days ago1761231191
0xbfb8D464...c5e523418
 Contract Creation0 ETH
0x60806040236409232025-10-23 14:53:1194 days ago1761231191
0xbfb8D464...c5e523418
 Contract Creation0 ETH
0x60806040236409232025-10-23 14:53:1194 days ago1761231191
0xbfb8D464...c5e523418
 Contract Creation0 ETH
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
HackathonFactory

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "./Hackathon.sol";
import "./JudgeCouncil.sol";
import "./VotingTypes.sol";
import "./OpenVoting.sol";
import "./RevealCommitVoting.sol";
import "./ZKVotingSystem.sol";
import "./QuadraticVoting.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

// Curve Finance Router interface
interface ICurveRouter {
    function exchange(
        address[11] calldata _route,
        uint256[5][5] calldata _swap_params,
        uint256 _amount,
        uint256 _min_dy,
        address[5] calldata _pools
    ) external payable returns (uint256);

    function get_exchange_amount(
        address[11] calldata _route,
        uint256[5][5] calldata _swap_params,
        uint256 _amount,
        address[5] calldata _pools
    ) external view returns (uint256);
}

interface IWETH9 {
    function deposit() external payable;
    function withdraw(uint256 wad) external;
    function transfer(address to, uint256 value) external returns (bool);
    function balanceOf(address) external view returns (uint256);
}

/**
 * @title HackathonFactory
 * @dev Factory contract for creating new hackathon instances using cloning
 * @notice This contract serves as a factory for deploying individual hackathon contracts using the clone pattern for gas efficiency.
 * @notice This contract also manages global judge governance.
 */
contract HackathonFactory is JudgeCouncil {

    using SafeERC20 for IERC20;

    mapping(address => uint256) public organizerHackathonCount;
    mapping(address => mapping(uint256 => address)) public organizerHackathons;

    uint256 public totalHackathons;
    uint256 public constant MAX_PRIZE_CLAIM_COOLDOWN = 7 days;

    // Implementation contract addresses
    address public immutable implementation;

    // Voting system implementation addresses
    address public immutable openVotingImplementation;
    address public immutable RevealCommitVotingImplementation;
    address public immutable zkVotingImplementation;
    address public immutable qvWrapperImplementation;

    // Curve Finance Router and token addresses
    address public immutable curveRouter;
    address public immutable weth;
    address public immutable pyusd;

    event HackathonCreated(
        address indexed hackathonAddress,
        uint256 hackathonId,
        address indexed organizer,
        uint256 prizePool,
        VotingSystemType votingSystem,
        bool useQuadraticVoting
    );

    event VotingSystemDeployed(
        address indexed votingContract,
        VotingSystemType systemType,
        bool useQuadraticVoting
    );

    event EmergencyWithdrawal(
        address indexed judge,
        address indexed token,
        uint256 amount
    );

    event EthToPyusdConversion(
        uint256 ethAmount,
        uint256 pyusdAmount,
        uint256 minPyusdOut
    );

    /**
     * @dev Constructor that sets the implementation contract address and initializes judge governance
     * @param _implementation Address of the HackathonImplementation contract
     * @param _curveRouter Address of Curve router for ETH/PYUSD swaps
     * @param _weth Address of WETH token
     * @param _pyusd Address of PYUSD token
     */
    constructor(
        address _implementation,
        address _curveRouter,
        address _weth,
        address _pyusd
    )
        JudgeCouncil(address(this))
    {

        implementation = _implementation;
        curveRouter = _curveRouter;
        weth = _weth;
        pyusd = _pyusd;

        // Deploy voting system implementations (initial)
        openVotingImplementation = address(
            new OpenVoting()
        );

        RevealCommitVotingImplementation = address(
            new RevealCommitVoting()
        );

        zkVotingImplementation = address(
            new ZKVotingSystem()
        );

        qvWrapperImplementation = address(
            new QVWrapper()
        );

        _addFirstGlobalJudge(
            msg.sender
        );
    }

    /**
     * @dev Converts ETH to PYUSD using Curve Finance
     * @param _ethAmount Amount of ETH to convert
     * @param _minPyusdOut Minimum PYUSD amount expected (for slippage protection)
     * @return pyusdAmount Amount of PYUSD received
     */
    function _convertEthToPyusd(
        uint256 _ethAmount,
        uint256 _minPyusdOut
    )
        internal
        returns (uint256 pyusdAmount)
    {
        require(
            _ethAmount > 0,
            "ETH amount must be greater than 0"
        );

        // Execute the swap using Curve router
        // Route: ETH -> USDC -> PYUSD
        address[11] memory route = [
            0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, // ETH
            0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC
            pyusd,      // PYUSD
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0)  // Unused
        ];

        uint256[5][5] memory swapParams = [
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)]  // Unused
        ];

        address[5] memory pools = [
            0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B, // ETH/USDC pool
            0x383E6b4437b59fff47B619CBA855CA29342A8559, // USDC/PYUSD pool
            address(0), // Unused
            address(0), // Unused
            address(0)  // Unused
        ];

        pyusdAmount = ICurveRouter(curveRouter).exchange{
            value: _ethAmount
        }(
            route,
            swapParams,
            _ethAmount,
            _minPyusdOut,
            pools
        );

        // Emit conversion event
        emit EthToPyusdConversion(
            _ethAmount,
            pyusdAmount,
            _minPyusdOut
        );

        return pyusdAmount;
    }

    /**
     * @dev Estimates how much ETH is needed to get the required PYUSD amount
     * @param _pyusdAmount Required PYUSD amount
     * @return Estimated ETH amount needed
     */
    function _estimateEthForPyusd(
        uint256 _pyusdAmount
    )
        internal
        view
        returns (uint256)
    {
        // Get the current exchange rate from Curve pool
        uint256 estimatedPyusd = this.estimatePyusdOutput(1 ether);

        // Calculate how much ETH is needed for the PYUSD amount
        // Add 5% buffer for slippage
        return (_pyusdAmount * 1 ether * 105) / (estimatedPyusd * 100);
    }

    /**
     * @dev Helper function to create and initialize hackathon (reduces stack depth)
     */
    function _createAndInitializeHackathon(
        uint256 _hackathonId,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _minimumSponsorContribution,
        uint256 _stakeAmount,
        uint256[] memory _prizeDistribution,
        address[] memory _selectedJudges,
        uint256 _pyusdAmount,
        uint256 _estimatedEthForPrizes
    )
        internal
        returns (address hackathonAddress)
    {
        // Clone the implementation contract
        hackathonAddress = Clones.clone(
            implementation
        );

        // Transfer PYUSD to the hackathon contract
        IERC20(pyusd).safeTransfer(
            hackathonAddress,
            _pyusdAmount
        );

        // Initialize the cloned contract with remaining ETH for judges
        _initializeHackathon(
            hackathonAddress,
            _hackathonId,
            _startTime,
            _endTime,
            _minimumSponsorContribution,
            _stakeAmount,
            _prizeDistribution,
            _selectedJudges,
            msg.value - _estimatedEthForPrizes
        );

        return hackathonAddress;
    }

    /**
     * @dev Helper function to initialize hackathon (reduces stack depth)
     */
    function _initializeHackathon(
        address hackathonAddress,
        uint256 _hackathonId,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _minimumSponsorContribution,
        uint256 _stakeAmount,
        uint256[] memory _prizeDistribution,
        address[] memory _selectedJudges,
        uint256 _remainingEth
    )
        internal
    {
        Hackathon(hackathonAddress).initialize{
            value: _remainingEth
        }(
            msg.sender, // organizer
            _hackathonId,
            _startTime,
            _endTime,
            _minimumSponsorContribution,
            _stakeAmount,
            _prizeDistribution,
            address(this), // factory
            _selectedJudges,
            pyusd // PYUSD token address
        );
    }

    /**
     * @notice Creates a new hackathon contract
     * @dev Deploys a new Hackathon contract with the specified parameters and tracks it
     * @param _hackathonId Unique identifier for the hackathon
     * @param _startTime Start time in Unix timestamp
     * @param _endTime End time in Unix timestamp
     * @param _minimumSponsorContribution Minimum contribution required to become a sponsor
     * @param _stakeAmount Amount participants must stake when joining
     * @param _prizeDistribution Array defining how the prize pool is distributed among winners
     * @param _selectedJudges Array of judge addresses to assign to this hackathon
     * @param _votingConfig Voting system configuration
     * @return hackathonAddress Address of the newly created hackathon
     */
    function createHackathon(
        uint256 _hackathonId,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _minimumSponsorContribution,
        uint256 _stakeAmount,
        uint256[] memory _prizeDistribution,
        address[] memory _selectedJudges,
        VotingConfig memory _votingConfig
    )
        external
        payable
        returns (address hackathonAddress)
    {
        // Basic validation
        require(
            _startTime > block.timestamp,
            "Start time must be in the future"
        );

        require(
            _endTime > _startTime,
            "End time must be after start time"
        );

        // Calculate total prize distribution
        uint256 totalPrizeDistribution = 0;
        for (uint256 i = 0; i < _prizeDistribution.length; i++) {
            totalPrizeDistribution += _prizeDistribution[i];
        }

        // Estimate how much ETH is needed for the prize distribution
        // This is a rough estimate - in practice, the organizer should send more ETH
        uint256 estimatedEthForPrizes = _estimateEthForPyusd(
            totalPrizeDistribution
        );

        // Convert only the prize distribution amount to PYUSD
        uint256 pyusdAmount = _convertEthToPyusd(
            estimatedEthForPrizes,
            10000000 // @TODO: pass dynamically through constructor (6 decimals)
        );

        // Validate that the converted PYUSD amount matches the total prize distribution
        require(
            pyusdAmount >= totalPrizeDistribution,
            "Insufficient PYUSD amount for prize distribution"
        );

        // Validate that all selected judges are in the global registry
        for (uint256 i = 0; i < _selectedJudges.length; i++) {
            require(
                isJudgeOrDelegate(_selectedJudges[i]),
                "Selected judge is not in global registry"
            );
        }

        // Deploy voting system
        _deployVotingSystem(
            _votingConfig,
            _selectedJudges
        );

        // Create and initialize hackathon
        hackathonAddress = _createAndInitializeHackathon(
            _hackathonId,
            _startTime,
            _endTime,
            _minimumSponsorContribution,
            _stakeAmount,
            _prizeDistribution,
            _selectedJudges,
            pyusdAmount,
            estimatedEthForPrizes
        );

        // Store the hackathon address
        uint256 organizerIndex = organizerHackathonCount[msg.sender];
        organizerHackathons[msg.sender][organizerIndex] = hackathonAddress;
        organizerHackathonCount[msg.sender]++;
        totalHackathons++;

        emit HackathonCreated(
            hackathonAddress,
            _hackathonId,
            msg.sender,
            pyusdAmount,
            _votingConfig.systemType,
            _votingConfig.useQuadraticVoting
        );

        return hackathonAddress;
    }

    /**
     * @dev Deploy voting system based on configuration using clone pattern
     * @param _votingConfig Voting system configuration
     * @param _judges Array of judge addresses
     * @return votingContract Address of the deployed voting contract
     */
    function _deployVotingSystem(
        VotingConfig memory _votingConfig,
        address[] memory _judges
    )
        internal
        returns (address votingContract)
    {
        // Clone base voting system
        if (_votingConfig.systemType == VotingSystemType.OPEN) {
            votingContract = Clones.clone(openVotingImplementation);
        } else if (_votingConfig.systemType == VotingSystemType.COMMIT_REVEAL) {
            votingContract = Clones.clone(RevealCommitVotingImplementation);
        } else if (_votingConfig.systemType == VotingSystemType.ZK_SNARK) {
            votingContract = Clones.clone(zkVotingImplementation);
        }

        // Initialize the voting system
        IVotingSystem(votingContract).initialize(
            _votingConfig.votingPowerPerJudge,
            _votingConfig.maxWinners,
            _judges
        );

        // Wrap with quadratic voting if enabled
        if (_votingConfig.useQuadraticVoting) {
            // Clone QVWrapper for gas efficiency
            address qvWrapper = Clones.clone(
                qvWrapperImplementation
            );

            // Initialize the QVWrapper
            QVWrapper(qvWrapper).initialize(
                votingContract,
                _votingConfig.votingPowerPerJudge
            );

            votingContract = qvWrapper;
        }

        emit VotingSystemDeployed(
            votingContract,
            _votingConfig.systemType,
            _votingConfig.useQuadraticVoting
        );

        return votingContract;
    }

    /**
     * @dev Gets the total number of hackathons created
     */
    function getHackathonCount()
        external
        view
        returns (uint256)
    {
        return totalHackathons;
    }

    /**
     * @dev Gets the number of hackathons created by a specific organizer
     * @param _organizer Address of the organizer
     */
    function getOrganizerHackathonCount(
        address _organizer
    )
        external
        view
        returns (uint256)
    {
        return organizerHackathonCount[
            _organizer
        ];
    }

    /**
     * @dev Gets a specific hackathon created by an organizer
     * @param _organizer Address of the organizer
     * @param _index Index of the hackathon
     */
    function getOrganizerHackathon(
        address _organizer,
        uint256 _index
    )
        external
        view
        returns (address)
    {
        require(
            _index < organizerHackathonCount[_organizer],
            "Index out of bounds"
        );

        return organizerHackathons[_organizer][_index];
    }

    /**
     * @dev Gets the current PYUSD balance of the factory
     */
    function getPyusdBalance()
        external
        view
        returns (uint256)
    {
        return IERC20(pyusd).balanceOf(
            address(this)
        );
    }

    /**
     * @dev Gets the current WETH balance of the factory
     */
    function getWethBalance()
        external
        view
        returns (uint256)
    {
        return IERC20(weth).balanceOf(
            address(this)
        );
    }

    /**
     * @dev Estimate PYUSD output for given ETH input using Curve pool
     * @param _ethAmount Amount of ETH to convert
     * @return Estimated PYUSD amount
     */
    function estimatePyusdOutput(
        uint256 _ethAmount
    )
        external
        view
        returns (uint256)
    {
        // Route: ETH -> USDC -> PYUSD
        address[11] memory route = [
            0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, // ETH
            0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC
            pyusd,      // PYUSD
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0), // Unused
            address(0)  // Unused
        ];

        uint256[5][5] memory swapParams = [
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)], // Unused
            [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)]  // Unused
        ];

        address[5] memory pools = [
            0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B, // ETH/USDC pool
            0x383E6b4437b59fff47B619CBA855CA29342A8559, // USDC/PYUSD pool
            address(0), // Unused
            address(0), // Unused
            address(0)  // Unused
        ];

        return ICurveRouter(curveRouter).get_exchange_amount(
            route,
            swapParams,
            _ethAmount,
            pools
        );
    }

    /**
     * @dev Get Curve router information
     * @return router address, WETH address, PYUSD address
     */
    function getCurveRouterInfo()
        external
        view
        returns (
            address,
            address,
            address
        )
    {
        return (
            curveRouter,
            weth,
            pyusd
        );
    }

    /**
     * @dev Emergency function to withdraw any remaining PYUSD (only global judges)
     */
    function emergencyWithdrawPyusd()
        external
        onlyGlobalJudge
    {
        uint256 balance = IERC20(pyusd).balanceOf(
            address(this)
        );

        if (balance > 0) {
            IERC20(pyusd).safeTransfer(
                msg.sender,
                balance
            );
        }
    }

    /**
     * @notice Emergency withdrawal function for ETH - only global judges can execute
     * @dev Allows global judges to withdraw ETH in emergency situations
     * @param _amount Amount of ETH to withdraw
     */
    function emergencyWithdrawETH(
        uint256 _amount
    )
        external
        onlyGlobalJudge
    {
        // Transfer ETH to the global judge
        payable(msg.sender).transfer(
            _amount
        );

        emit EmergencyWithdrawal(
            msg.sender,
            address(0),
            _amount
        );
    }
}

// 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.4.0) (interfaces/IERC1363.sol)

pragma solidity >=0.6.2;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

File 4 of 24 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

import {IERC165} from "../utils/introspection/IERC165.sol";

File 5 of 24 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;

import {IERC20} from "../token/ERC20/IERC20.sol";

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    error CloneArgumentsTooLong();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     */
    function clone(address implementation) internal returns (address instance) {
        return clone(implementation, 0);
    }

    /**
     * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
     * to the new contract.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function clone(address implementation, uint256 value) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(value, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple times will revert, since
     * the clones cannot be deployed twice at the same address.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        return cloneDeterministic(implementation, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
     * a `value` parameter to send native currency to the new contract.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministic(
        address implementation,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(value, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create opcode, which should never revert.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     */
    function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
        return cloneWithImmutableArgs(implementation, args, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
     * parameter to send native currency to the new contract.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneWithImmutableArgs(
        address implementation,
        bytes memory args,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        assembly ("memory-safe") {
            instance := create(value, add(bytecode, 0x20), mload(bytecode))
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
     * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
     * at the same address.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal returns (address instance) {
        return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
     * but with a `value` parameter to send native currency to the new contract.
     *
     * WARNING: This function does not check if `implementation` has code. A clone that points to an address
     * without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
     * have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.deploy(value, salt, bytecode);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.computeAddress(salt, keccak256(bytecode), deployer);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
    }

    /**
     * @dev Get the immutable args attached to a clone.
     *
     * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
     *   function will return an empty array.
     * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
     *   `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
     *   creation.
     * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
     *   function should only be used to check addresses that are known to be clones.
     */
    function fetchCloneArgs(address instance) internal view returns (bytes memory) {
        bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
        assembly ("memory-safe") {
            extcodecopy(instance, add(result, 32), 45, mload(result))
        }
        return result;
    }

    /**
     * @dev Helper that prepares the initcode of the proxy with immutable args.
     *
     * An assembly variant of this function requires copying the `args` array, which can be efficiently done using
     * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
     * abi.encodePacked is more expensive but also more portable and easier to review.
     *
     * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
     * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
     */
    function _cloneCodeWithImmutableArgs(
        address implementation,
        bytes memory args
    ) private pure returns (bytes memory) {
        if (args.length > 24531) revert CloneArgumentsTooLong();
        return
            abi.encodePacked(
                hex"61",
                uint16(args.length + 45),
                hex"3d81600a3d39f3363d3d373d3d3d363d73",
                implementation,
                hex"5af43d82803e903d91602b57fd5bf3",
                args
            );
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @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.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// 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/Create2.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}

File 11 of 24 : Errors.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @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
pragma solidity ^0.8.28;

import "./StakeSystem.sol";
import "./VotingSystem.sol";
import "./JudgingSystem.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Hackathon is StakeSystem, VotingSystem, JudgingSystem {

    struct Submission {
        address participant;
        string projectName;
        string projectUrl;
        uint256 submissionTime;
        uint256 score;
        bool isEvaluated;
    }

    struct Sponsor {
        address sponsorAddress;
        uint256 contribution;
        bool isActive;
        uint256 distributedAmount; // Amount this sponsor has already distributed
        bool isETHSponsor; // true if contributed ETH, false if contributed tokens
    }

    uint256 public hackathonId;
    uint256 public startTime;
    uint256 public endTime;
    uint256 public prizePool;
    address public organizer;
    bool public isActive;

    address public factory;
    uint256 public participantCount;
    uint256 public minimumSponsorContribution;
    uint256 public judgingDuration;
    uint256 public votingStartTime;
    uint256 public claimingStartTime;

    mapping(address => Submission) public submissions;
    mapping(address => bool) public isRegistered;
    mapping(address => bool) public hasSubmitted;
    uint256 public totalSubmissions;

    mapping(address => Sponsor) public sponsors;

    address[] public sponsorList;
    uint256 public totalSponsorContributions;

    // Token sponsor tracking
    mapping(address => mapping(address => uint256)) public tokenContributions; // sponsor => token => amount

    // Sponsor-specific prize pools
    mapping(address => uint256) public sponsorPrizePools; // sponsor => amount available for distribution
    mapping(address => address) public sponsorTokenAddresses; // sponsor => token address (for token sponsors)
    mapping(address => uint256) public totalTokenContributions; // token => total amount

    event ParticipantRegistered(
        address indexed participant
    );

    event SubmissionMade(
        address indexed participant,
        string projectName
    );

    event PrizeDistributed(
        address indexed winner,
        uint256 amount
    );

    event EmergencyWithdrawal(
        address indexed judge,
        address indexed token,
        uint256 amount
    );

    event HackathonEnded();

    event SponsorAdded(
        address indexed sponsor,
        uint256 contribution
    );

    event SubmissionScored(
        address indexed participant,
        uint256 score
    );

    event TokenSponsorAdded(
        address indexed sponsor,
        address indexed token,
        uint256 amount
    );

    modifier onlyOrganizer() {
        require(
            msg.sender == organizer,
            "Only organizer can call this function"
        );
        _;
    }

    modifier hackathonActive() {
        require(
            isActive,
            "Hackathon is not active"
        );

        require(
            block.timestamp >= startTime,
            "Hackathon has not started yet"
        );

        require(
            block.timestamp <= endTime,
            "Hackathon has ended"
        );
        _;
    }

    modifier onlyRegistered() {
        require(
            isRegistered[msg.sender],
            "Not registered for this hackathon"
        );
        _;
    }

    modifier onlySponsor() {
        require(
            sponsors[msg.sender].isActive,
            "Only sponsors can call this function"
        );
        _;
    }

    /**
     * @dev Internal function to initialize hackathon parameters (for cloning)
     * @notice This function is called by the implementation contract during initialization
     */
    function _initializeHackathon(
        uint256 _hackathonId,
        uint256 _startTime,
        uint256 _endTime,
        uint256[] memory _prizeDistribution,
        uint256 _stakeAmount,
        uint256 _prizeClaimCooldown,
        uint256 _judgingDuration,
        address[] memory _selectedJudges
    )
        internal
    {
        require(
            _startTime > block.timestamp,
            "Start time must be in the future"
        );

        require(
            _endTime > _startTime,
            "End time must be after start time"
        );

        require(
            _prizeDistribution.length > 0,
            "Must have at least 1 winner"
        );

        require(
            _prizeClaimCooldown > 0,
            "Prize claim cooldown must be greater than 0"
        );

        require(
            _judgingDuration >= 2 hours && _judgingDuration <= 2 days,
            "Judging duration must be between 2 hours and 2 days"
        );

        // Validate prize distribution
        uint256 totalDistribution = 0;
        for (uint256 i = 0; i < _prizeDistribution.length; i++) {
            require(
                _prizeDistribution[i] > 0,
                "Each prize distribution must be greater than 0"
            );
            totalDistribution += _prizeDistribution[i];
        }

        // Initialize all hackathon state variables
        hackathonId = _hackathonId;
        startTime = _startTime;
        endTime = _endTime;
        isActive = true;
        participantCount = 0;
        totalSponsorContributions = 0;
        stakeAmount = _stakeAmount;
        prizeClaimCooldown = _prizeClaimCooldown;
        judgingDuration = _judgingDuration;
        pointsPerJudge = 100;
        votingOpen = false;
        totalStakes = 0;

        // Calculate all phase timestamps upfront
        votingStartTime = _endTime; // Voting starts when hackathon ends
        votingEndTime = votingStartTime + _judgingDuration;
        claimingStartTime = votingEndTime + _prizeClaimCooldown;

        // Initialize inherited systems
        // StakeSystem initialization
        stakeAmount = _stakeAmount;

        // VotingSystem initialization
        prizeDistribution = _prizeDistribution;
        prizeClaimCooldown = _prizeClaimCooldown;
        pointsPerJudge = 100;
        maxWinners = _prizeDistribution.length;
        prizePool = totalDistribution;

        // Initialize judging system
        // Judge rewards are now handled via remaining ETH, not percentage

        // Add selected judges
        for (uint256 i = 0; i < _selectedJudges.length; i++) {
            require(_selectedJudges[i] != address(0), "Invalid judge address");
            isJudge[_selectedJudges[i]] = true;
            judgeList.push(_selectedJudges[i]);
        }
    }

    /**
     * @dev Initialize the cloned hackathon with actual parameters
     * @notice This function is called once after cloning to set the actual hackathon parameters
     * @notice Only the factory can call this function
     */
    function initialize(
        address _organizer,
        uint256 _hackathonId,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _minimumSponsorContribution,
        uint256 _stakeAmount,
        uint256[] memory _prizeDistribution,
        address _factory,
        address[] memory _selectedJudges,
        address _pyusdToken
    )
        external
        payable
    {
        // Only factory can call initialize
        require(
            msg.sender == _factory,
            "Only factory can initialize"
        );

        // Prevent multiple initialization
        require(
            organizer == address(0),
            "Already initialized"
        );

        // Set the organizer and factory
        organizer = _organizer;
        factory = _factory;

        // Set the PYUSD token
        pyusdToken = IERC20(_pyusdToken);
        _setPyusdToken(_pyusdToken);

        // Set parameters
        minimumSponsorContribution = _minimumSponsorContribution;
        uint256 _prizeClaimCooldown = 1 days; // Default prize claim cooldown
        uint256 _judgingDuration = 1 days; // Default judging duration

        // Set judge reward pool with the remaining ETH sent to the contract
        _setJudgeRewardPool(
            msg.value
        );

        // Initialize the hackathon with the provided parameters
        _initializeHackathon(
            _hackathonId,
            _startTime,
            _endTime,
            _prizeDistribution,
            _stakeAmount,
            _prizeClaimCooldown,
            _judgingDuration,
            _selectedJudges
        );
    }

    /**
     * @dev Registers a participant for this hackathon
     */
    function register()
        external
        payable
    {
        require(
            isActive,
            "Hackathon is not active"
        );

        require(
            block.timestamp < startTime,
            "Registration closed - hackathon has started"
        );

        require(
            isRegistered[msg.sender] == false,
            "Already registered"
        );

        isRegistered[msg.sender] = true;
        participantCount++;

        // Use inherited stake system
        _depositStake(
            msg.sender
        );

        emit ParticipantRegistered(
            msg.sender
        );
    }

    /**
     * @dev Submits a project for this hackathon
     * @param _projectName Name of the project
     * @param _projectUrl URL of the project repository or demo
     */
    function submitProject(
        string memory _projectName,
        string memory _projectUrl
    )
        external
        onlyDuringSubmission
        onlyRegistered
    {
        require(
            hasSubmitted[msg.sender] == false,
            "Already submitted"
        );

        submissions[msg.sender] = Submission({
            participant: msg.sender,
            projectName: _projectName,
            projectUrl: _projectUrl,
            submissionTime: block.timestamp,
            score: 0,
            isEvaluated: false
        });

        hasSubmitted[msg.sender] = true;
        totalSubmissions++;

        // Return stake to participant
        uint256 stake = participantStakes[msg.sender];
        if (stake > 0) {
            participantStakes[msg.sender] = 0;
            totalStakes -= stake;
            payable(msg.sender).transfer(stake);
            emit StakeReturned(
                msg.sender, stake);
        }

        emit SubmissionMade(
            msg.sender,
            _projectName
        );
    }

    /**
     * @dev Checks if an address is registered for this hackathon
     * @param _participant Address to check
     */
    function isParticipantRegistered(
        address _participant
    )
        external
        view
        returns (bool)
    {
        return isRegistered[
            _participant
        ];
    }

    /**
     * @dev Gets submission details for a participant
     * @param _participant Address of the participant
     */
    function getSubmission(
        address _participant
    )
        external
        view
        returns (
            address participant,
            string memory projectName,
            string memory projectUrl,
            uint256 submissionTime,
            uint256 score,
            bool isEvaluated
        )
    {
        Submission storage submission = submissions[
            _participant
        ];

        return (
            submission.participant,
            submission.projectName,
            submission.projectUrl,
            submission.submissionTime,
            submission.score,
            submission.isEvaluated
        );
    }

    /**
     * @dev Distributes prize to winner (only organizer or sponsors)
     * @param _winner Address of the winner
     * @param _amount Amount to distribute
     */
    function distributePrize(
        address _winner,
        uint256 _amount
    )
        external
        onlySponsor
    {
        require(!isActive || block.timestamp > endTime, "Hackathon is still active");
        require(_amount > 0, "Amount must be greater than 0");

        // If organizer is distributing, use main prize pool
        if (msg.sender == organizer) {
            require(_amount <= prizePool, "Amount exceeds prize pool");
            prizePool -= _amount;
            payable(_winner).transfer(_amount);
        } else {
            // If sponsor is distributing, use their specific prize pool
            require(_amount <= sponsorPrizePools[msg.sender], "Amount exceeds sponsor's available prize pool");
            require(_amount <= (sponsors[msg.sender].contribution - sponsors[msg.sender].distributedAmount), "Amount exceeds sponsor's remaining contribution");

            // Update sponsor's distributed amount
            sponsors[msg.sender].distributedAmount += _amount;
            sponsorPrizePools[msg.sender] -= _amount;

            // Distribute based on sponsor type (ETH or token)
            if (sponsors[msg.sender].isETHSponsor) {
                payable(_winner).transfer(_amount);
            } else {
                // For token sponsors, transfer tokens
                address tokenAddress = sponsorTokenAddresses[msg.sender];
                IERC20(tokenAddress).transfer(_winner, _amount);
            }
        }

        emit PrizeDistributed(
            _winner,
            _amount
        );
    }

    /**
     * @dev Gets hackathon details
     */
    function getHackathonDetails()
        external
        view
        returns (
            uint256 _hackathonId,
            uint256 _startTime,
            uint256 _endTime,
            uint256 _prizePool,
            address _organizer,
            bool _isActive,
            uint256 _participantCount
        )
    {
        return (
            hackathonId,
            startTime,
            endTime,
            prizePool,
            organizer,
            isActive,
            participantCount
        );
    }

    /**
     * @dev Checks if hackathon is currently accepting registrations
     */
    function isRegistrationOpen()
        external
        view
        returns (bool)
    {
        return isActive && block.timestamp < startTime;
    }

    /**
     * @dev Checks if hackathon is currently accepting submissions
     */
    function isSubmissionOpen()
        external
        view
        returns (bool)
    {
        return isActive && block.timestamp >= startTime && block.timestamp <= endTime;
    }


    modifier onlyDuringSubmission() {
        require(isActive && block.timestamp >= startTime && block.timestamp <= endTime, "Not during submission phase");
        _;
    }

    modifier onlyDuringVoting() {
        require(block.timestamp >= votingStartTime && block.timestamp <= votingEndTime, "Not during voting phase");
        _;
    }

    modifier onlyDuringClaiming() {
        require(block.timestamp >= claimingStartTime, "Not during claiming phase");
        _;
    }

    /**
     * @dev Check if hackathon is currently active based on timestamps
     */
    function _updateActiveStatus() internal {
        isActive = block.timestamp >= startTime && block.timestamp <= endTime;
    }

    /**
     * @dev Allows anyone to become a sponsor by contributing the minimum amount
     */
    function becomeSponsor()
        external
        payable
    {
        require(
            msg.value >= minimumSponsorContribution,
            "Contribution below minimum required"
        );

        require(
            sponsors[msg.sender].isActive == false,
            "Already a sponsor"
        );

        sponsors[msg.sender] = Sponsor({
            sponsorAddress: msg.sender,
            contribution: msg.value,
            isActive: true,
            distributedAmount: 0,
            isETHSponsor: true
        });

        sponsorList.push(msg.sender);
        totalSponsorContributions += msg.value;

        // Set up sponsor-specific prize pool (ETH contribution)
        sponsorPrizePools[msg.sender] = msg.value;

        emit SponsorAdded(
            msg.sender,
            msg.value
        );
    }

    /**
     * @dev Allows anyone to become a sponsor by contributing tokens
     * @param _tokenAddress Address of the ERC20 token to contribute
     * @param _tokenAmount Amount of tokens to contribute
     */
    function becomeSponsorWithToken(
        address _tokenAddress,
        uint256 _tokenAmount
    )
        external
    {
        require(
            _tokenAmount >= minimumSponsorContribution,
            "Contribution below minimum required"
        );

        require(
            sponsors[msg.sender].isActive == false,
            "Already a sponsor"
        );

        // Transfer tokens from sender to this contract
        IERC20 token = IERC20(
            _tokenAddress
        );

        token.transferFrom(
            msg.sender,
            address(this),
            _tokenAmount
        );

        // Add sponsor to the list
        sponsors[msg.sender] = Sponsor({
            sponsorAddress: msg.sender,
            contribution: _tokenAmount,
            isActive: true,
            distributedAmount: 0,
            isETHSponsor: false
        });

        sponsorList.push(msg.sender);
        totalSponsorContributions += _tokenAmount;

        // Track token contributions
        tokenContributions[msg.sender][_tokenAddress] = _tokenAmount;
        sponsorTokenAddresses[msg.sender] = _tokenAddress;

        // Set up sponsor-specific prize pool (token contribution)
        sponsorPrizePools[msg.sender] = _tokenAmount;
        totalTokenContributions[_tokenAddress] += _tokenAmount;

        emit TokenSponsorAdded(
            msg.sender,
            _tokenAddress,
            _tokenAmount
        );
    }

    /**
     * @dev Adds a judge to the hackathon (only organizer can call)
     * @param _judge Address of the judge
     */
    function addJudge(
        address _judge
    )
        public
        override
        onlyOrganizer
    {
        require(isActive, "Cannot add judges to inactive hackathon");
        require(block.timestamp < endTime, "Cannot add judges after hackathon ends");

        // Call inherited function directly
        JudgingSystem.addJudge(_judge);
    }

    /**
     * @dev Allows a judge to score a submission
     * @param _participant Address of the participant
     * @param _score Score to assign (0-100)
     */
    function scoreSubmission(
        address _participant,
        uint256 _score
    )
        external
    {
        require(
            isJudgeOrDelegate(msg.sender),
            "Only judges can score"
        );

        require(
            hasSubmitted[_participant],
            "No submission found"
        );

        require(_score <= 100, "Score must be between 0 and 100");
        require(!submissions[_participant].isEvaluated, "Submission already evaluated");

        submissions[_participant].score = _score;
        submissions[_participant].isEvaluated = true;

        emit SubmissionScored(
            _participant,
            _score
        );
    }

    /**
     * @dev Gets all sponsors
     */
    function getSponsors()
        external
        view
        returns (address[] memory)
    {
        return sponsorList;
    }

    /**
     * @dev Gets sponsor contribution amount
     * @param _sponsor Address of the sponsor
     */
    function getSponsorContribution(
        address _sponsor
    )
        external
        view
        returns (uint256)
    {
        return sponsors[_sponsor].contribution;
    }

    /**
     * @dev Claim judge reward (hackathon-specific)
     */
    function claimJudgeReward()
        external
    {
        require(
            isJudgeOrDelegate(msg.sender),
            "Only judges or their delegates can claim rewards"
        );

        address actualJudge = isJudge[msg.sender]
            ? msg.sender
            : delegateToJudge[msg.sender];

        require(
            !hasReceivedJudgeReward[actualJudge],
            "Already claimed judge reward"
        );

        require(
            judgeList.length > 0,
            "No judges to distribute rewards to"
        );

        uint256 rewardPerJudge = judgeRewardPool / judgeList.length;

        require(
            rewardPerJudge > 0,
            "Insufficient reward per judge"
        );

        hasReceivedJudgeReward[actualJudge] = true;

        payable(msg.sender).transfer(
            rewardPerJudge
        );

        emit JudgeRewardDistributed(
            actualJudge,
            rewardPerJudge
        );
    }


    /**
     * @dev Gets total prize pool including sponsor contributions
     */
    function getTotalPrizePool()
        external
        view
        returns (uint256)
    {
        return prizePool;
    }

    /**
     * @dev Gets minimum sponsor contribution required
     */
    function getMinimumSponsorContribution()
        external
        view
        returns (uint256)
    {
        return minimumSponsorContribution;
    }

    /**
     * @dev Gets token contribution amount for a specific sponsor and token
     * @param _sponsor Address of the sponsor
     * @param _tokenAddress Address of the token
     */
    function getTokenContribution(
        address _sponsor,
        address _tokenAddress
    )
        external
        view
        returns (uint256)
    {
        return tokenContributions[_sponsor][_tokenAddress];
    }

    /**
     * @dev Gets sponsor's available prize pool for distribution
     * @param _sponsor Address of the sponsor
     * @return Available amount the sponsor can still distribute
     */
    function getSponsorAvailablePrize(address _sponsor) external view returns (uint256) {
        return sponsorPrizePools[_sponsor];
    }

    /**
     * @dev Gets sponsor's total contribution
     * @param _sponsor Address of the sponsor
     * @return Total contribution amount
     */
    function getSponsorTotalContribution(
        address _sponsor
    )
        external
        view
        returns (uint256)
    {
        return sponsors[_sponsor].contribution;
    }

    /**
     * @dev Gets sponsor's distributed amount
     * @param _sponsor Address of the sponsor
     * @return Amount already distributed by this sponsor
     */
    function getSponsorDistributedAmount(
        address _sponsor
    )
        external
        view
        returns (uint256)
    {
        return sponsors[_sponsor].distributedAmount;
    }

    /**
     * @dev Gets sponsor's token address (for token sponsors)
     * @param _sponsor Address of the sponsor
     * @return Token address used by this sponsor
     */
    function getSponsorTokenAddress(
        address _sponsor
    )
        external
        view
        returns (address)
    {
        return sponsorTokenAddresses[
            _sponsor
        ];
    }

    /**
     * @dev Gets total token contributions for a specific token
     * @param _tokenAddress Address of the token
     */
    function getTotalTokenContributions(
        address _tokenAddress
    )
        external
        view
        returns (uint256)
    {
        return totalTokenContributions[
            _tokenAddress
        ];
    }


    // ========== Voting System Functions ==========

    /**
     * @dev Allows a judge to vote on submissions by allocating points
     * @param _participant Address of the participant to vote for
     * @param _points Points to allocate (0-100)
     */
    function voteForSubmission(
        address _participant,
        uint256 _points
    )
        external
        onlyDuringVoting
    {
        _updateActiveStatus();

        require(
            isJudgeOrDelegate(msg.sender),
            "Only judges can vote"
        );

        require(
            hasSubmitted[_participant],
            "Participant has not submitted"
        );

        require(
            _points <= pointsPerJudge,
            "Cannot allocate more points than allowed"
        );

        require(
            hasVoted[msg.sender] == false,
            "Judge has already voted"
        );

        // Check if judge has already allocated points to this participant
        uint256 currentPoints = judgeVotes[msg.sender][_participant];

        require(
            currentPoints == 0,
            "Judge has already voted for this participant"
        );

        judgeVotes[msg.sender][_participant] = _points;
        totalPoints[_participant] += _points;

        emit JudgeVoted(
            msg.sender,
            _participant,
            _points
        );
    }

    /**
     * @dev Allows winners to claim their prize after cooldown period
     */
    function claimPrize()
        external
        onlyDuringClaiming
    {
        require(
            hasSubmitted[msg.sender],
            "Must have submitted a project"
        );

        _claimPrize(
            msg.sender,
            prizePool
        );
    }

    /**
     * @notice Get prize amount for a participant
     * @dev Uses VotingSystem's internal function to calculate prize amount
     * @param _participant Address of the participant
     * @return Prize amount for the participant
     */
    function getPrizeAmount(
        address _participant
    )
        external
        view
        returns (uint256)
    {
        return _getPrizeAmount(
            _participant
        );
    }

    /**
     * @notice Check if a participant is a winner
     * @dev Public function to check if a participant is a winner
     * @param _participant Address of the participant
     * @return True if the participant is a winner, false otherwise
     */
    function isWinner(
        address _participant
    )
        external
        view
        returns (bool)
    {
        return _isWinner(
            _participant
        );
    }

    /**
     * @notice Check if a participant has claimed their prize
     * @dev Public function to check if a participant has claimed their prize
     * @param _participant Address of the participant
     * @return True if prize has been claimed, false otherwise
     */
    function getHasClaimedPrize(
        address _participant
    )
        external
        view
        returns (bool)
    {
        return hasClaimedPrize[
            _participant
        ];
    }

    /**
     * @notice Emergency withdrawal function for ETH - only judges can execute
     * @dev Allows judges to withdraw ETH in emergency situations
     * @param _amount Amount of ETH to withdraw
     */
    function emergencyWithdrawETH(
        uint256 _amount
    )
        external
        onlyJudge
    {
        require(
            _amount > 0,
            "Amount must be greater than 0"
        );
        require(
            _amount <= address(this).balance,
            "Insufficient ETH balance"
        );

        // Transfer ETH to the judge
        payable(msg.sender).transfer(_amount);

        emit EmergencyWithdrawal(
            msg.sender,
            address(0),
            _amount
        );
    }

    /**
     * @notice Emergency withdrawal function for PYUSD tokens - only judges can execute
     * @dev Allows judges to withdraw PYUSD tokens in emergency situations
     * @param _amount Amount of PYUSD tokens to withdraw
     */
    function emergencyWithdrawPYUSD(
        uint256 _amount
    )
        external
        onlyJudge
    {
        require(
            _amount > 0,
            "Amount must be greater than 0"
        );
        require(
            _amount <= pyusdToken.balanceOf(address(this)),
            "Insufficient PYUSD balance"
        );

        // Transfer PYUSD tokens to the judge
        pyusdToken.transfer(msg.sender, _amount);

        emit EmergencyWithdrawal(
            msg.sender,
            address(pyusdToken),
            _amount
        );
    }
}

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

/**
 * @title IVotingSystem
 * @dev Interface for all voting systems in DeHack platform
 * @notice Supports batch voting for multiple participants in one transaction
 */
interface IVotingSystem {

    // ============ EVENTS ============

    event VotesSubmitted(
        address indexed judge,
        address[] participants,
        uint256[] points
    );

    event WinnerUpdated(
        address indexed participant,
        uint256 oldPosition,
        uint256 newPosition
    );

    // ============ CORE FUNCTIONS ============

    /**
     * @dev Submit votes for multiple participants in one transaction
     * @param _participants Array of participant addresses
     * @param _points Array of points allocated to each participant
     */
    function submitVotes(
        address[] calldata _participants,
        uint256[] calldata _points
    )
        external;

    /**
     * @dev Get current winners in order (1st, 2nd, 3rd, etc.)
     * @return Array of winner addresses in ranking order
     */
    function getWinners()
        external
        view
        returns (address[] memory);

    /**
     * @dev Get participant's current total points
     * @param _participant Address of the participant
     * @return Total points received
     */
    function getParticipantPoints(
        address _participant
    )
        external
        view
        returns (uint256);

    /**
     * @dev Get participant's current ranking position
     * @param _participant Address of the participant
     * @return Ranking position (0 = not in top N, 1+ = position)
     */
    function getParticipantRanking(
        address _participant
    )
        external
        view
        returns (uint256);

    /**
     * @dev Check if a judge has voted
     * @param _judge Address of the judge
     * @return True if judge has voted
     */
    function hasJudgeVoted(
        address _judge
    )
        external
        view
        returns (bool);

    /**
     * @dev Get voting statistics
     * @return _totalJudges Total number of judges
     * @return _votedJudges Number of judges who have voted
     * @return _totalParticipants Number of participants with votes
     */
    function getVotingStats()
        external
        view
        returns (
            uint256 _totalJudges,
            uint256 _votedJudges,
            uint256 _totalParticipants
        );

    // ============ CONFIGURATION ============

    /**
     * @dev Initialize the voting system
     * @param _pointsPerJudge Maximum points each judge can allocate
     * @param _maxWinners Maximum number of winners to track
     * @param _judges Array of judge addresses
     */
    function initialize(
        uint256 _pointsPerJudge,
        uint256 _maxWinners,
        address[] calldata _judges
    )
        external;

    /**
     * @dev Add a judge to the voting system
     * @param _judge Address of the judge to add
     */
    function addJudge(
        address _judge
    )
        external;

    /**
     * @dev Remove a judge from the voting system
     * @param _judge Address of the judge to remove
     */
    function removeJudge(
        address _judge
    )
        external;

    // ============ WINNER MANAGEMENT ============

    /**
     * @dev Get the number of current winners
     * @return Number of winners currently tracked
     */
    function getWinnerCount()
        external
        view
        returns (uint256);

    /**
     * @dev Get winner at specific position
     * @param _position Position in rankings (1-indexed)
     * @return Address of winner at position
     */
    function getWinnerAtPosition(
        uint256 _position
    )
        external
        view
        returns (address);

    /**
     * @dev Check if participant is currently a winner
     * @param _participant Address of the participant
     * @return True if participant is in top N winners
     */
    function isWinner(
        address _participant
    )
        external
        view
        returns (bool);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.28;

/**
 * @title JudgeCouncil
 * @dev Standalone service for managing judges and their rewards
 * @notice This contract can be used by hackathons to manage judge-related functionality
 */
contract JudgeCouncil {

    // Global judge registry
    mapping(address => bool) public isGlobalJudge;
    address[] public globalJudges;

    // Judge rewards tracking
    mapping(address => uint256) public judgeRewards;
    mapping(address => bool) public hasClaimedReward;

    // Judge delegation
    mapping(address => address) public judgeDelegates;
    mapping(address => address) public delegateToJudge;

    // Governance voting system
    uint256 public votesRequired = 1; // Minimum votes required for governance actions
    mapping(bytes32 => mapping(address => bool)) public hasVoted; // proposalId => judge => hasVoted
    mapping(bytes32 => uint256) public proposalVotes; // proposalId => vote count
    mapping(bytes32 => bool) public proposalExecuted; // proposalId => executed

    // Events
    event GlobalJudgeAdded(
        address indexed judge
    );

    event GlobalJudgeRemoved(
        address indexed judge
    );

    event JudgeDelegated(
        address indexed judge,
        address indexed delegate
    );

    event JudgeRewardAdded(
        address indexed judge,
        uint256 amount
    );

    event JudgeRewardClaimed(
        address indexed judge,
        uint256 amount
    );

    event ProposalCreated(
        bytes32 indexed proposalId,
        string proposalType,
        address indexed proposer
    );

    event VoteCast(
        bytes32 indexed proposalId,
        address indexed voter,
        bool support
    );

    event ProposalExecuted(
        bytes32 indexed proposalId
    );

    event VotesRequiredChanged(
        uint256 oldVotesRequired,
        uint256 newVotesRequired
    );

    modifier onlyGlobalJudge() {
        require(
            isGlobalJudge[msg.sender],
            "Only global judges can call this function"
        );
        _;
    }

    modifier onlyFactory() {
        require(
            msg.sender == factory,
            "Only factory can call this function"
        );
        _;
    }

    address public immutable factory;

    /**
     * @dev Constructor to set the factory address
     * @param _factory Address of the factory contract
     */
    constructor(
        address _factory
    ) {
        factory = _factory;
    }

    /**
     * @dev Add the first global judge (only factory can call this)
     * @param _judge Address of the first judge to add
     */
    function _addFirstGlobalJudge(
        address _judge
    )
        internal
    {
        require(_judge != address(0), "Invalid judge address");
        require(!isGlobalJudge[_judge], "Judge already exists");
        require(globalJudges.length == 0, "First judge already added");

        isGlobalJudge[_judge] = true;
        globalJudges.push(_judge);

        emit GlobalJudgeAdded(_judge);
    }

    /**
     * @dev Delegate judge responsibilities to another address
     * @param _delegate Address to delegate to
     */
    function delegateToAgent(
        address _delegate
    )
        external
        onlyGlobalJudge
    {
        require(_delegate != address(0), "Invalid delegate address");
        require(_delegate != msg.sender, "Cannot delegate to yourself");
        require(judgeDelegates[msg.sender] == address(0), "Already has a delegate");

        judgeDelegates[msg.sender] = _delegate;
        delegateToJudge[_delegate] = msg.sender;

        emit JudgeDelegated(msg.sender, _delegate);
    }

    /**
     * @dev Revoke delegation
     */
    function revokeDelegation()
        external
        onlyGlobalJudge
    {
        require(
            judgeDelegates[msg.sender] != address(0),
            "No delegation to revoke"
        );

        address delegate = judgeDelegates[msg.sender];
        judgeDelegates[msg.sender] = address(0);
        delegateToJudge[delegate] = address(0);

        emit JudgeDelegated(
            msg.sender,
            address(0)
        );
    }

    /**
     * @dev Add reward for a judge (called by hackathons)
     * @param _judge Address of the judge
     * @param _amount Amount to add
     */
    function addJudgeReward(
        address _judge,
        uint256 _amount
    )
        external
        payable
    {
        require(_judge != address(0), "Invalid judge address");
        require(_amount > 0, "Amount must be greater than 0");
        require(msg.value >= _amount, "Insufficient payment");

        judgeRewards[_judge] += _amount;

        emit JudgeRewardAdded(
            _judge,
            _amount
        );
    }

    /**
     * @dev Claim accumulated judge rewards
     */
    function claimJudgeReward()
        external
    {
        address judge = isGlobalJudge[msg.sender]
            ? msg.sender
            : delegateToJudge[msg.sender];

        require(judgeRewards[judge] > 0, "No rewards to claim");
        require(!hasClaimedReward[judge], "Already claimed rewards");

        uint256 amount = judgeRewards[judge];
        hasClaimedReward[judge] = true;

        payable(msg.sender).transfer(
            amount
        );

        emit JudgeRewardClaimed(
            judge,
            amount
        );
    }

    /**
     * @dev Check if an address is a judge or delegate
     * @param _address Address to check
     * @return True if the address is a judge or delegate
     */
    function isJudgeOrDelegate(
        address _address
    )
        public
        view
        returns (bool)
    {
        return isGlobalJudge[_address] || delegateToJudge[_address] != address(0);
    }

    /**
     * @dev Get all global judges
     * @return Array of global judge addresses
     */
    function getGlobalJudges()
        external
        view
        returns (address[] memory)
    {
        return globalJudges;
    }

    /**
     * @dev Get judge rewards for an address
     * @param _judge Address of the judge
     * @return Amount of rewards
     */
    function getJudgeRewards(
        address _judge
    )
        external
        view
        returns (uint256)
    {
        return judgeRewards[_judge];
    }

    /**
     * @dev Get delegate for a judge
     * @param _judge Address of the judge
     * @return Address of the delegate
     */
    function getJudgeDelegate(
        address _judge
    )
        external
        view
        returns (address)
    {
        return judgeDelegates[_judge];
    }

    /**
     * @dev Get judge for a delegate
     * @param _delegate Address of the delegate
     * @return Address of the judge
     */
    function getDelegateJudge(
        address _delegate
    )
        external
        view
        returns (address)
    {
        return delegateToJudge[_delegate];
    }

    // ========== Governance Functions ==========

    /**
     * @dev Create a proposal to add a new judge
     * @param _judge Address of the judge to add
     * @return proposalId Unique identifier for the proposal
     */
    function proposeAddJudge(
        address _judge
    )
        external
        onlyGlobalJudge
        returns (bytes32)
    {
        require(_judge != address(0), "Invalid judge address");
        require(!isGlobalJudge[_judge], "Judge already exists");

        bytes32 proposalId = keccak256(abi.encodePacked("addJudge", _judge, block.timestamp));
        require(!proposalExecuted[proposalId], "Proposal already executed");

        emit ProposalCreated(
            proposalId,
            "addJudge",
            msg.sender
        );

        return proposalId;
    }

    /**
     * @dev Create a proposal to remove a judge
     * @param _judge Address of the judge to remove
     * @return proposalId Unique identifier for the proposal
     */
    function proposeRemoveJudge(
        address _judge
    )
        external
        onlyGlobalJudge
        returns (bytes32)
    {
        require(isGlobalJudge[_judge], "Judge does not exist");
        require(_judge != msg.sender, "Cannot propose to remove yourself");

        bytes32 proposalId = keccak256(abi.encodePacked("removeJudge", _judge, block.timestamp));
        require(!proposalExecuted[proposalId], "Proposal already executed");

        emit ProposalCreated(proposalId, "removeJudge", msg.sender);
        return proposalId;
    }

    /**
     * @dev Create a proposal to change the required votes
     * @param _newVotesRequired New number of votes required
     * @return proposalId Unique identifier for the proposal
     */
    function proposeChangeVotesRequired(
        uint256 _newVotesRequired
    )
        external
        onlyGlobalJudge
        returns (bytes32)
    {
        require(_newVotesRequired > 0, "Votes required must be greater than 0");
        require(_newVotesRequired <= globalJudges.length, "Votes required cannot exceed total judges");

        bytes32 proposalId = keccak256(abi.encodePacked("changeVotesRequired", _newVotesRequired, block.timestamp));
        require(!proposalExecuted[proposalId], "Proposal already executed");

        emit ProposalCreated(
            proposalId,
            "changeVotesRequired",
            msg.sender
        );

        return proposalId;
    }

    /**
     * @dev Vote on a proposal
     * @param _proposalId Unique identifier for the proposal
     * @param _support True to support the proposal, false to oppose
     */
    function vote(
        bytes32 _proposalId,
        bool _support
    )
        external
        onlyGlobalJudge
    {
        require(!hasVoted[_proposalId][msg.sender], "Already voted on this proposal");
        require(!proposalExecuted[_proposalId], "Proposal already executed");

        hasVoted[_proposalId][msg.sender] = true;

        if (_support) {
            proposalVotes[_proposalId]++;
        }

        emit VoteCast(
            _proposalId,
            msg.sender,
            _support
        );
    }

    /**
     * @dev Execute a proposal if it has enough votes
     * @param _proposalId Unique identifier for the proposal
     * @param _judge Address of the judge (for add/remove judge proposals)
     * @param _newVotesRequired New votes required (for change votes required proposals)
     */
    function executeProposal(
        bytes32 _proposalId,
        address _judge,
        uint256 _newVotesRequired
    )
        external
        onlyGlobalJudge
    {
        require(!proposalExecuted[_proposalId], "Proposal already executed");
        require(proposalVotes[_proposalId] >= votesRequired, "Insufficient votes");

        proposalExecuted[_proposalId] = true;

        // Determine proposal type and execute
        if (_judge != address(0)) {
            // Add or remove judge proposal
            if (isGlobalJudge[_judge]) {
                // Remove judge
        isGlobalJudge[_judge] = false;
        for (uint256 i = 0; i < globalJudges.length; i++) {
            if (globalJudges[i] == _judge) {
                globalJudges[i] = globalJudges[globalJudges.length - 1];
                globalJudges.pop();
                break;
            }
                }
                emit GlobalJudgeRemoved(_judge);
            } else {
                // Add judge
                isGlobalJudge[_judge] = true;
                globalJudges.push(_judge);
                emit GlobalJudgeAdded(_judge);
            }
        } else if (_newVotesRequired > 0) {
            // Change votes required
            uint256 oldVotesRequired = votesRequired;
            votesRequired = _newVotesRequired;

            emit VotesRequiredChanged(
                oldVotesRequired,
                _newVotesRequired
            );
        }

        emit ProposalExecuted(
            _proposalId
        );
    }

    /**
     * @dev Get the current vote count for a proposal
     * @param _proposalId Unique identifier for the proposal
     * @return Current vote count
     */
    function getProposalVotes(
        bytes32 _proposalId
    )
        external
        view
        returns (uint256)
    {
        return proposalVotes[_proposalId];
    }

    /**
     * @dev Check if a judge has voted on a proposal
     * @param _proposalId Unique identifier for the proposal
     * @param _judge Address of the judge
     * @return True if the judge has voted
     */
    function hasJudgeVoted(
        bytes32 _proposalId,
        address _judge
    )
        external
        view
        returns (bool)
    {
        return hasVoted[_proposalId][_judge];
    }

    /**
     * @dev Check if a proposal has been executed
     * @param _proposalId Unique identifier for the proposal
     * @return True if the proposal has been executed
     */
    function isProposalExecuted(
        bytes32 _proposalId
    )
        external
        view
        returns (bool)
    {
        return proposalExecuted[_proposalId];
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.28;

/**
 * @title JudgingSystem
 * @dev Handles all judge-related functionality for hackathons
 * @notice This contract manages judges, their delegation, scoring, and rewards
 */
contract JudgingSystem {

    // Judge management
    mapping(address => bool) public isJudge;
    address[] public judgeList;

    // Judge delegation
    mapping(address => address) public judgeDelegates;
    mapping(address => address) public delegateToJudge;

    // Judge rewards
    uint256 public judgeRewardPercentage;
    uint256 public judgeRewardPool;
    mapping(address => bool) public hasReceivedJudgeReward;


    // Events
    event JudgeAdded(address indexed judge);
    event JudgeRemoved(address indexed judge);
    event JudgeDelegated(address indexed judge, address indexed delegate);
    event JudgeRewardDistributed(address indexed judge, uint256 amount);

    modifier onlyJudge() {
        require(
            isJudge[msg.sender],
            "Only judges can call this function"
        );
        _;
    }


    /**
     * @dev Add a judge to the system
     * @param _judge Address of the judge to add
     */
    function addJudge(address _judge) public virtual {
        require(_judge != address(0), "Invalid judge address");
        require(!isJudge[_judge], "Judge already added");

        isJudge[_judge] = true;
        judgeList.push(_judge);

        emit JudgeAdded(_judge);
    }

    /**
     * @dev Remove a judge from the system
     * @param _judge Address of the judge to remove
     */
    function removeJudge(address _judge) public {
        require(isJudge[_judge], "Old judge not found");

        isJudge[_judge] = false;

        // Remove from array
        for (uint256 i = 0; i < judgeList.length; i++) {
            if (judgeList[i] == _judge) {
                judgeList[i] = judgeList[judgeList.length - 1];
                judgeList.pop();
                break;
            }
        }

        emit JudgeRemoved(_judge);
    }

    /**
     * @dev Delegate judge responsibilities to another address
     * @param _delegate Address to delegate to
     */
    function delegateToAgent(address _delegate) external {
        require(isJudge[msg.sender], "Only judges can delegate");
        require(_delegate != address(0), "Invalid delegate address");
        require(_delegate != msg.sender, "Cannot delegate to yourself");
        require(judgeDelegates[msg.sender] == address(0), "Already has a delegate");

        judgeDelegates[msg.sender] = _delegate;
        delegateToJudge[_delegate] = msg.sender;

        emit JudgeDelegated(msg.sender, _delegate);
    }

    /**
     * @dev Revoke delegation
     */
    function revokeDelegation() external {
        require(isJudge[msg.sender], "Only judges can revoke delegation");
        require(judgeDelegates[msg.sender] != address(0), "No delegation to revoke");

        address delegate = judgeDelegates[msg.sender];
        judgeDelegates[msg.sender] = address(0);
        delegateToJudge[delegate] = address(0);

        emit JudgeDelegated(msg.sender, address(0));
    }

    /**
     * @dev Add funds to judge reward pool
     */
    function addJudgeRewards() public payable {
        require(msg.value > 0, "Must send ETH");
        judgeRewardPool += msg.value;
    }

    /**
     * @dev Distribute judge rewards
     */
    function distributeJudgeRewards() external {
        require(judgeRewardPool > 0, "No judge rewards available");
        require(judgeList.length > 0, "No judges to reward");

        uint256 rewardPerJudge = judgeRewardPool / judgeList.length;
        require(rewardPerJudge > 0, "Insufficient reward per judge");

        for (uint256 i = 0; i < judgeList.length; i++) {
            address judge = judgeList[i];
            if (!hasReceivedJudgeReward[judge]) {
                hasReceivedJudgeReward[judge] = true;
                payable(judge).transfer(rewardPerJudge);
                emit JudgeRewardDistributed(judge, rewardPerJudge);
            }
        }

        judgeRewardPool = 0;
    }

    /**
     * @dev Check if an address is a judge or delegate
     * @param _address Address to check
     * @return True if the address is a judge or delegate
     */
    function isJudgeOrDelegate(address _address) public view returns (bool) {
        return isJudge[_address] || delegateToJudge[_address] != address(0);
    }

    /**
     * @dev Get all judges
     * @return Array of judge addresses
     */
    function getJudges() external view returns (address[] memory) {
        return judgeList;
    }


    /**
     * @dev Get judge reward pool amount
     * @return Amount in the reward pool
     */
    function getJudgeRewardPool() external view returns (uint256) {
        return judgeRewardPool;
    }

    /**
     * @dev Get reward per judge
     * @return Amount each judge would receive
     */
    function getRewardPerJudge() external view returns (uint256) {
        if (judgeList.length == 0) return 0;
        return judgeRewardPool / judgeList.length;
    }

    /**
     * @dev Get judge reward percentage
     * @return Percentage of prize pool for judges
     */
    function getJudgeRewardPercentage() public view returns (uint256) {
        return judgeRewardPercentage;
    }

    /**
     * @dev Get delegate for a judge
     * @param _judge Address of the judge
     * @return Address of the delegate
     */
    function getJudgeDelegate(address _judge) external view returns (address) {
        return judgeDelegates[_judge];
    }

    /**
     * @dev Get judge for a delegate
     * @param _delegate Address of the delegate
     * @return Address of the judge
     */
    function getDelegateJudge(address _delegate) external view returns (address) {
        return delegateToJudge[_delegate];
    }


    /**
     * @dev Set the judge reward pool (called during hackathon initialization)
     * @param _rewardPool Amount of ETH available for judge rewards
     */
    function _setJudgeRewardPool(
        uint256 _rewardPool
    )
        internal
    {
        judgeRewardPool = _rewardPool;
    }
}

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

import "./IVotingSystem.sol";
import "./VotingTypes.sol";

/**
 * @title OpenVoting
 * @dev Transparent voting system with batch voting support
 * @notice Judges can vote for multiple participants in one transaction
 */
contract OpenVoting is IVotingSystem {

    // ============ STATE VARIABLES ============

    // Voting configuration
    uint256 public pointsPerJudge;
    uint256 public maxWinners;
    uint256 public votingDeadline;

    // Judge management
    mapping(address => bool) public isJudge;
    mapping(address => bool) public hasVoted;
    address[] public judges;
    uint256 public totalJudges;

    // Participant scoring
    mapping(address => uint256) public totalPoints;
    mapping(address => mapping(address => uint256)) public judgeVotes;

    // Dynamic winner tracking - updated on each vote
    mapping(address => uint256) public winnerPosition; // 0 = not a winner, 1+ = position (1-indexed)
    address[] public winners; // Array of winners in order (1st, 2nd, 3rd, etc.)

    // ============ EVENTS ============

    event JudgeAdded(address indexed judge);
    event JudgeRemoved(address indexed judge);

    // ============ MODIFIERS ============

    modifier onlyJudge() {
        require(isJudge[msg.sender], "Only judges can perform this action");
        _;
    }

    modifier onlyDuringVoting() {
        require(block.timestamp <= votingDeadline, "Voting deadline has passed");
        _;
    }

    // ============ INITIALIZATION ============

    /**
     * @dev Initialize the voting system
     * @param _pointsPerJudge Maximum points each judge can allocate
     * @param _maxWinners Maximum number of winners to track
     * @param _judges Array of judge addresses
     */
    function initialize(
        uint256 _pointsPerJudge,
        uint256 _maxWinners,
        address[] calldata _judges
    ) external override {
        require(pointsPerJudge == 0, "Already initialized");

        pointsPerJudge = _pointsPerJudge;
        maxWinners = _maxWinners;

        // Add judges
        for (uint256 i = 0; i < _judges.length; i++) {
            _addJudge(_judges[i]);
        }
    }

    // ============ CORE VOTING FUNCTIONS ============

    /**
     * @dev Submit votes for multiple participants in one transaction
     * @param _participants Array of participant addresses
     * @param _points Array of points allocated to each participant
     */
    function submitVotes(
        address[] calldata _participants,
        uint256[] calldata _points
    ) external override onlyJudge onlyDuringVoting {
        require(_participants.length == _points.length, "Arrays length mismatch");
        require(_participants.length > 0, "Must vote for at least one participant");
        require(!hasVoted[msg.sender], "Judge has already voted");

        uint256 totalPointsUsed = 0;

        // Process each vote
        for (uint256 i = 0; i < _participants.length; i++) {
            require(_points[i] > 0, "Points must be greater than 0");
            totalPointsUsed += _points[i];

            // Update participant's total points
            uint256 oldPoints = totalPoints[_participants[i]];
            totalPoints[_participants[i]] += _points[i];

            // Store judge's vote
            judgeVotes[msg.sender][_participants[i]] = _points[i];

            // Update winner list (O(1) operation per participant)
            _updateWinnerList(_participants[i], oldPoints, totalPoints[_participants[i]]);
        }

        require(totalPointsUsed <= pointsPerJudge, "Exceeds points per judge limit");

        hasVoted[msg.sender] = true;

        emit VotesSubmitted(msg.sender, _participants, _points);
    }

    // ============ JUDGE MANAGEMENT ============

    /**
     * @dev Add a judge to the voting system
     * @param _judge Address of the judge to add
     */
    function addJudge(address _judge) external override {
        require(_judge != address(0), "Invalid judge address");
        require(!isJudge[_judge], "Judge already exists");

        _addJudge(_judge);
    }

    /**
     * @dev Remove a judge from the voting system
     * @param _judge Address of the judge to remove
     */
    function removeJudge(address _judge) external override {
        require(isJudge[_judge], "Judge does not exist");
        require(!hasVoted[_judge], "Cannot remove judge who has voted");

        _removeJudge(_judge);
    }

    /**
     * @dev Internal function to add a judge
     */
    function _addJudge(address _judge) internal {
        isJudge[_judge] = true;
        judges.push(_judge);
        totalJudges++;

        emit JudgeAdded(_judge);
    }

    /**
     * @dev Internal function to remove a judge
     */
    function _removeJudge(address _judge) internal {
        isJudge[_judge] = false;

        // Remove from judges array
        for (uint256 i = 0; i < judges.length; i++) {
            if (judges[i] == _judge) {
                judges[i] = judges[judges.length - 1];
                judges.pop();
                break;
            }
        }

        totalJudges--;

        emit JudgeRemoved(_judge);
    }

    // ============ WINNER TRACKING (O(1) Operations) ============

    /**
     * @dev Update winner list dynamically based on participant's new points
     * @param _participant Address of the participant whose points changed
     * @param _oldPoints Previous total points
     * @param _newPoints New total points
     */
    function _updateWinnerList(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    ) internal {
        uint256 currentPosition = winnerPosition[_participant];

        // If participant was already a winner
        if (currentPosition > 0) {
            _adjustWinnerPosition(_participant, _oldPoints, _newPoints);
            return;
        }

        // Check if participant should be added to winners
        if (_newPoints > 0 && (winners.length < maxWinners || _newPoints > totalPoints[_getLowestWinner()])) {
            _addNewWinner(_participant);
        }
    }

    /**
     * @dev Adjust position of existing winner based on new points
     */
    function _adjustWinnerPosition(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    ) internal {
        uint256 currentPosition = winnerPosition[_participant];

        if (_newPoints > _oldPoints) {
            _moveWinnerUp(_participant, currentPosition);
        } else if (_newPoints < _oldPoints) {
            _moveWinnerDown(_participant, currentPosition);
        }
    }

    /**
     * @dev Move winner up in rankings
     */
    function _moveWinnerUp(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        // Find correct position by comparing with winners above
        for (uint256 i = currentPosition - 1; i > 0; i--) {
            if (totalPoints[_participant] > totalPoints[winners[i - 1]]) {
                newPosition = i;
            } else {
                break;
            }
        }

        if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    /**
     * @dev Move winner down in rankings or remove if no longer in top
     */
    function _moveWinnerDown(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        // Find correct position by comparing with winners below
        for (uint256 i = currentPosition; i < winners.length; i++) {
            if (totalPoints[_participant] < totalPoints[winners[i]]) {
                newPosition = i + 1;
            } else {
                break;
            }
        }

        // If moved beyond max winners, remove from winners
        if (newPosition > maxWinners) {
            _removeWinner(_participant, currentPosition);
        } else if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    /**
     * @dev Add new winner to the list
     */
    function _addNewWinner(address _participant) internal {
        if (winners.length < maxWinners) {
            // Add to end if we have space
            winners.push(_participant);
            winnerPosition[_participant] = winners.length;
            _moveWinnerUp(_participant, winners.length);
        } else {
            // Replace the lowest winner
            address lowestWinner = _getLowestWinner();
            uint256 lowestPosition = winnerPosition[lowestWinner];
            _removeWinner(lowestWinner, lowestPosition);
            _addNewWinner(_participant);
        }
    }

    /**
     * @dev Remove winner from the list
     */
    function _removeWinner(address _participant, uint256 position) internal {
        // Move last winner to this position
        if (position < winners.length) {
            address lastWinner = winners[winners.length - 1];
            winners[position - 1] = lastWinner;
            winnerPosition[lastWinner] = position;
            winners.pop();
        }

        winnerPosition[_participant] = 0;

        emit WinnerUpdated(_participant, position, 0);
    }

    /**
     * @dev Swap two winners in the rankings
     */
    function _swapWinners(address _participant, uint256 fromPosition, uint256 toPosition) internal {
        if (fromPosition != toPosition) {
            // Update positions
            winnerPosition[_participant] = toPosition;
            winnerPosition[winners[toPosition - 1]] = fromPosition;

            // Swap in array
            address temp = winners[fromPosition - 1];
            winners[fromPosition - 1] = winners[toPosition - 1];
            winners[toPosition - 1] = temp;

            emit WinnerUpdated(_participant, fromPosition, toPosition);
        }
    }

    /**
     * @dev Get the lowest winner (last in winners array)
     */
    function _getLowestWinner() internal view returns (address) {
        if (winners.length == 0) return address(0);
        return winners[winners.length - 1];
    }

    // ============ VIEW FUNCTIONS ============

    /**
     * @dev Get current winners in order
     */
    function getWinners() external view override returns (address[] memory) {
        return winners;
    }

    /**
     * @dev Get participant's current total points
     */
    function getParticipantPoints(address _participant) external view override returns (uint256) {
        return totalPoints[_participant];
    }

    /**
     * @dev Get participant's current ranking position
     */
    function getParticipantRanking(address _participant) external view override returns (uint256) {
        return winnerPosition[_participant];
    }

    /**
     * @dev Check if a judge has voted
     */
    function hasJudgeVoted(address _judge) external view override returns (bool) {
        return hasVoted[_judge];
    }

    /**
     * @dev Get voting statistics
     */
    function getVotingStats() external view override returns (
        uint256 _totalJudges,
        uint256 _votedJudges,
        uint256 _totalParticipants
    ) {
        _totalJudges = totalJudges;

        uint256 votedCount = 0;
        uint256 participantsCount = 0;

        for (uint256 i = 0; i < judges.length; i++) {
            if (hasVoted[judges[i]]) {
                votedCount++;
            }
        }

        _votedJudges = votedCount;

        // Count participants with points
        for (uint256 i = 0; i < judges.length; i++) {
            for (uint256 j = 0; j < judges.length; j++) {
                if (judgeVotes[judges[i]][judges[j]] > 0) {
                    participantsCount++;
                }
            }
        }

        _totalParticipants = participantsCount;
    }

    /**
     * @dev Get the number of current winners
     */
    function getWinnerCount() external view override returns (uint256) {
        return winners.length;
    }

    /**
     * @dev Get winner at specific position
     */
    function getWinnerAtPosition(uint256 _position) external view override returns (address) {
        require(_position > 0 && _position <= winners.length, "Invalid position");
        return winners[_position - 1];
    }

    /**
     * @dev Check if participant is currently a winner
     */
    function isWinner(address _participant) external view override returns (bool) {
        return winnerPosition[_participant] > 0;
    }

    // ============ ADMIN FUNCTIONS ============

    /**
     * @dev Set voting deadline (called by hackathon contract)
     */
    function setVotingDeadline(uint256 _deadline) external {
        require(votingDeadline == 0, "Deadline already set");
        votingDeadline = _deadline;
    }
}

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

import "./IVotingSystem.sol";

/**
 * @title QuadraticVoting
 * @dev Library for quadratic voting calculations and validation
 * @notice Implements quadratic voting where cost = votes²
 */
library QuadraticVoting {

    // ============ ERRORS ============

    error InvalidVoteAllocation();
    error ExceedsCreditLimit();
    error InvalidVoteValue();

    // ============ FUNCTIONS ============

    /**
     * @dev Calculate the cost of a vote allocation
     * @param _votes Array of votes for each participant
     * @return Total cost in credits
     */
    function calculateCost(uint256[] memory _votes) internal pure returns (uint256) {
        uint256 totalCost = 0;

        for (uint256 i = 0; i < _votes.length; i++) {
            totalCost += _votes[i] * _votes[i];
        }

        return totalCost;
    }

    /**
     * @dev Validate quadratic voting allocation
     * @param _votes Array of votes for each participant
     * @param _totalCredits Total credits available to judge
     * @return True if allocation is valid
     */
    function validateAllocation(
        uint256[] memory _votes,
        uint256 _totalCredits
    ) internal pure returns (bool) {
        uint256 totalCost = calculateCost(_votes);
        return totalCost <= _totalCredits;
    }

    /**
     * @dev Calculate square root using Babylonian method
     * @param _x Number to calculate square root of
     * @return Square root of _x
     */
    function sqrt(uint256 _x) internal pure returns (uint256) {
        if (_x == 0) return 0;

        uint256 z = (_x + 1) / 2;
        uint256 y = _x;

        while (z < y) {
            y = z;
            z = (_x / z + z) / 2;
        }

        return y;
    }

    /**
     * @dev Convert credits to votes (sqrt function)
     * @param _credits Number of credits
     * @return Number of votes
     */
    function creditsToVotes(uint256 _credits) internal pure returns (uint256) {
        return sqrt(_credits);
    }

    /**
     * @dev Convert votes to credits (squared function)
     * @param _votes Number of votes
     * @return Number of credits required
     */
    function votesToCredits(uint256 _votes) internal pure returns (uint256) {
        return _votes * _votes;
    }

    /**
     * @dev Validate that all votes are non-negative
     * @param _votes Array of votes
     * @return True if all votes are valid
     */
    function validateVotes(uint256[] memory _votes) internal pure returns (bool) {
        for (uint256 i = 0; i < _votes.length; i++) {
            if (_votes[i] < 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @dev Calculate optimal vote distribution for given credits
     * @param _participants Number of participants
     * @param _totalCredits Total credits available
     * @param _preferences Array of preference weights (0-100)
     * @return Array of optimal votes for each participant
     */
    function calculateOptimalDistribution(
        uint256 _participants,
        uint256 _totalCredits,
        uint256[] memory _preferences
    ) internal pure returns (uint256[] memory) {
        require(_participants == _preferences.length, "Preferences length mismatch");
        require(_participants > 0, "Must have at least one participant");

        uint256[] memory votes = new uint256[](_participants);

        // Calculate total preference weight
        uint256 totalWeight = 0;
        for (uint256 i = 0; i < _participants; i++) {
            totalWeight += _preferences[i];
        }

        if (totalWeight == 0) {
            // If no preferences, distribute equally
            uint256 equalVotes = sqrt(_totalCredits / _participants);
            for (uint256 i = 0; i < _participants; i++) {
                votes[i] = equalVotes;
            }
        } else {
            // Distribute based on preferences
            for (uint256 i = 0; i < _participants; i++) {
                uint256 proportionalCredits = (_totalCredits * _preferences[i]) / totalWeight;
                votes[i] = sqrt(proportionalCredits);
            }
        }

        return votes;
    }
}

/**
 * @title QVWrapper
 * @dev Wrapper contract that adds quadratic voting to any voting system
 * @notice Converts quadratic votes to linear points for the base voting system
 */
contract QVWrapper is IVotingSystem {

    // ============ STATE VARIABLES ============

    IVotingSystem public baseVotingSystem;
    uint256 public creditsPerJudge;
    bool public initialized;

    // ============ EVENTS ============

    event QVVotesSubmitted(
        address indexed judge,
        address[] participants,
        uint256[] votes,
        uint256 totalCreditsUsed
    );

    // ============ MODIFIERS ============

    modifier onlyInitialized() {
        require(initialized, "Contract not initialized");
        _;
    }

    // ============ INITIALIZATION ============

    /**
     * @dev Initialize the QVWrapper (for clone pattern)
     * @param _baseVotingSystem Address of the base voting system
     * @param _creditsPerJudge Number of credits per judge
     */
    function initialize(address _baseVotingSystem, uint256 _creditsPerJudge) external {
        require(!initialized, "Already initialized");
        require(_baseVotingSystem != address(0), "Invalid base voting system");
        require(_creditsPerJudge > 0, "Credits per judge must be greater than 0");

        baseVotingSystem = IVotingSystem(_baseVotingSystem);
        creditsPerJudge = _creditsPerJudge;
        initialized = true;
    }

    // ============ CORE FUNCTIONS ============

    /**
     * @dev Submit quadratic votes for multiple participants
     * @param _participants Array of participant addresses
     * @param _votes Array of votes (not credits) for each participant
     */
    function submitVotes(
        address[] calldata _participants,
        uint256[] calldata _votes
    ) external override onlyInitialized {
        require(_participants.length == _votes.length, "Arrays length mismatch");
        require(_participants.length > 0, "Must vote for at least one participant");

        // Validate quadratic voting allocation
        require(
            QuadraticVoting.validateAllocation(_votes, creditsPerJudge),
            "Invalid quadratic vote allocation"
        );

        // Calculate total credits used
        uint256 totalCreditsUsed = QuadraticVoting.calculateCost(_votes);

        // Convert votes to points for base system
        // In QV, votes are the actual points (not credits)
        uint256[] memory points = new uint256[](_votes.length);
        for (uint256 i = 0; i < _votes.length; i++) {
            points[i] = _votes[i];
        }

        // Submit to base voting system
        baseVotingSystem.submitVotes(_participants, points);

        emit QVVotesSubmitted(msg.sender, _participants, _votes, totalCreditsUsed);
    }

    // ============ DELEGATION TO BASE SYSTEM ============

    function getWinners() external view override returns (address[] memory) {
        return baseVotingSystem.getWinners();
    }

    function getParticipantPoints(address _participant) external view override returns (uint256) {
        return baseVotingSystem.getParticipantPoints(_participant);
    }

    function getParticipantRanking(address _participant) external view override returns (uint256) {
        return baseVotingSystem.getParticipantRanking(_participant);
    }

    function hasJudgeVoted(address _judge) external view override returns (bool) {
        return baseVotingSystem.hasJudgeVoted(_judge);
    }

    function getVotingStats() external view override returns (
        uint256 _totalJudges,
        uint256 _votedJudges,
        uint256 _totalParticipants
    ) {
        return baseVotingSystem.getVotingStats();
    }

    function initialize(
        uint256 _pointsPerJudge,
        uint256 _maxWinners,
        address[] calldata _judges
    ) external override {
        // QV wrapper doesn't need initialization
        // Base system should be initialized separately
    }

    function addJudge(address _judge) external override onlyInitialized {
        // QV wrapper doesn't manage judges directly
        // Judges are managed by the base voting system
        // This function is here for interface compliance
    }

    function removeJudge(address _judge) external override onlyInitialized {
        baseVotingSystem.removeJudge(_judge);
    }

    function getWinnerCount() external view override returns (uint256) {
        return baseVotingSystem.getWinnerCount();
    }

    function getWinnerAtPosition(uint256 _position) external view override returns (address) {
        return baseVotingSystem.getWinnerAtPosition(_position);
    }

    function isWinner(address _participant) external view override returns (bool) {
        return baseVotingSystem.isWinner(_participant);
    }

    // ============ QV-SPECIFIC FUNCTIONS ============

    /**
     * @dev Calculate optimal vote distribution for a judge
     * @param _participants Array of participant addresses
     * @param _preferences Array of preference weights (0-100)
     * @return Array of optimal votes for each participant
     */
    function calculateOptimalVotes(
        address[] calldata _participants,
        uint256[] calldata _preferences
    ) external view returns (uint256[] memory) {
        require(_participants.length == _preferences.length, "Arrays length mismatch");

        return QuadraticVoting.calculateOptimalDistribution(
            _participants.length,
            creditsPerJudge,
            _preferences
        );
    }

    /**
     * @dev Calculate cost of a vote allocation
     * @param _votes Array of votes
     * @return Total cost in credits
     */
    function calculateVoteCost(uint256[] calldata _votes) external pure returns (uint256) {
        return QuadraticVoting.calculateCost(_votes);
    }

    /**
     * @dev Validate a vote allocation
     * @param _votes Array of votes
     * @return True if allocation is valid
     */
    function validateVoteAllocation(uint256[] calldata _votes) external view returns (bool) {
        return QuadraticVoting.validateAllocation(_votes, creditsPerJudge);
    }
}

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

import "./IVotingSystem.sol";
import "./VotingTypes.sol";

/**
 * @title RevealCommitVoting
 * @dev Commit-reveal voting system with batch voting support
 * @notice Judges commit votes first, then reveal them in a separate phase
 */
contract RevealCommitVoting is IVotingSystem {

    // ============ STATE VARIABLES ============

    // Voting configuration
    uint256 public pointsPerJudge;
    uint256 public maxWinners;
    uint256 public votingDeadline;

    // Judge management
    mapping(address => bool) public isJudge;
    mapping(address => bool) public hasCommitted;
    mapping(address => bool) public hasRevealed;
    address[] public judges;
    uint256 public totalJudges;

    // Commit phase
    mapping(address => bytes32) public commitments;

    // Reveal phase
    mapping(address => address[]) public revealedParticipants;
    mapping(address => uint256[]) public revealedPoints;
    mapping(address => uint256) public revealedNonces;

    // Participant scoring (only after reveal)
    mapping(address => uint256) public totalPoints;
    mapping(address => mapping(address => uint256)) public judgeVotes;

    // Dynamic winner tracking
    mapping(address => uint256) public winnerPosition;
    address[] public winners;

    // ============ EVENTS ============

    event VotesCommitted(
        address indexed judge,
        bytes32 commitment
    );

    event VotesRevealed(
        address indexed judge,
        address[] participants,
        uint256[] points
    );

    event JudgeAdded(address indexed judge);
    event JudgeRemoved(address indexed judge);

    // ============ MODIFIERS ============

    modifier onlyJudge() {
        require(isJudge[msg.sender], "Only judges can perform this action");
        _;
    }

    modifier onlyDuringCommit() {
        require(block.timestamp <= votingDeadline, "Voting deadline has passed");
        _;
    }

    modifier onlyDuringReveal() {
        require(block.timestamp > votingDeadline, "Still in commit phase");
        _;
    }

    // ============ INITIALIZATION ============

    /**
     * @dev Initialize the voting system
     */
    function initialize(
        uint256 _pointsPerJudge,
        uint256 _maxWinners,
        address[] calldata _judges
    ) external override {
        require(pointsPerJudge == 0, "Already initialized");

        pointsPerJudge = _pointsPerJudge;
        maxWinners = _maxWinners;

        // Add judges
        for (uint256 i = 0; i < _judges.length; i++) {
            _addJudge(_judges[i]);
        }
    }

    /**
     * @dev Set voting deadline (called by Hackathon contract)
     */
    function setVotingDeadline(uint256 _deadline) external {
        require(votingDeadline == 0, "Deadline already set");
        votingDeadline = _deadline;
    }

    // ============ COMMIT PHASE ============

    /**
     * @dev Commit votes for multiple participants
     * @param _participants Array of participant addresses
     * @param _points Array of points allocated to each participant
     * @param _nonce Random nonce for commitment
     */
    function commitBatchVotes(
        address[] calldata _participants,
        uint256[] calldata _points,
        uint256 _nonce
    ) external onlyJudge onlyDuringCommit {
        require(_participants.length == _points.length, "Arrays length mismatch");
        require(_participants.length > 0, "Must vote for at least one participant");
        require(!hasCommitted[msg.sender], "Judge has already committed");

        // Validate points
        uint256 totalPointsUsed = 0;
        for (uint256 i = 0; i < _participants.length; i++) {
            require(_points[i] > 0, "Points must be greater than 0");
            totalPointsUsed += _points[i];
        }
        require(totalPointsUsed <= pointsPerJudge, "Exceeds points per judge limit");

        // Create commitment
        bytes32 commitment = keccak256(abi.encodePacked(
            msg.sender,
            _participants,
            _points,
            _nonce
        ));

        commitments[msg.sender] = commitment;
        hasCommitted[msg.sender] = true;

        emit VotesCommitted(msg.sender, commitment);
    }

    // ============ REVEAL PHASE ============

    /**
     * @dev Reveal votes for multiple participants
     * @param _participants Array of participant addresses
     * @param _points Array of points allocated to each participant
     * @param _nonce Nonce used in commitment
     */
    function revealBatchVotes(
        address[] calldata _participants,
        uint256[] calldata _points,
        uint256 _nonce
    ) external onlyJudge onlyDuringReveal {
        require(hasCommitted[msg.sender], "Must commit votes first");
        require(!hasRevealed[msg.sender], "Judge has already revealed");
        require(_participants.length == _points.length, "Arrays length mismatch");

        // Verify commitment
        bytes32 expectedCommitment = keccak256(abi.encodePacked(
            msg.sender,
            _participants,
            _points,
            _nonce
        ));
        require(commitments[msg.sender] == expectedCommitment, "Commitment mismatch");

        // Process revealed votes
        uint256 totalPointsUsed = 0;

        for (uint256 i = 0; i < _participants.length; i++) {
            require(_points[i] > 0, "Points must be greater than 0");
            totalPointsUsed += _points[i];

            // Update participant's total points
            uint256 oldPoints = totalPoints[_participants[i]];
            totalPoints[_participants[i]] += _points[i];

            // Store judge's vote
            judgeVotes[msg.sender][_participants[i]] = _points[i];

            // Update winner list
            _updateWinnerList(_participants[i], oldPoints, totalPoints[_participants[i]]);
        }

        require(totalPointsUsed <= pointsPerJudge, "Exceeds points per judge limit");

        // Store revealed data
        revealedParticipants[msg.sender] = _participants;
        revealedPoints[msg.sender] = _points;
        revealedNonces[msg.sender] = _nonce;
        hasRevealed[msg.sender] = true;

        emit VotesRevealed(msg.sender, _participants, _points);
    }

    /**
     * @dev Reveal votes for multiple participants (interface requirement)
     * @param _participants Array of participant addresses
     * @param _points Array of points allocated to each participant
     */
    function submitVotes(
        address[] calldata _participants,
        uint256[] calldata _points
    ) external override onlyJudge onlyDuringReveal {
        // This function is used for reveal phase
        require(hasCommitted[msg.sender], "Must commit votes first");
        require(!hasRevealed[msg.sender], "Judge has already revealed");
        require(_participants.length == _points.length, "Arrays length mismatch");

        // Verify commitment
        bytes32 expectedCommitment = keccak256(abi.encodePacked(
            msg.sender,
            _participants,
            _points,
            revealedNonces[msg.sender]
        ));
        require(commitments[msg.sender] == expectedCommitment, "Commitment mismatch");

        // Process revealed votes
        uint256 totalPointsUsed = 0;

        for (uint256 i = 0; i < _participants.length; i++) {
            require(_points[i] > 0, "Points must be greater than 0");
            totalPointsUsed += _points[i];

            // Update participant's total points
            uint256 oldPoints = totalPoints[_participants[i]];
            totalPoints[_participants[i]] += _points[i];

            // Store judge's vote
            judgeVotes[msg.sender][_participants[i]] = _points[i];

            // Update winner list
            _updateWinnerList(_participants[i], oldPoints, totalPoints[_participants[i]]);
        }

        require(totalPointsUsed <= pointsPerJudge, "Exceeds points per judge limit");

        // Store revealed data
        revealedParticipants[msg.sender] = _participants;
        revealedPoints[msg.sender] = _points;
        hasRevealed[msg.sender] = true;

        emit VotesRevealed(msg.sender, _participants, _points);
    }

    /**
     * @dev Set nonce for reveal (must be called before submitVotes)
     * @param _nonce Nonce used in original commitment
     */
    function setRevealNonce(uint256 _nonce) external onlyJudge {
        revealedNonces[msg.sender] = _nonce;
    }

    // ============ JUDGE MANAGEMENT ============

    function addJudge(address _judge) external override {
        require(_judge != address(0), "Invalid judge address");
        require(!isJudge[_judge], "Judge already exists");

        _addJudge(_judge);
    }

    function removeJudge(address _judge) external override {
        require(isJudge[_judge], "Judge does not exist");
        require(!hasCommitted[_judge], "Cannot remove judge who has committed");

        _removeJudge(_judge);
    }

    function _addJudge(address _judge) internal {
        isJudge[_judge] = true;
        judges.push(_judge);
        totalJudges++;

        emit JudgeAdded(_judge);
    }

    function _removeJudge(address _judge) internal {
        isJudge[_judge] = false;

        for (uint256 i = 0; i < judges.length; i++) {
            if (judges[i] == _judge) {
                judges[i] = judges[judges.length - 1];
                judges.pop();
                break;
            }
        }

        totalJudges--;

        emit JudgeRemoved(_judge);
    }

    // ============ WINNER TRACKING (Same as OpenVoting) ============

    function _updateWinnerList(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    ) internal {
        uint256 currentPosition = winnerPosition[_participant];

        if (currentPosition > 0) {
            _adjustWinnerPosition(_participant, _oldPoints, _newPoints);
            return;
        }

        if (_newPoints > 0 && (winners.length < maxWinners || _newPoints > totalPoints[_getLowestWinner()])) {
            _addNewWinner(_participant);
        }
    }

    function _adjustWinnerPosition(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    ) internal {
        uint256 currentPosition = winnerPosition[_participant];

        if (_newPoints > _oldPoints) {
            _moveWinnerUp(_participant, currentPosition);
        } else if (_newPoints < _oldPoints) {
            _moveWinnerDown(_participant, currentPosition);
        }
    }

    function _moveWinnerUp(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        for (uint256 i = currentPosition - 1; i > 0; i--) {
            if (totalPoints[_participant] > totalPoints[winners[i - 1]]) {
                newPosition = i;
            } else {
                break;
            }
        }

        if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    function _moveWinnerDown(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        for (uint256 i = currentPosition; i < winners.length; i++) {
            if (totalPoints[_participant] < totalPoints[winners[i]]) {
                newPosition = i + 1;
            } else {
                break;
            }
        }

        if (newPosition > maxWinners) {
            _removeWinner(_participant, currentPosition);
        } else if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    function _addNewWinner(address _participant) internal {
        if (winners.length < maxWinners) {
            winners.push(_participant);
            winnerPosition[_participant] = winners.length;
            _moveWinnerUp(_participant, winners.length);
        } else {
            address lowestWinner = _getLowestWinner();
            uint256 lowestPosition = winnerPosition[lowestWinner];
            _removeWinner(lowestWinner, lowestPosition);
            _addNewWinner(_participant);
        }
    }

    function _removeWinner(address _participant, uint256 position) internal {
        if (position < winners.length) {
            address lastWinner = winners[winners.length - 1];
            winners[position - 1] = lastWinner;
            winnerPosition[lastWinner] = position;
            winners.pop();
        }

        winnerPosition[_participant] = 0;

        emit WinnerUpdated(_participant, position, 0);
    }

    function _swapWinners(address _participant, uint256 fromPosition, uint256 toPosition) internal {
        if (fromPosition != toPosition) {
            winnerPosition[_participant] = toPosition;
            winnerPosition[winners[toPosition - 1]] = fromPosition;

            address temp = winners[fromPosition - 1];
            winners[fromPosition - 1] = winners[toPosition - 1];
            winners[toPosition - 1] = temp;

            emit WinnerUpdated(_participant, fromPosition, toPosition);
        }
    }

    function _getLowestWinner() internal view returns (address) {
        if (winners.length == 0) return address(0);
        return winners[winners.length - 1];
    }

    // ============ VIEW FUNCTIONS ============

    function getWinners() external view override returns (address[] memory) {
        return winners;
    }

    function getParticipantPoints(address _participant) external view override returns (uint256) {
        return totalPoints[_participant];
    }

    function getParticipantRanking(address _participant) external view override returns (uint256) {
        return winnerPosition[_participant];
    }

    function hasJudgeVoted(address _judge) external view override returns (bool) {
        return hasRevealed[_judge];
    }

    function getVotingStats() external view override returns (
        uint256 _totalJudges,
        uint256 _votedJudges,
        uint256 _totalParticipants
    ) {
        _totalJudges = totalJudges;

        uint256 votedCount = 0;
        for (uint256 i = 0; i < judges.length; i++) {
            if (hasRevealed[judges[i]]) {
                votedCount++;
            }
        }

        _votedJudges = votedCount;
        _totalParticipants = 0; // Calculate based on revealed votes
    }

    function getWinnerCount() external view override returns (uint256) {
        return winners.length;
    }

    function getWinnerAtPosition(uint256 _position) external view override returns (address) {
        require(_position > 0 && _position <= winners.length, "Invalid position");
        return winners[_position - 1];
    }

    function isWinner(address _participant) external view override returns (bool) {
        return winnerPosition[_participant] > 0;
    }

    // ============ PHASE MANAGEMENT ============


    // ============ COMMITMENT HELPERS ============

    /**
     * @dev Generate commitment hash for batch votes
     * @param _judge Judge address
     * @param _participants Array of participant addresses
     * @param _points Array of points
     * @param _nonce Random nonce
     * @return Commitment hash
     */
    function generateCommitment(
        address _judge,
        address[] calldata _participants,
        uint256[] calldata _points,
        uint256 _nonce
    ) external pure returns (bytes32) {
        return keccak256(abi.encodePacked(_judge, _participants, _points, _nonce));
    }

    /**
     * @dev Check if judge has committed votes
     * @param _judge Judge address
     * @return True if judge has committed
     */
    function hasJudgeCommitted(address _judge) external view returns (bool) {
        return hasCommitted[_judge];
    }

    /**
     * @dev Get judge's commitment
     * @param _judge Judge address
     * @return Commitment hash
     */
    function getJudgeCommitment(address _judge) external view returns (bytes32) {
        return commitments[_judge];
    }
}

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

/**
 * @title StakeSystem
 * @dev Handles stake management for hackathon participants
 * @notice This contract manages the stake system where participants must deposit funds
 */
contract StakeSystem {

    uint256 public stakeAmount;
    uint256 public totalStakes;

    mapping(address => uint256) public participantStakes;

    event StakeDeposited(
        address indexed participant,
        uint256 amount
    );

    event StakeReturned(
        address indexed participant,
        uint256 amount
    );


    /**
     * @notice Deposit stake for a participant
     * @dev Internal function to handle stake deposits and emit events
     * @param _participant Address of the participant depositing the stake
     */
    function _depositStake(
        address _participant
    )
        internal
    {
        require(
            msg.value == stakeAmount,
            "Must deposit exact stake amount"
        );

        participantStakes[_participant] = msg.value;
        totalStakes += msg.value;

        emit StakeDeposited(
            _participant,
            msg.value
        );
    }

    /**
     * @notice Return stake to a participant
     * @dev Internal function to return stake to participant and emit events
     * @param _participant Address of the participant to return stake to
     */
    function _returnStake(
        address _participant
    )
        internal
    {
        uint256 stake = participantStakes[
            _participant
        ];

        if (stake > 0) {
            participantStakes[_participant] = 0;
            totalStakes -= stake;

            payable(_participant).transfer(
                stake
            );

            emit StakeReturned(
                _participant,
                stake
            );
        }
    }

    /**
     * @notice Get the stake amount for a specific participant
     * @dev Returns the stake amount deposited by a participant
     * @param _participant Address of the participant to check
     * @return Stake amount deposited by the participant
     */
    function getParticipantStake(
        address _participant
    )
        external
        view
        returns (uint256)
    {
        return participantStakes[
            _participant
        ];
    }

    /**
     * @notice Get the total stakes collected from all participants
     * @dev Returns the sum of all stakes deposited by participants
     * @return Total amount of stakes collected
     */
    function getTotalStakes()
        external
        view
        returns (uint256)
    {
        return totalStakes;
    }

    /**
     * @notice Get the required stake amount for hackathon participation
     * @dev Returns the amount that participants must deposit when joining
     * @return Required stake amount for participation
     */
    function getStakeAmount()
        external
        view
        returns (uint256)
    {
        return stakeAmount;
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract VotingSystem {
    using SafeERC20 for IERC20;

    uint256[] public prizeDistribution;
    uint256 public pointsPerJudge;

    // PYUSD token for prize distribution
    IERC20 public pyusdToken;

    /**
     * @dev Set the PYUSD token address
     * @param _pyusdToken Address of the PYUSD token
     */
    function _setPyusdToken(
        address _pyusdToken
    )
        internal
    {
        pyusdToken = IERC20(
            _pyusdToken
        );
    }

    mapping(address => bool) public hasVoted;
    mapping(address => uint256) public totalPoints;
    mapping(address => mapping(address => uint256)) public judgeVotes;

    uint256 public votingDeadline;

    bool public votingOpen;
    uint256 public votingEndTime;

    uint256 public prizeClaimCooldown;
    mapping(address => bool) public hasClaimedPrize;

    // Dynamic winner tracking - updated on each vote
    mapping(address => uint256) public winnerPosition; // 0 = not a winner, 1+ = position (1-indexed)
    address[] public winners; // Array of winners in order (1st, 2nd, 3rd, etc.)
    uint256 public maxWinners; // Maximum number of winners (from prize distribution length)

    event JudgeVoted(
        address indexed judge,
        address indexed participant,
        uint256 points
    );

    event VotingOpened(
        uint256 deadline
    );


    event PrizeClaimed(
        address indexed winner,
        uint256 amount
    );

    event WinnerAdded(
        address indexed participant,
        uint256 position
    );

    event WinnerRemoved(
        address indexed participant
    );


    /**
     * @notice Vote for a participant's submission
     * @dev Internal function for judges to allocate points to participants and update winner list
     * @param _participant Address of the participant to vote for
     * @param _points Number of points to allocate (max 100 per judge)
     */
    function _voteForSubmission(
        address _participant,
        uint256 _points
    )
        internal
    {
        require(
            votingOpen,
            "Voting is not open"
        );

        require(
            block.timestamp <= votingDeadline,
            "Voting deadline has passed"
        );

        require(
            _points <= pointsPerJudge,
            "Cannot allocate more than 100 points"
        );

        require(
            hasVoted[msg.sender] == false,
            "Judge has already voted"
        );

        // Store old points for comparison
        uint256 oldPoints = totalPoints[_participant];

        // Add points to participant's total
        totalPoints[_participant] += _points;
        judgeVotes[msg.sender][_participant] = _points;
        hasVoted[msg.sender] = true;

        // Update winner list based on new points
        _updateWinnerList(
            _participant,
            oldPoints,
            totalPoints[_participant]
        );

        emit JudgeVoted(
            msg.sender,
            _participant,
            _points
        );
    }


    /**
     * @notice Update winner list dynamically based on participant's new points
     * @dev Maintains a sorted list of top performers without sorting all participants
     * @param _participant Address of the participant whose points changed
     * @param _oldPoints Previous total points
     * @param _newPoints New total points
     */
    function _updateWinnerList(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    )
        internal
    {
        uint256 currentPosition = winnerPosition[
            _participant
        ];

        // If participant was already a winner
        if (currentPosition > 0) {

            // Check if they should be moved up or down in rankings
            _adjustWinnerPosition(
                _participant,
                _oldPoints,
                _newPoints
            );

            return;
        }

        if (_newPoints > 0 && (winners.length < maxWinners || _newPoints > totalPoints[_getLowestWinner()])) {
            _addNewWinner(
                _participant
            );
        }
    }

    /**
     * @notice Adjust position of existing winner based on new points
     * @dev Moves winner up or down in the rankings as needed
     */
    function _adjustWinnerPosition(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    )
        internal
    {
        uint256 currentPosition = winnerPosition[
            _participant
        ];

        // If points increased, try to move up
        if (_newPoints > _oldPoints) {
            _moveWinnerUp(
                _participant,
                currentPosition
            );

            return;
        }

        if (_newPoints < _oldPoints) {
            _moveWinnerDown(
                _participant,
                currentPosition
            );
        }
    }

    /**
     * @notice Move winner up in rankings
     */
    function _moveWinnerUp(
        address _participant,
        uint256 currentPosition
    )
        internal
    {
        uint256 newPosition = currentPosition;

        // Find the correct position by comparing with winners above
        for (uint256 i = currentPosition - 1; i > 0; i--) {
            if (totalPoints[_participant] > totalPoints[winners[i - 1]]) {
                newPosition = i;
            } else {
                break;
            }
        }

        if (newPosition != currentPosition) {
            _swapWinners(
                _participant,
                currentPosition,
                newPosition
            );
        }
    }

    /**
     * @notice Move winner down in rankings or remove if no longer in top
     */
    function _moveWinnerDown(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        // Find the correct position by comparing with winners below
        for (uint256 i = currentPosition; i < winners.length; i++) {
            if (totalPoints[_participant] < totalPoints[winners[i]]) {
                newPosition = i + 1;
            } else {
                break;
            }
        }

        // If moved beyond max winners, remove from winners
        if (newPosition > maxWinners) {
            _removeWinner(_participant, currentPosition);
        } else if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    /**
     * @notice Add new winner to the list
     */
    function _addNewWinner(address _participant) internal {
        if (winners.length < maxWinners) {
            // Add to end if we have space
            winners.push(_participant);
            winnerPosition[_participant] = winners.length;
            _moveWinnerUp(_participant, winners.length);
        } else {
            // Replace the lowest winner
            address lowestWinner = _getLowestWinner();
            uint256 lowestPosition = winnerPosition[lowestWinner];
            _removeWinner(lowestWinner, lowestPosition);
            _addNewWinner(_participant);
        }
    }

    /**
     * @notice Remove winner from the list
     */
    function _removeWinner(address _participant, uint256 position) internal {
        // Move last winner to this position
        if (winners.length > 1) {
            address lastWinner = winners[winners.length - 1];
            winners[position - 1] = lastWinner;
            winnerPosition[lastWinner] = position;
        }

        // Remove from array
        winners.pop();
        winnerPosition[_participant] = 0;

        emit WinnerRemoved(
            _participant
        );
    }

    /**
     * @notice Swap two winners in the list
     */
    function _swapWinners(address _participant, uint256 fromPosition, uint256 toPosition) internal {
        address otherParticipant = winners[toPosition - 1];

        // Swap in array
        winners[fromPosition - 1] = otherParticipant;
        winners[toPosition - 1] = _participant;

        // Update positions
        winnerPosition[_participant] = toPosition;
        winnerPosition[otherParticipant] = fromPosition;

        emit WinnerAdded(_participant, toPosition);
    }

    /**
     * @notice Get the winner with the lowest points
     */
    function _getLowestWinner()
        internal
        view
        returns (address)
    {
        require(
            winners.length > 0,
            "No winners"
        );

        return winners[
            winners.length - 1
        ];
    }

    /**
     * @notice Check if voting has ended (either manually closed or deadline passed)
     * @dev Automatically determines if voting should be considered ended
     * @return True if voting has ended, false otherwise
     */
    function _isVotingEnded()
    internal
    view
    returns (bool)
    {
        // If manually closed, voting has ended
        if (!votingOpen) {
            return true;
        }
        // If deadline has passed, voting has ended
        if (block.timestamp > votingDeadline) {
            return true;
        }
        return false;
    }

    /**
     * @notice Get the actual voting end time
     * @dev Returns the time when voting actually ended (manual close or deadline)
     * @return Timestamp when voting ended
     */
    function _getVotingEndTime() internal view returns (uint256) {
        // If manually closed, return the recorded end time
        if (!votingOpen && votingEndTime > 0) {
            return votingEndTime;
        }
        // If deadline passed but not manually closed, return deadline
        if (block.timestamp > votingDeadline) {
            return votingDeadline;
        }
        // Voting is still active
        return 0;
    }



    /**
     * @notice Check if a participant is a winner
     * @dev Uses pre-determined winners for O(1) lookup
     * @param _participant Address of the participant to check
     * @return True if the participant is a winner, false otherwise
     */
    function _isWinner(
        address _participant
    )
        internal
        view
        returns (bool)
    {
        return winnerPosition[_participant] > 0;
    }


    /**
     * @notice Get prize amount for a participant based on their ranking
     * @dev Determines the participant's position and returns the exact prize amount from the distribution array
     * @param _participant Address of the participant
     * @return Prize amount for the participant
     */
    function _getPrizeAmount(
        address _participant
    )
        internal
        view
        returns (uint256)
    {
        uint256 position = winnerPosition[
            _participant
        ];

        if (position == 0) {
            return 0; // Not a winner
        }

        // Convert to 0-indexed for array access
        uint256 arrayIndex = position - 1;

        // Check if position is within prize distribution range
        if (arrayIndex >= prizeDistribution.length) {
            return 0;
        }

        // Return exact prize amount from the distribution array
        return prizeDistribution[
            arrayIndex
        ];
    }

    /**
     * @notice Claim prize for a winner
     * @dev Internal function to handle prize claiming with cooldown and winner validation
     * @param _participant Address of the participant claiming the prize
     */
    function _claimPrize(
        address _participant,
        uint256 /* _totalPrizePool */
    )
        internal
    {
        require(
            hasClaimedPrize[_participant] == false,
            "Prize already claimed"
        );

        require(
            _isWinner(_participant),
            "Not a winner"
        );

        uint256 prizeAmount = _getPrizeAmount(
            _participant
        );

        hasClaimedPrize[_participant] = true;

        pyusdToken.safeTransfer(
            _participant,
            prizeAmount
        );

        emit PrizeClaimed(
            _participant,
            prizeAmount
        );
    }

    // Getter functions

    /**
     * @notice Get the maximum number of winners based on prize distribution
     * @return Maximum number of winners that can be selected
     */
    function getMaxWinners()
        external
        view
        returns (uint256)
    {
        return maxWinners;
    }

    /**
     * @notice Get the prize distribution array
     * @return Array defining how the prize pool is distributed among winners
     */
    function getPrizeDistribution()
        external
        view
        returns (uint256[] memory)
    {
        return prizeDistribution;
    }

    /**
     * @notice Get the points each judge can distribute
     * @return Maximum points each judge can allocate
     */
    function getPointsPerJudge()
        external
        view
        returns (uint256)
    {
        return pointsPerJudge;
    }

    /**
     * @notice Get total points received by a participant
     * @param _participant Address of the participant
     * @return Total points received by the participant
     */
    function getTotalPoints(
        address _participant
    )
        external
        view
        returns (uint256)
    {
        return totalPoints[
            _participant
        ];
    }

    /**
     * @notice Get all winners in order
     * @return Array of winner addresses in ranking order
     */
    function getWinners()
        external
        view
        returns (address[] memory)
    {
        return winners;
    }

    /**
     * @notice Get winner position for a participant
     * @param _participant Address of the participant
     * @return Position (1-indexed, 0 if not a winner)
     */
    function getWinnerPosition(
        address _participant
    )
        external
        view
        returns (uint256)
    {
        return winnerPosition[
            _participant
        ];
    }

    /**
     * @notice Get the voting deadline timestamp
     * @return Timestamp when voting period ends
     */
    function getVotingDeadline()
        external
        view
        returns (uint256)
    {
        return votingDeadline;
    }

    /**
     * @notice Get the voting end time timestamp
     * @return Timestamp when voting actually ended (manual close or deadline)
     */
    function getVotingEndTime()
        external
        view
        returns (uint256)
    {
        return _getVotingEndTime();
    }

    /**
     * @notice Check if voting is currently open
     * @return True if voting is open and deadline not passed, false otherwise
     */
    function isVotingOpen()
        external
        view
        returns (bool)
    {
        return votingOpen && block.timestamp <= votingDeadline;
    }

    /**
     * @notice Get the prize claim cooldown period
     * @return Cooldown period in seconds
     */
    function getPrizeClaimCooldown()
        external
        view
        returns (uint256)
    {
        return prizeClaimCooldown;
    }

    /**
     * @notice Check if a participant has claimed their prize
     * @param _participant Address of the participant
     * @return True if prize has been claimed, false otherwise
     */
    function hasParticipantClaimedPrize(
        address _participant
    )
        external
        view
        returns (bool)
    {
        return hasClaimedPrize[_participant];
    }
}

File 22 of 24 : VotingTypes.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/**
 * @title VotingTypes
 * @dev Common types and enums for all voting systems
 */

// ============ ENUMS ============

/**
 * @dev Available voting system types
 */
enum VotingSystemType {
    OPEN,           // Transparent voting (current system)
    COMMIT_REVEAL,  // Hidden until reveal phase
    ZK_SNARK,       // Zero-knowledge proofs
    QUADRATIC       // Quadratic voting
}

// ============ STRUCTS ============

/**
 * @dev Voting configuration for hackathon creation
 */
struct VotingConfig {
    VotingSystemType systemType;
    bool useQuadraticVoting;
    uint256 votingPowerPerJudge; // Credits for quadratic voting, points for normal voting
    uint256 maxWinners;          // Number of winners to track
}

/**
 * @dev Judge voting data for batch submissions
 */
struct JudgeVote {
    address[] participants;
    uint256[] points;
    uint256 nonce;           // For commit-reveal
    bytes32 commitment;      // For commit-reveal
    bytes zkProof;           // For ZK-SNARK
    uint256[] quadraticCredits; // For quadratic voting
}

/**
 * @dev Winner information
 */
struct WinnerInfo {
    address participant;
    uint256 totalPoints;
    uint256 position;        // 1-indexed position
    bool isWinner;          // True if in top N
}

/**
 * @dev Voting statistics
 */
struct VotingStats {
    uint256 totalJudges;
    uint256 votedJudges;
    uint256 totalParticipants;
    uint256 totalPointsAllocated;
    bool votingOpen;
    uint256 votingDeadline;
}

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

import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title ZKProofVerifier
 * @dev Verifies Groth16 Zero-Knowledge proofs for the DeHack voting system
 * @notice Fast and efficient Groth16 proof verification for hackathon voting
 */
contract ZKProofVerifier is Ownable {

    // ============ STRUCTS ============

    struct G1Point {
        uint256 x;
        uint256 y;
    }

    struct G2Point {
        uint256[2] x;
        uint256[2] y;
    }

    struct Proof {
        G1Point a;
        G2Point b;
        G1Point c;
    }

    // ============ STATE VARIABLES ============

    // Groth16 verification key components
    uint256[2] public vk_alpha1_x;
    uint256[2] public vk_alpha1_y;
    uint256[2] public vk_beta2_x;
    uint256[2] public vk_beta2_y;
    uint256 public vk_gamma2_x;
    uint256 public vk_gamma2_y;
    uint256[2] public vk_delta2_x;
    uint256[2] public vk_delta2_y;
    uint256[] public vk_ic_x;
    uint256[] public vk_ic_y;

    // Circuit parameters
    uint256 public constant MAX_POINTS = 100;
    uint256 public constant MIN_POINTS = 0;

    // ============ EVENTS ============

    event ProofVerified(address indexed judge, address indexed participant, uint256 points);
    event VerificationKeyUpdated();

    // ============ CONSTRUCTOR ============

    constructor() Ownable(msg.sender) {
        // Initialize with default verification key
        // In production, this would be set during deployment
        _initializeDefaultVerificationKey();
    }

    // ============ VERIFICATION FUNCTIONS ============

    /**
     * @dev Verify a Groth16 proof for vote validity
     * @param _proof The Groth16 proof to verify
     * @param _publicSignals Public signals from the proof
     * @param _judge Judge address
     * @param _participant Participant address
     * @param _points Points allocated
     * @return True if proof is valid
     */
    function verifyGroth16Proof(
        Proof memory _proof,
        uint256[] memory _publicSignals,
        address _judge,
        address _participant,
        uint256 _points
    ) external returns (bool) {
        // Verify proof using Groth16 verification
        bool proofValid = _verifyGroth16Proof(_proof, _publicSignals);

        if (!proofValid) {
            return false;
        }

        // Verify public signals match expected values
        require(_publicSignals.length >= 3, "Invalid public signals length");

        // Public signals should contain:
        // [0] = commitment hash
        // [1] = points
        // [2] = judge address hash

        // Verify points match
        require(_publicSignals[1] == _points, "Points mismatch");

        // Verify points are within valid range
        require(_points >= MIN_POINTS && _points <= MAX_POINTS, "Invalid points range");

        // Verify judge address hash
        uint256 judgeHash = uint256(keccak256(abi.encodePacked(_judge)));
        require(_publicSignals[2] == judgeHash, "Judge hash mismatch");

        emit ProofVerified(_judge, _participant, _points);
        return true;
    }

    /**
     * @dev Verify a simplified proof (for testing/development)
     * @param _proofHash Hash of the proof
     * @param _judge Judge address
     * @param _participant Participant address
     * @param _points Points allocated
     * @return True if proof is valid
     */
    function verifySimplifiedProof(
        bytes32 _proofHash,
        address _judge,
        address _participant,
        uint256 _points
    ) external view returns (bool) {
        // Verify points are within valid range
        if (_points < MIN_POINTS || _points > MAX_POINTS) {
            return false;
        }

        // For testing purposes, accept any non-zero proof hash
        // In production, this would verify the actual ZK proof
        return _proofHash != bytes32(0);
    }

    // ============ INTERNAL FUNCTIONS ============

    /**
     * @dev Verify Groth16 proof
     * @param _proof The proof to verify
     * @param _publicSignals Public signals
     * @return True if proof is valid
     */
    function _verifyGroth16Proof(
        Proof memory _proof,
        uint256[] memory _publicSignals
    ) internal pure returns (bool) {
        // This is a simplified implementation
        // In production, you would use a proper Groth16 verifier

        // Verify proof components are non-zero
        if (_proof.a.x == 0 || _proof.a.y == 0) {
            return false;
        }

        if (_proof.b.x[0] == 0 || _proof.b.x[1] == 0) {
            return false;
        }

        if (_proof.b.y[0] == 0 || _proof.b.y[1] == 0) {
            return false;
        }

        if (_proof.c.x == 0 || _proof.c.y == 0) {
            return false;
        }

        // Verify public signals are valid
        if (_publicSignals.length == 0) {
            return false;
        }

        // In a real implementation, this would perform the actual
        // Groth16 verification using the verification key
        // For now, we'll do a simplified check

        // Verify that the proof components form a valid proof
        // This is a placeholder - real verification would use elliptic curve operations

        return true;
    }

    /**
     * @dev Initialize default verification key
     */
    function _initializeDefaultVerificationKey() internal {
        // This would be set with the actual verification key from the trusted setup
        // For now, we'll use placeholder values

        vk_alpha1_x = [uint256(0), uint256(0)];
        vk_alpha1_y = [uint256(0), uint256(0)];

        vk_beta2_x = [uint256(0), uint256(0)];
        vk_beta2_y = [uint256(0), uint256(0)];

        vk_gamma2_x = 0;
        vk_gamma2_y = 0;

        vk_delta2_x = [uint256(0), uint256(0)];
        vk_delta2_y = [uint256(0), uint256(0)];

        // Initialize IC (Input Coefficients) array
        vk_ic_x = new uint256[](4); // 4 public inputs
        vk_ic_y = new uint256[](4);
        for (uint256 i = 0; i < 4; i++) {
            vk_ic_x[i] = 0;
            vk_ic_y[i] = 0;
        }
    }

    // ============ ADMIN FUNCTIONS ============

    /**
     * @dev Update verification key (only owner)
     * @param _vk_alpha1_x New alpha1 x value
     * @param _vk_alpha1_y New alpha1 y value
     * @param _vk_beta2_x New beta2 x value
     * @param _vk_beta2_y New beta2 y value
     * @param _vk_gamma2_x New gamma2 x value
     * @param _vk_gamma2_y New gamma2 y value
     * @param _vk_delta2_x New delta2 x value
     * @param _vk_delta2_y New delta2 y value
     * @param _vk_ic_x New IC x values
     * @param _vk_ic_y New IC y values
     */
    function updateVerificationKey(
        uint256[2] memory _vk_alpha1_x,
        uint256[2] memory _vk_alpha1_y,
        uint256[2] memory _vk_beta2_x,
        uint256[2] memory _vk_beta2_y,
        uint256 _vk_gamma2_x,
        uint256 _vk_gamma2_y,
        uint256[2] memory _vk_delta2_x,
        uint256[2] memory _vk_delta2_y,
        uint256[] memory _vk_ic_x,
        uint256[] memory _vk_ic_y
    ) external onlyOwner {
        vk_alpha1_x = _vk_alpha1_x;
        vk_alpha1_y = _vk_alpha1_y;
        vk_beta2_x = _vk_beta2_x;
        vk_beta2_y = _vk_beta2_y;
        vk_gamma2_x = _vk_gamma2_x;
        vk_gamma2_y = _vk_gamma2_y;
        vk_delta2_x = _vk_delta2_x;
        vk_delta2_y = _vk_delta2_y;
        vk_ic_x = _vk_ic_x;
        vk_ic_y = _vk_ic_y;

        emit VerificationKeyUpdated();
    }

    // ============ VIEW FUNCTIONS ============

    /**
     * @dev Get verification key
     * @return alpha1_x, alpha1_y, beta2_x, beta2_y, gamma2_x, gamma2_y, delta2_x, delta2_y, ic_x, ic_y
     */
    function getVerificationKey() external view returns (
        uint256[2] memory,
        uint256[2] memory,
        uint256[2] memory,
        uint256[2] memory,
        uint256,
        uint256,
        uint256[2] memory,
        uint256[2] memory,
        uint256[] memory,
        uint256[] memory
    ) {
        return (vk_alpha1_x, vk_alpha1_y, vk_beta2_x, vk_beta2_y, vk_gamma2_x, vk_gamma2_y, vk_delta2_x, vk_delta2_y, vk_ic_x, vk_ic_y);
    }

    /**
     * @dev Check if proof is valid format
     * @param _proof The proof to check
     * @return True if proof format is valid
     */
    function isValidProofFormat(Proof memory _proof) external pure returns (bool) {
        // Check that all proof components are non-zero
        if (_proof.a.x == 0 || _proof.a.y == 0) {
            return false;
        }

        if (_proof.b.x[0] == 0 || _proof.b.x[1] == 0) {
            return false;
        }

        if (_proof.b.y[0] == 0 || _proof.b.y[1] == 0) {
            return false;
        }

        if (_proof.c.x == 0 || _proof.c.y == 0) {
            return false;
        }

        return true;
    }
}

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

import "./IVotingSystem.sol";
import "./VotingTypes.sol";
import "./ZKProofVerifier.sol";

/**
 * @title ZKVotingSystem
 * @dev ZK voting system with batch voting support
 * @notice Judges can vote for multiple participants in one transaction with ZK proofs
 */
contract ZKVotingSystem is IVotingSystem {

    // ============ STATE VARIABLES ============

    // Voting configuration
    uint256 public pointsPerJudge;
    uint256 public maxWinners;
    uint256 public votingDeadline;

    // Judge management
    mapping(address => bool) public isJudge;
    mapping(address => bool) public hasVoted;
    address[] public judges;
    uint256 public totalJudges;

    // ZK proof verifier
    ZKProofVerifier public proofVerifier;

    // Batch vote commitments
    mapping(address => bytes32) public batchCommitments;
    mapping(address => bool) public hasCommitted;

    // Batch vote reveals
    mapping(address => address[]) public revealedParticipants;
    mapping(address => uint256[]) public revealedPoints;
    mapping(address => uint256) public revealedNonces;
    mapping(address => bool) public hasRevealed;

    // Participant scoring (only after reveal)
    mapping(address => uint256) public totalPoints;
    mapping(address => mapping(address => uint256)) public judgeVotes;

    // Dynamic winner tracking
    mapping(address => uint256) public winnerPosition;
    address[] public winners;

    // ============ EVENTS ============

    event BatchVotesCommitted(
        address indexed judge,
        bytes32 commitment
    );

    event BatchVotesRevealed(
        address indexed judge,
        address[] participants,
        uint256[] points,
        bytes zkProof
    );

    event JudgeAdded(address indexed judge);
    event JudgeRemoved(address indexed judge);

    // ============ MODIFIERS ============

    modifier onlyJudge() {
        require(isJudge[msg.sender], "Only judges can perform this action");
        _;
    }

    modifier onlyDuringVoting() {
        require(block.timestamp <= votingDeadline, "Voting deadline has passed");
        _;
    }

    // ============ INITIALIZATION ============

    /**
     * @dev Initialize the voting system
     */
    function initialize(
        uint256 _pointsPerJudge,
        uint256 _maxWinners,
        address[] calldata _judges
    )
        external
        override
    {
        require(pointsPerJudge == 0, "Already initialized");

        pointsPerJudge = _pointsPerJudge;
        maxWinners = _maxWinners;

        // Deploy ZK proof verifier
        proofVerifier = new ZKProofVerifier();

        // Add judges
        for (uint256 i = 0; i < _judges.length; i++) {
            _addJudge(_judges[i]);
        }
    }

    /**
     * @dev Set voting deadline (called by Hackathon contract)
     */
    function setVotingDeadline(
        uint256 _deadline
    )
        external
    {
        require(votingDeadline == 0, "Deadline already set");
        votingDeadline = _deadline;
    }

    // ============ CORE VOTING FUNCTIONS ============

    /**
     * @dev Submit votes for multiple participants (interface requirement)
     */
    function submitVotes(
        address[] calldata /* _participants */,
        uint256[] calldata /* _points */
    )
        external
        pure
        override
    {
        // This function signature is required by interface but ZK uses commit-reveal
        revert("Use commitBatchVotes and revealBatchVotes for ZK voting");
    }

    /**
     * @dev Commit batch votes for multiple participants
     * @param _participants Array of participant addresses
     * @param _points Array of points for each participant
     * @param _nonce Random nonce for commitment
     */
    function commitBatchVotes(
        address[] calldata _participants,
        uint256[] calldata _points,
        uint256 _nonce
    ) external onlyJudge onlyDuringVoting {
        require(_participants.length == _points.length, "Arrays length mismatch");
        require(_participants.length > 0, "Must vote for at least one participant");
        require(!hasCommitted[msg.sender], "Judge has already committed");

        // Validate points
        uint256 totalPointsUsed = 0;
        for (uint256 i = 0; i < _participants.length; i++) {
            require(_points[i] > 0, "Points must be greater than 0");
            totalPointsUsed += _points[i];
        }

        require(totalPointsUsed <= pointsPerJudge, "Exceeds points per judge limit");

        // Create batch commitment
        bytes32 commitment = keccak256(abi.encodePacked(
            msg.sender,
            _participants,
            _points,
            _nonce
        ));

        batchCommitments[msg.sender] = commitment;
        hasCommitted[msg.sender] = true;

        emit BatchVotesCommitted(
            msg.sender,
            commitment
        );
    }

    /**
     * @dev Reveal batch votes with ZK proof
     * @param _participants Array of participant addresses
     * @param _points Array of points for each participant
     * @param _nonce Nonce used in commitment
     * @param _zkProof ZK proof for batch vote validity
     */
    function revealBatchVotes(
        address[] calldata _participants,
        uint256[] calldata _points,
        uint256 _nonce,
        bytes calldata _zkProof
    )
        external
        onlyJudge
    {
        require(hasCommitted[msg.sender], "Must commit votes first");
        require(!hasRevealed[msg.sender], "Judge has already revealed");
        require(_participants.length == _points.length, "Arrays length mismatch");

        // Verify commitment
        bytes32 expectedCommitment = keccak256(abi.encodePacked(
            msg.sender,
            _participants,
            _points,
            _nonce
        ));

        require(batchCommitments[msg.sender] == expectedCommitment, "Commitment mismatch");

        // Verify ZK proof for batch vote validity
        require(
            _verifyBatchVoteProof(
                _zkProof,
                _participants,
                _points
            ),
            "Invalid ZK proof for batch vote"
        );

        // Process revealed votes
        uint256 totalPointsUsed = 0;

        for (uint256 i = 0; i < _participants.length; i++) {
            require(_points[i] > 0, "Points must be greater than 0");
            totalPointsUsed += _points[i];

            // Update participant's total points
            uint256 oldPoints = totalPoints[_participants[i]];
            totalPoints[_participants[i]] += _points[i];

            // Store judge's vote
            judgeVotes[msg.sender][_participants[i]] = _points[i];

            // Update winner list
            _updateWinnerList(_participants[i], oldPoints, totalPoints[_participants[i]]);
        }

        require(totalPointsUsed <= pointsPerJudge, "Exceeds points per judge limit");

        // Store revealed data
        revealedParticipants[msg.sender] = _participants;
        revealedPoints[msg.sender] = _points;
        revealedNonces[msg.sender] = _nonce;
        hasRevealed[msg.sender] = true;

        emit BatchVotesRevealed(
            msg.sender,
            _participants,
            _points,
            _zkProof
        );
    }

    // ============ ZK PROOF VERIFICATION ============

    /**
     * @dev Verify ZK proof for batch vote validity
     * @param _zkProof ZK proof bytes
     * @param _points Array of points for each participant
     * @return True if proof is valid
     */
    function _verifyBatchVoteProof(
        bytes calldata _zkProof,
        address[] calldata /* _participants */,
        uint256[] calldata _points
    )
        internal
        view
        returns (bool)
    {
        // Verify that sum of points doesn't exceed limit
        uint256 totalPointsSum = 0;
        for (uint256 i = 0; i < _points.length; i++) {
            totalPointsSum += _points[i];
        }

        if (totalPointsSum > pointsPerJudge) {
            return false;
        }

        // Verify all points are positive
        for (uint256 i = 0; i < _points.length; i++) {
            if (_points[i] == 0) {
                return false;
            }
        }

        // Verify ZK proof using the proof verifier
        // In a real implementation, this would verify the actual ZK proof
        // For now, we'll use a simplified verification
        return _verifySimplifiedBatchProof(
            _zkProof,
            _points
        );
    }

    /**
     * @dev Simplified batch proof verification (for demonstration)
     */
    function _verifySimplifiedBatchProof(
        bytes calldata _zkProof,
        uint256[] calldata /* _points */
    )
        internal
        pure
        returns (bool)
    {
        // @TODO: Implement actual ZK proof verification
        // Simplified verification - in production, this would verify actual ZK proof
        // For now, just check that proof is not empty
        return _zkProof.length > 0;
    }

    // ============ JUDGE MANAGEMENT ============

    function addJudge(
        address _judge
    )
        external
        override
    {
        require(_judge != address(0), "Invalid judge address");
        require(!isJudge[_judge], "Judge already exists");

        _addJudge(_judge);
    }

    function removeJudge(
        address _judge
    )
        external
        override
    {
        require(isJudge[_judge], "Judge does not exist");
        require(!hasCommitted[_judge], "Cannot remove judge who has committed");

        _removeJudge(_judge);
    }

    function _addJudge(
        address _judge
    )
        internal
    {
        isJudge[_judge] = true;
        judges.push(_judge);
        totalJudges++;

        emit JudgeAdded(_judge);
    }

    function _removeJudge(
        address _judge
    )
        internal
    {
        isJudge[_judge] = false;

        for (uint256 i = 0; i < judges.length; i++) {
            if (judges[i] == _judge) {
                judges[i] = judges[judges.length - 1];
                judges.pop();
                break;
            }
        }

        totalJudges--;

        emit JudgeRemoved(_judge);
    }

    // ============ WINNER TRACKING (Same as OpenVoting) ============

    function _updateWinnerList(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    ) internal {
        uint256 currentPosition = winnerPosition[_participant];

        if (currentPosition > 0) {
            _adjustWinnerPosition(_participant, _oldPoints, _newPoints);
            return;
        }

        if (_newPoints > 0 && (winners.length < maxWinners || _newPoints > totalPoints[_getLowestWinner()])) {
            _addNewWinner(_participant);
        }
    }

    function _adjustWinnerPosition(
        address _participant,
        uint256 _oldPoints,
        uint256 _newPoints
    ) internal {
        uint256 currentPosition = winnerPosition[_participant];

        if (_newPoints > _oldPoints) {
            _moveWinnerUp(_participant, currentPosition);
        } else if (_newPoints < _oldPoints) {
            _moveWinnerDown(_participant, currentPosition);
        }
    }

    function _moveWinnerUp(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        for (uint256 i = currentPosition - 1; i > 0; i--) {
            if (totalPoints[_participant] > totalPoints[winners[i - 1]]) {
                newPosition = i;
            } else {
                break;
            }
        }

        if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    function _moveWinnerDown(address _participant, uint256 currentPosition) internal {
        uint256 newPosition = currentPosition;

        for (uint256 i = currentPosition; i < winners.length; i++) {
            if (totalPoints[_participant] < totalPoints[winners[i]]) {
                newPosition = i + 1;
            } else {
                break;
            }
        }

        if (newPosition > maxWinners) {
            _removeWinner(_participant, currentPosition);
        } else if (newPosition != currentPosition) {
            _swapWinners(_participant, currentPosition, newPosition);
        }
    }

    function _addNewWinner(address _participant) internal {
        if (winners.length < maxWinners) {
            winners.push(_participant);
            winnerPosition[_participant] = winners.length;
            _moveWinnerUp(_participant, winners.length);
        } else {
            address lowestWinner = _getLowestWinner();
            uint256 lowestPosition = winnerPosition[lowestWinner];
            _removeWinner(lowestWinner, lowestPosition);
            _addNewWinner(_participant);
        }
    }

    function _removeWinner(
        address _participant,
        uint256 position
    )
        internal
    {
        if (position < winners.length) {
            address lastWinner = winners[winners.length - 1];
            winners[position - 1] = lastWinner;
            winnerPosition[lastWinner] = position;
            winners.pop();
        }

        winnerPosition[_participant] = 0;
    }

    function _swapWinners(
        address _participant,
        uint256 fromPosition,
        uint256 toPosition
    )
        internal
    {
        if (fromPosition != toPosition) {
            winnerPosition[_participant] = toPosition;
            winnerPosition[winners[toPosition - 1]] = fromPosition;

            address temp = winners[fromPosition - 1];
            winners[fromPosition - 1] = winners[toPosition - 1];
            winners[toPosition - 1] = temp;
        }
    }

    function _getLowestWinner()
        internal
        view
        returns (address)
    {
        if (winners.length == 0) return address(0);
        return winners[winners.length - 1];
    }

    // ============ VIEW FUNCTIONS ============

    function getWinners()
        external
        view
        override
        returns (address[] memory)
    {
        return winners;
    }

    function getParticipantPoints(
        address _participant
    )
        external
        view
        override
        returns (uint256)
    {
        return totalPoints[_participant];
    }

    function getParticipantRanking(
        address _participant
    )
        external
        view
        override
        returns (uint256)
    {
        return winnerPosition[_participant];
    }

    function hasJudgeVoted(
        address _judge
    )
        external
        view
        override
        returns (bool)
    {
        return hasRevealed[_judge];
    }

    function getVotingStats()
        external
        view
        override
        returns (
            uint256 _totalJudges,
            uint256 _votedJudges,
            uint256 _totalParticipants
        )
    {
        _totalJudges = totalJudges;

        uint256 votedCount = 0;
        for (uint256 i = 0; i < judges.length; i++) {
            if (hasRevealed[judges[i]]) {
                votedCount++;
            }
        }

        _votedJudges = votedCount;
        _totalParticipants = winners.length;
    }

    function getWinnerCount()
        external
        view
        override
        returns (uint256)
    {
        return winners.length;
    }

    function getWinnerAtPosition(
        uint256 _position
    )
        external
        view
        override
        returns (address)
    {
        require(_position > 0 && _position <= winners.length, "Invalid position");
        return winners[_position - 1];
    }

    function isWinner(
        address _participant
    )
        external
        view
        override

        returns (bool)
    {
        return winnerPosition[_participant] > 0;
    }

    // ============ ZK-SPECIFIC FUNCTIONS ============

    /**
     * @dev Generate commitment hash for batch votes
     * @param _judge Judge address
     * @param _participants Array of participant addresses
     * @param _points Array of points
     * @param _nonce Random nonce
     * @return Commitment hash
     */
    function generateBatchCommitment(
        address _judge,
        address[] calldata _participants,
        uint256[] calldata _points,
        uint256 _nonce
    )
        external
        pure
        returns (bytes32)
    {
        return keccak256(
            abi.encodePacked(
                _judge,
                _participants,
                _points,
                _nonce
            )
        );
    }

    /**
     * @dev Get judge's revealed votes
     * @param _judge Judge address
     * @return participants Array of participant addresses
     * @return points Array of points
     */
    function getJudgeRevealedVotes(
        address _judge
    )
        external
        view
        returns (
            address[] memory participants,
            uint256[] memory points
    ) {
        require(
            hasRevealed[_judge],
            "Judge has not revealed votes"
        );

        return (
            revealedParticipants[_judge],
            revealedPoints[_judge]
        );
    }

    /**
     * @dev Check if judge has committed votes
     * @param _judge Judge address
     * @return True if judge has committed
     */
    function hasJudgeCommitted(
        address _judge
    )
        external
        view
        returns (bool)
    {
        return hasCommitted[_judge];
    }

    /**
     * @dev Get judge's commitment
     * @param _judge Judge address
     * @return Commitment hash
     */
    function getJudgeCommitment(
        address _judge
    )
        external
        view
        returns (bytes32)
    {
        return batchCommitments[_judge];
    }
}

Settings
{
  "evmVersion": "cancun",
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "remappings": [
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/",
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/",
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/",
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/",
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/",
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/",
    "project/:@openzeppelin/contracts/=npm/@openzeppelin/[email protected]/"
  ]
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"address","name":"_curveRouter","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"_pyusd","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FailedDeployment","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"judge","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ethAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pyusdAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minPyusdOut","type":"uint256"}],"name":"EthToPyusdConversion","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"judge","type":"address"}],"name":"GlobalJudgeAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"judge","type":"address"}],"name":"GlobalJudgeRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"hackathonAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"hackathonId","type":"uint256"},{"indexed":true,"internalType":"address","name":"organizer","type":"address"},{"indexed":false,"internalType":"uint256","name":"prizePool","type":"uint256"},{"indexed":false,"internalType":"enum VotingSystemType","name":"votingSystem","type":"uint8"},{"indexed":false,"internalType":"bool","name":"useQuadraticVoting","type":"bool"}],"name":"HackathonCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"judge","type":"address"},{"indexed":true,"internalType":"address","name":"delegate","type":"address"}],"name":"JudgeDelegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"judge","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"JudgeRewardAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"judge","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"JudgeRewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"proposalId","type":"bytes32"},{"indexed":false,"internalType":"string","name":"proposalType","type":"string"},{"indexed":true,"internalType":"address","name":"proposer","type":"address"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"proposalId","type":"bytes32"}],"name":"ProposalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"proposalId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"voter","type":"address"},{"indexed":false,"internalType":"bool","name":"support","type":"bool"}],"name":"VoteCast","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldVotesRequired","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newVotesRequired","type":"uint256"}],"name":"VotesRequiredChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"votingContract","type":"address"},{"indexed":false,"internalType":"enum VotingSystemType","name":"systemType","type":"uint8"},{"indexed":false,"internalType":"bool","name":"useQuadraticVoting","type":"bool"}],"name":"VotingSystemDeployed","type":"event"},{"inputs":[],"name":"MAX_PRIZE_CLAIM_COOLDOWN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RevealCommitVotingImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_judge","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"addJudgeReward","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"claimJudgeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_hackathonId","type":"uint256"},{"internalType":"uint256","name":"_startTime","type":"uint256"},{"internalType":"uint256","name":"_endTime","type":"uint256"},{"internalType":"uint256","name":"_minimumSponsorContribution","type":"uint256"},{"internalType":"uint256","name":"_stakeAmount","type":"uint256"},{"internalType":"uint256[]","name":"_prizeDistribution","type":"uint256[]"},{"internalType":"address[]","name":"_selectedJudges","type":"address[]"},{"components":[{"internalType":"enum VotingSystemType","name":"systemType","type":"uint8"},{"internalType":"bool","name":"useQuadraticVoting","type":"bool"},{"internalType":"uint256","name":"votingPowerPerJudge","type":"uint256"},{"internalType":"uint256","name":"maxWinners","type":"uint256"}],"internalType":"struct VotingConfig","name":"_votingConfig","type":"tuple"}],"name":"createHackathon","outputs":[{"internalType":"address","name":"hackathonAddress","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"curveRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_delegate","type":"address"}],"name":"delegateToAgent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegateToJudge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"emergencyWithdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyWithdrawPyusd","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethAmount","type":"uint256"}],"name":"estimatePyusdOutput","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_proposalId","type":"bytes32"},{"internalType":"address","name":"_judge","type":"address"},{"internalType":"uint256","name":"_newVotesRequired","type":"uint256"}],"name":"executeProposal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurveRouterInfo","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_delegate","type":"address"}],"name":"getDelegateJudge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGlobalJudges","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getHackathonCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_judge","type":"address"}],"name":"getJudgeDelegate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_judge","type":"address"}],"name":"getJudgeRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_organizer","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getOrganizerHackathon","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_organizer","type":"address"}],"name":"getOrganizerHackathonCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_proposalId","type":"bytes32"}],"name":"getProposalVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPyusdBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWethBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"globalJudges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"hasClaimedReward","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_proposalId","type":"bytes32"},{"internalType":"address","name":"_judge","type":"address"}],"name":"hasJudgeVoted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"hasVoted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isGlobalJudge","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"isJudgeOrDelegate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_proposalId","type":"bytes32"}],"name":"isProposalExecuted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"judgeDelegates","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"judgeRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openVotingImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"organizerHackathonCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"organizerHackathons","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"proposalExecuted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"proposalVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_judge","type":"address"}],"name":"proposeAddJudge","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newVotesRequired","type":"uint256"}],"name":"proposeChangeVotesRequired","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_judge","type":"address"}],"name":"proposeRemoveJudge","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pyusd","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"qvWrapperImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"revokeDelegation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalHackathons","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_proposalId","type":"bytes32"},{"internalType":"bool","name":"_support","type":"bool"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"votesRequired","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"zkVotingImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

6101a06040526001600655348015610015575f5ffd5b5060405161bf7238038061bf7283398101604081905261003491610322565b306080526001600160a01b0384811660a0528381166101405282811661016052811661018052604051610066906102d3565b604051809103905ff08015801561007f573d5f5f3e3d5ffd5b506001600160a01b031660c052604051610098906102e0565b604051809103905ff0801580156100b1573d5f5f3e3d5ffd5b506001600160a01b031660e0526040516100ca906102ed565b604051809103905ff0801580156100e3573d5f5f3e3d5ffd5b506001600160a01b0316610100526040516100fd906102fa565b604051809103905ff080158015610116573d5f5f3e3d5ffd5b506001600160a01b03166101205261012d33610136565b50505050610373565b6001600160a01b0381166101915760405162461bcd60e51b815260206004820152601560248201527f496e76616c6964206a756467652061646472657373000000000000000000000060448201526064015b60405180910390fd5b6001600160a01b0381165f9081526020819052604090205460ff16156101f95760405162461bcd60e51b815260206004820152601460248201527f4a7564676520616c7265616479206578697374730000000000000000000000006044820152606401610188565b600154156102495760405162461bcd60e51b815260206004820152601960248201527f4669727374206a7564676520616c7265616479206164646564000000000000006044820152606401610188565b6001600160a01b0381165f81815260208190526040808220805460ff19166001908117909155805480820182559083527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180546001600160a01b03191684179055517f0b221fa4a77e140717a07cc6edef3e54fbb0560c79d71e5fe77b8f7e618d664c9190a250565b6118468061379183390190565b61245780614fd783390190565b6137728061742e83390190565b6113d28061aba083390190565b80516001600160a01b038116811461031d575f5ffd5b919050565b5f5f5f5f60808587031215610335575f5ffd5b61033e85610307565b935061034c60208601610307565b925061035a60408601610307565b915061036860608601610307565b905092959194509250565b60805160a05160c05160e051610100516101205161014051610160516101805161333b6104565f395f818161043d0152818161056a01528181610eb001528181611bc5015281816122c70152818161234d015281816124bf015281816129710152612a5801525f81816104e1015281816105420152611c5301525f818161051d0152818161095201528181611034015261264501525f8181610789015261285c01525f8181610a3501526127c001525f8181610985015261277d01525f8181610678015261273301525f8181610626015261293e01525f6108bd015261333b5ff3fe6080604052600436106102bf575f3560e01c80638b52d56d1161016f578063cbf7719b116100d8578063da7bafce11610092578063e456766b1161006d578063e456766b14610a57578063e79b754714610a85578063ed440f5014610ab9578063fa71039114610ada575f5ffd5b8063da7bafce146109f1578063db5abbd014610a05578063e029983614610a24575f5ffd5b8063cbf7719b146108df578063cc60cf1f1461090a578063cda90b8814610941578063ce762f6414610974578063ce836b21146109a7578063d14b85b9146109c6575f5ffd5b8063a3c2001d11610129578063a3c2001d1461080c578063a4d3180514610821578063a717430c14610835578063aadc3b7214610848578063bff28ea814610881578063c45a0155146108ac575f5ffd5b80638b52d56d146107455780638bc6dae0146107645780638f02fc6f146107785780639dd58b58146107ab5780639f2ce678146107bf578063a074696a146107de575f5ffd5b80633fc8cef31161022b5780635c60da1b116101e55780636b792c4b116101c05780636b792c4b1461069a5780636f9948c7146106b957806373615775146106f85780637fae382714610726575f5ffd5b80635c60da1b146106155780635e2490211461064857806366cab36b14610667575f5ffd5b80633fc8cef3146104d057806340b1187d146105035780634dd218fd1461059957806351dc554b146105b8578063543f5d38146105cb57806355f8cf0d146105e1575f5ffd5b80632244d1cb1161027c5780632244d1cb146103f557806322e669d31461042c578063234c03551461045f5780632c7dc1931461047e57806332bb3ed81461049d5780633f3642dd146104bc575f5ffd5b80630368d346146102c35780630396ec101461030a5780630c2891b71461032b5780630cf79cde14610340578063138051281461037e5780632062b2a7146103a9575b5f5ffd5b3480156102ce575f5ffd5b506102f76102dd366004612bd5565b6001600160a01b03165f9081526002602052604090205490565b6040519081526020015b60405180910390f35b348015610315575f5ffd5b50610329610324366004612bee565b610aee565b005b348015610336575f5ffd5b506102f760065481565b34801561034b575f5ffd5b5061036e61035a366004612c21565b60096020525f908152604090205460ff1681565b6040519015158152602001610301565b348015610389575f5ffd5b506102f7610398366004612bd5565b600a6020525f908152604090205481565b3480156103b4575f5ffd5b506103dd6103c3366004612bd5565b60056020525f90815260409020546001600160a01b031681565b6040516001600160a01b039091168152602001610301565b348015610400575f5ffd5b506103dd61040f366004612bd5565b6001600160a01b039081165f908152600460205260409020541690565b348015610437575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b34801561046a575f5ffd5b506103dd610479366004612c21565b610e3e565b348015610489575f5ffd5b506102f7610498366004612c21565b610e66565b3480156104a8575f5ffd5b506102f76104b7366004612bd5565b6110b7565b3480156104c7575f5ffd5b5061032961128f565b3480156104db575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b34801561050e575f5ffd5b50604080516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811682527f0000000000000000000000000000000000000000000000000000000000000000811660208301527f00000000000000000000000000000000000000000000000000000000000000001691810191909152606001610301565b3480156105a4575f5ffd5b506103296105b3366004612bd5565b611429565b6103dd6105c6366004612d95565b6115d0565b3480156105d6575f5ffd5b506102f762093a8081565b3480156105ec575f5ffd5b506103dd6105fb366004612bd5565b60046020525f90815260409020546001600160a01b031681565b348015610620575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b348015610653575f5ffd5b5061036e610662366004612e90565b6118bc565b348015610672575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b3480156106a5575f5ffd5b506103296106b4366004612c21565b6118e8565b3480156106c4575f5ffd5b506103dd6106d3366004612eba565b600b60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610703575f5ffd5b5061036e610712366004612bd5565b5f6020819052908152604090205460ff1681565b348015610731575f5ffd5b506103dd610740366004612eba565b61197b565b348015610750575f5ffd5b506102f761075f366004612bd5565b6119ff565b34801561076f575f5ffd5b506102f7611bae565b348015610783575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b3480156107b6575f5ffd5b506102f7611c3c565b3480156107ca575f5ffd5b506103296107d9366004612ee2565b611c8a565b3480156107e9575f5ffd5b5061036e6107f8366004612bd5565b60036020525f908152604090205460ff1681565b348015610817575f5ffd5b506102f7600c5481565b34801561082c575f5ffd5b50610329611dd4565b610329610843366004612eba565b611ecf565b348015610853575f5ffd5b5061036e610862366004612e90565b600760209081525f928352604080842090915290825290205460ff1681565b34801561088c575f5ffd5b506102f761089b366004612c21565b5f9081526008602052604090205490565b3480156108b7575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b3480156108ea575f5ffd5b506102f76108f9366004612bd5565b60026020525f908152604090205481565b348015610915575f5ffd5b506103dd610924366004612bd5565b6001600160a01b039081165f908152600560205260409020541690565b34801561094c575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b34801561097f575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b3480156109b2575f5ffd5b506102f76109c1366004612c21565b61201a565b3480156109d1575f5ffd5b506102f76109e0366004612c21565b60086020525f908152604090205481565b3480156109fc575f5ffd5b50600c546102f7565b348015610a10575f5ffd5b5061036e610a1f366004612bd5565b6121e1565b348015610a2f575f5ffd5b506103dd7f000000000000000000000000000000000000000000000000000000000000000081565b348015610a62575f5ffd5b5061036e610a71366004612c21565b5f9081526009602052604090205460ff1690565b348015610a90575f5ffd5b506102f7610a9f366004612bd5565b6001600160a01b03165f908152600a602052604090205490565b348015610ac4575f5ffd5b50610acd612222565b6040516103019190612f46565b348015610ae5575f5ffd5b50610329612282565b335f9081526020819052604090205460ff16610b255760405162461bcd60e51b8152600401610b1c90612f58565b60405180910390fd5b5f8381526009602052604090205460ff1615610b535760405162461bcd60e51b8152600401610b1c90612fa1565b6006545f848152600860205260409020541015610ba75760405162461bcd60e51b8152602060048201526012602482015271496e73756666696369656e7420766f74657360701b6044820152606401610b1c565b5f838152600960205260409020805460ff191660011790556001600160a01b03821615610dc6576001600160a01b0382165f9081526020819052604090205460ff1615610d3a576001600160a01b0382165f908152602081905260408120805460ff191690555b600154811015610d0157826001600160a01b031660018281548110610c3557610c35612fd8565b5f918252602090912001546001600160a01b031603610cf95760018054610c5d908290613000565b81548110610c6d57610c6d612fd8565b5f91825260209091200154600180546001600160a01b039092169183908110610c9857610c98612fd8565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055506001805480610cd457610cd4613013565b5f8281526020902081015f1990810180546001600160a01b0319169055019055610d01565b600101610c0e565b506040516001600160a01b038316907fc19950c209d8ad1fcb5d64f3c545c4ad0782202fcbfc1b564cc1ed120025e659905f90a2610e0f565b6001600160a01b0382165f81815260208190526040808220805460ff19166001908117909155805480820182559083527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180546001600160a01b03191684179055517f0b221fa4a77e140717a07cc6edef3e54fbb0560c79d71e5fe77b8f7e618d664c9190a2610e0f565b8015610e0f57600680549082905560408051828152602081018490527f7f95e655f1cebcc68c938397e57f67e4dacc24fc905d34e073a3b65fbcfbd7fb910160405180910390a1505b60405183907f7b1bcf1ccf901a11589afff5504d59fd0a53780eed2a952adade0348985139e0905f90a2505050565b60018181548110610e4d575f80fd5b5f918252602090912001546001600160a01b0316905081565b604080516101608101825273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815273a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486020808301919091526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116838501525f6060808501829052608080860183905260a080870184905260c080880185905260e0808901869052610100808a01879052610120808b01889052610140808c018990528c519081018d52808601898152948101899052928301889052908201879052810186905290815288518083018a52858152808801869052808a018690528085018690528084018690528188015288518083018a52858152808801869052808a01869052808501869052808401869052818a015288518083018a52858152808801869052808a018690528085018690528084018690528185015288518083018a52858152808801869052808a018690528085018690528084018690528184015288519182018952737f86bf177dd4f3494b841a37e810a34dd56c829b825273383e6b4437b59fff47b619cba855ca29342a85599682019690965280880184905291820183905281018290529451637b1257b360e01b81529094917f00000000000000000000000000000000000000000000000000000000000000001690637b1257b39061106f90869086908a9087906004016130c6565b602060405180830381865afa15801561108a573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110ae91906130f8565b95945050505050565b335f9081526020819052604081205460ff166110e55760405162461bcd60e51b8152600401610b1c90612f58565b6001600160a01b0382165f9081526020819052604090205460ff166111435760405162461bcd60e51b8152602060048201526014602482015273129d5919d948191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610b1c565b336001600160a01b038316036111a55760405162461bcd60e51b815260206004820152602160248201527f43616e6e6f742070726f706f736520746f2072656d6f766520796f757273656c6044820152603360f91b6064820152608401610b1c565b6040516a72656d6f76654a7564676560a81b60208201526bffffffffffffffffffffffff19606084901b16602b82015242603f8201525f90605f0160408051601f1981840301815291815281516020928301205f818152600990935291205490915060ff16156112275760405162461bcd60e51b8152600401610b1c90612fa1565b336001600160a01b0316817f898a80665dd7411c488cdf4a8658fc065411886e2b29e8d29f159de5651f212460405161127f906020808252600b908201526a72656d6f76654a7564676560a81b604082015260600190565b60405180910390a390505b919050565b335f9081526020819052604081205460ff166112c257335f908152600560205260409020546001600160a01b03166112c4565b335b6001600160a01b0381165f908152600260205260409020549091506113215760405162461bcd60e51b81526020600482015260136024820152724e6f207265776172647320746f20636c61696d60681b6044820152606401610b1c565b6001600160a01b0381165f9081526003602052604090205460ff16156113895760405162461bcd60e51b815260206004820152601760248201527f416c726561647920636c61696d656420726577617264730000000000000000006044820152606401610b1c565b6001600160a01b0381165f908152600260209081526040808320546003909252808320805460ff19166001179055519091339183156108fc0291849190818181858888f193505050501580156113e1573d5f5f3e3d5ffd5b50816001600160a01b03167f2b8d51946813c957a7b1714546806778b063466901c289b82d967a22caed6e028260405161141d91815260200190565b60405180910390a25050565b335f9081526020819052604090205460ff166114575760405162461bcd60e51b8152600401610b1c90612f58565b6001600160a01b0381166114ad5760405162461bcd60e51b815260206004820152601860248201527f496e76616c69642064656c6567617465206164647265737300000000000000006044820152606401610b1c565b336001600160a01b038216036115055760405162461bcd60e51b815260206004820152601b60248201527f43616e6e6f742064656c656761746520746f20796f757273656c6600000000006044820152606401610b1c565b335f908152600460205260409020546001600160a01b0316156115635760405162461bcd60e51b8152602060048201526016602482015275416c72656164792068617320612064656c656761746560501b6044820152606401610b1c565b335f81815260046020908152604080832080546001600160a01b0387166001600160a01b03199182168117909255818552600590935281842080549093168517909255519092917f0c195f050c9d1eae4d0a59bee2ef525b8a5d91975fa9342a54bfd6528ac8fb6f91a350565b5f4288116116205760405162461bcd60e51b815260206004820181905260248201527f53746172742074696d65206d75737420626520696e20746865206675747572656044820152606401610b1c565b8787116116795760405162461bcd60e51b815260206004820152602160248201527f456e642074696d65206d7573742062652061667465722073746172742074696d6044820152606560f81b6064820152608401610b1c565b5f805b85518110156116b45785818151811061169757611697612fd8565b6020026020010151826116aa919061310f565b915060010161167c565b505f6116bf82612377565b90505f6116cf826298968061241b565b90508281101561173a5760405162461bcd60e51b815260206004820152603060248201527f496e73756666696369656e7420505955534420616d6f756e7420666f7220707260448201526f34bd32903234b9ba3934b13aba34b7b760811b6064820152608401610b1c565b5f5b86518110156117cc5761176787828151811061175a5761175a612fd8565b60200260200101516121e1565b6117c45760405162461bcd60e51b815260206004820152602860248201527f53656c6563746564206a75646765206973206e6f7420696e20676c6f62616c20604482015267726567697374727960c01b6064820152608401610b1c565b60010161173c565b506117d78587612714565b506117e98c8c8c8c8c8c8c888a612938565b335f818152600a602081815260408084208054600b8452828620818752845291852080546001600160a01b0319166001600160a01b038916179055948452919052825493975092919061183b83613122565b9091555050600c8054905f61184f83613122565b9190505550336001600160a01b0316856001600160a01b03167f526b2cf7f5bb87f9a4c01a6eb8c01bf90405b9726286908ac1dfd93944da0e848f858a5f01518b602001516040516118a4949392919061316e565b60405180910390a35050505098975050505050505050565b5f8281526007602090815260408083206001600160a01b038516845290915290205460ff165b92915050565b335f9081526020819052604090205460ff166119165760405162461bcd60e51b8152600401610b1c90612f58565b604051339082156108fc029083905f818181858888f19350505050158015611940573d5f5f3e3d5ffd5b506040518181525f9033907f9495d03190a79a43e534c9e328ff322f6283261383f5f19c809564f6ad5a57b39060200160405180910390a350565b6001600160a01b0382165f908152600a602052604081205482106119d75760405162461bcd60e51b8152602060048201526013602482015272496e646578206f7574206f6620626f756e647360681b6044820152606401610b1c565b506001600160a01b039182165f908152600b6020908152604080832093835292905220541690565b335f9081526020819052604081205460ff16611a2d5760405162461bcd60e51b8152600401610b1c90612f58565b6001600160a01b038216611a7b5760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b6044820152606401610b1c565b6001600160a01b0382165f9081526020819052604090205460ff1615611ada5760405162461bcd60e51b81526020600482015260146024820152734a7564676520616c72656164792065786973747360601b6044820152606401610b1c565b604051676164644a7564676560c01b60208201526bffffffffffffffffffffffff19606084901b16602882015242603c8201525f90605c0160408051601f1981840301815291815281516020928301205f818152600990935291205490915060ff1615611b595760405162461bcd60e51b8152600401610b1c90612fa1565b336001600160a01b0316817f898a80665dd7411c488cdf4a8658fc065411886e2b29e8d29f159de5651f212460405161127f906020808252600890820152676164644a7564676560c01b604082015260600190565b6040516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a08231906024015b602060405180830381865afa158015611c13573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c3791906130f8565b905090565b6040516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401611bf8565b335f9081526020819052604090205460ff16611cb85760405162461bcd60e51b8152600401610b1c90612f58565b5f82815260076020908152604080832033845290915290205460ff1615611d215760405162461bcd60e51b815260206004820152601e60248201527f416c726561647920766f746564206f6e20746869732070726f706f73616c00006044820152606401610b1c565b5f8281526009602052604090205460ff1615611d4f5760405162461bcd60e51b8152600401610b1c90612fa1565b5f8281526007602090815260408083203384529091529020805460ff191660011790558015611d97575f828152600860205260408120805491611d9183613122565b91905055505b6040518115158152339083907f8b40665146691327ee30f5bf56e9b2d6f445d2830d3b09b56385cd30f630ecfb9060200160405180910390a35050565b335f9081526020819052604090205460ff16611e025760405162461bcd60e51b8152600401610b1c90612f58565b335f908152600460205260409020546001600160a01b0316611e665760405162461bcd60e51b815260206004820152601760248201527f4e6f2064656c65676174696f6e20746f207265766f6b650000000000000000006044820152606401610b1c565b335f81815260046020908152604080832080546001600160a01b03198082169092556001600160a01b0316808552600590935281842080549091169055519092907f0c195f050c9d1eae4d0a59bee2ef525b8a5d91975fa9342a54bfd6528ac8fb6f908390a350565b6001600160a01b038216611f1d5760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b6044820152606401610b1c565b5f8111611f6c5760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610b1c565b80341015611fb35760405162461bcd60e51b8152602060048201526014602482015273125b9cdd59999a58da595b9d081c185e5b595b9d60621b6044820152606401610b1c565b6001600160a01b0382165f9081526002602052604081208054839290611fda90849061310f565b90915550506040518181526001600160a01b038316907fd545e22a252a1e46a3ff5d635804684897b3b9831ac8163a9bc6c54ac88e53739060200161141d565b335f9081526020819052604081205460ff166120485760405162461bcd60e51b8152600401610b1c90612f58565b5f82116120a55760405162461bcd60e51b815260206004820152602560248201527f566f746573207265717569726564206d75737420626520677265617465722074604482015264068616e20360dc1b6064820152608401610b1c565b6001548211156121095760405162461bcd60e51b815260206004820152602960248201527f566f7465732072657175697265642063616e6e6f742065786365656420746f74604482015268616c206a756467657360b81b6064820152608401610b1c565b6040517218da185b99d9559bdd195cd4995c5d5a5c9959606a1b6020820152603381018390524260538201525f9060730160408051601f1981840301815291815281516020928301205f818152600990935291205490915060ff16156121815760405162461bcd60e51b8152600401610b1c90612fa1565b336001600160a01b0316817f898a80665dd7411c488cdf4a8658fc065411886e2b29e8d29f159de5651f212460405161127f9060208082526013908201527218da185b99d9559bdd195cd4995c5d5a5c9959606a1b604082015260600190565b6001600160a01b0381165f9081526020819052604081205460ff16806118e25750506001600160a01b039081165f9081526005602052604090205416151590565b6060600180548060200260200160405190810160405280929190818152602001828054801561227857602002820191905f5260205f20905b81546001600160a01b0316815260019091019060200180831161225a575b5050505050905090565b335f9081526020819052604090205460ff166122b05760405162461bcd60e51b8152600401610b1c90612f58565b6040516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015612314573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061233891906130f8565b90508015612374576123746001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633836129bf565b50565b604051632c7dc19360e01b8152670de0b6b3a764000060048201525f9081903090632c7dc19390602401602060405180830381865afa1580156123bc573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123e091906130f8565b90506123ed81606461319a565b6123ff84670de0b6b3a764000061319a565b61240a90606961319a565b61241491906131b1565b9392505050565b5f5f83116124755760405162461bcd60e51b815260206004820152602160248201527f45544820616d6f756e74206d7573742062652067726561746572207468616e206044820152600360fc1b6064820152608401610b1c565b604080516101608101825273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815273a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486020808301919091526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116838501525f6060808501829052608080860183905260a080870184905260c080880185905260e0808901869052610100808a01879052610120808b01889052610140808c018990528c519081018d52808601898152948101899052928301889052908201879052810186905290815288518083018a52858152808801869052808a018690528085018690528084018690528188015288518083018a52858152808801869052808a01869052808501869052808401869052818a015288518083018a52858152808801869052808a018690528085018690528084018690528185015288518083018a52858152808801869052808a018690528085018690528084018690528184015288519182018952737f86bf177dd4f3494b841a37e810a34dd56c829b825273383e6b4437b59fff47b619cba855ca29342a8559968201969096528088018490529182018390528101919091529351632e4e0c7160e11b8152929391927f000000000000000000000000000000000000000000000000000000000000000090911690635c9c18e2908890612686908790879084908c9089906004016131d0565b60206040518083038185885af11580156126a2573d5f5f3e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906126c791906130f8565b60408051888152602081018390529081018790529094507f12aec4492341cce30775891eceb584f6fbbd825e56d94db93c1888839677903c9060600160405180910390a150505092915050565b5f80835160038111156127295761272961313a565b0361275e576127577f0000000000000000000000000000000000000000000000000000000000000000612a16565b90506127e7565b6001835160038111156127735761277361313a565b036127a1576127577f0000000000000000000000000000000000000000000000000000000000000000612a16565b6002835160038111156127b6576127b661313a565b036127e7576127e47f0000000000000000000000000000000000000000000000000000000000000000612a16565b90505b806001600160a01b0316637c33404f84604001518560600151856040518463ffffffff1660e01b815260040161281f93929190613213565b5f604051808303815f87803b158015612836575f5ffd5b505af1158015612848573d5f5f3e3d5ffd5b505050508260200151156128ea575f6128807f0000000000000000000000000000000000000000000000000000000000000000612a16565b604080860151905163cd6dc68760e01b81526001600160a01b038581166004830152602482019290925291925082169063cd6dc687906044015f604051808303815f87803b1580156128d0575f5ffd5b505af11580156128e2573d5f5f3e3d5ffd5b509293505050505b825160208401516040516001600160a01b038416927f5c968cbcc62446275183fea8d6eaa29a00505b77145a7fad4de724257508a33c9261292a92613231565b60405180910390a292915050565b5f6129627f0000000000000000000000000000000000000000000000000000000000000000612a16565b90506129986001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001682856129bf565b6129b2818b8b8b8b8b8b8b6129ad8b34613000565b612a21565b9998505050505050505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052612a11908490612ab9565b505050565b5f6118e2825f612b2b565b60405162a8bae760e31b81526001600160a01b038a1690630545d738908390612a809033908d908d908d908d908d908d9030908e907f00000000000000000000000000000000000000000000000000000000000000009060040161324e565b5f604051808303818588803b158015612a97575f5ffd5b505af1158015612aa9573d5f5f3e3d5ffd5b5050505050505050505050505050565b5f5f60205f8451602086015f885af180612ad8576040513d5f823e3d81fd5b50505f513d91508115612aef578060011415612afc565b6001600160a01b0384163b155b15612b2557604051635274afe760e01b81526001600160a01b0385166004820152602401610b1c565b50505050565b5f81471015612b565760405163cf47918160e01b815247600482015260248101839052604401610b1c565b763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c175f526e5af43d82803e903d91602b57fd5bf38360781b176020526037600983f090506001600160a01b0381166118e25760405163b06ebf3d60e01b815260040160405180910390fd5b80356001600160a01b038116811461128a575f5ffd5b5f60208284031215612be5575f5ffd5b61241482612bbf565b5f5f5f60608486031215612c00575f5ffd5b83359250612c1060208501612bbf565b929592945050506040919091013590565b5f60208284031215612c31575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612c7557612c75612c38565b604052919050565b5f67ffffffffffffffff821115612c9657612c96612c38565b5060051b60200190565b5f82601f830112612caf575f5ffd5b8135612cc2612cbd82612c7d565b612c4c565b8082825260208201915060208360051b860101925085831115612ce3575f5ffd5b602085015b83811015612d0757612cf981612bbf565b835260209283019201612ce8565b5095945050505050565b8035801515811461128a575f5ffd5b5f60808284031215612d30575f5ffd5b6040516080810167ffffffffffffffff81118282101715612d5357612d53612c38565b604052905080823560048110612d67575f5ffd5b8152612d7560208401612d11565b602082015260408381013590820152606092830135920191909152919050565b5f5f5f5f5f5f5f5f610160898b031215612dad575f5ffd5b883597506020890135965060408901359550606089013594506080890135935060a089013567ffffffffffffffff811115612de6575f5ffd5b8901601f81018b13612df6575f5ffd5b8035612e04612cbd82612c7d565b8082825260208201915060208360051b85010192508d831115612e25575f5ffd5b6020840193505b82841015612e47578335825260209384019390910190612e2c565b955050505060c089013567ffffffffffffffff811115612e65575f5ffd5b612e718b828c01612ca0565b925050612e818a60e08b01612d20565b90509295985092959890939650565b5f5f60408385031215612ea1575f5ffd5b82359150612eb160208401612bbf565b90509250929050565b5f5f60408385031215612ecb575f5ffd5b612ed483612bbf565b946020939093013593505050565b5f5f60408385031215612ef3575f5ffd5b82359150612eb160208401612d11565b5f8151808452602084019350602083015f5b82811015612f3c5781516001600160a01b0316865260209586019590910190600101612f15565b5093949350505050565b602081525f6124146020830184612f03565b60208082526029908201527f4f6e6c7920676c6f62616c206a75646765732063616e2063616c6c207468697360408201526810333ab731ba34b7b760b91b606082015260800190565b60208082526019908201527f50726f706f73616c20616c726561647920657865637574656400000000000000604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b818103818111156118e2576118e2612fec565b634e487b7160e01b5f52603160045260245ffd5b805f5b600b811015612b255781516001600160a01b031684526020938401939091019060010161302a565b805f5b6005811015612b25578151845f5b6005811015613082578251825260209283019290910190600101613063565b50505060a0939093019260209190910190600101613055565b805f5b6005811015612b255781516001600160a01b031684526020938401939091019060010161309e565b61054081016130d58287613027565b6130e3610160830186613052565b836104808301526110ae6104a083018461309b565b5f60208284031215613108575f5ffd5b5051919050565b808201808211156118e2576118e2612fec565b5f6001820161313357613133612fec565b5060010190565b634e487b7160e01b5f52602160045260245ffd5b6004811061316a57634e487b7160e01b5f52602160045260245ffd5b9052565b8481526020810184905260808101613189604083018561314e565b821515606083015295945050505050565b80820281158282048414176118e2576118e2612fec565b5f826131cb57634e487b7160e01b5f52601260045260245ffd5b500490565b61056081016131df8288613027565b6131ed610160830187613052565b84610480830152836104a08301526132096104c083018461309b565b9695505050505050565b838152826020820152606060408201525f6110ae6060830184612f03565b6040810161323f828561314e565b82151560208301529392505050565b5f610140820160018060a01b038d1683528b60208401528a60408401528960608401528860808401528760a084015261014060c0840152808751808352610160850191506020890192505f5b818110156132b857835183526020938401939092019160010161329a565b50506001600160a01b03871660e08501528381036101008501526132dc8187612f03565b925050506132f66101208301846001600160a01b03169052565b9b9a505050505050505050505056fea2646970667358221220f25571b24249f41926de9dbd3aa3581a313f63ac8b7eb8cd47de662ec25a43fd64736f6c634300081c00336080604052348015600e575f5ffd5b5061182a8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610153575f3560e01c8063842cb6de116100bf578063b06b036811610079578063b06b036814610367578063cde8e5d31461037a578063df15c37e14610383578063ed1224e514610398578063ed896d66146103ab578063edfbf7b6146103cd575f5ffd5b8063842cb6de146102d4578063927cab47146102fc5780639d9ca28d1461030f578063a2fb117514610339578063a46c36371461034c578063ae2ac4ae14610354575f5ffd5b8063522c90fa11610110578063522c90fa146102385780635ccbc7781461026357806376804a031461028b57806377e5c241146102ae5780637c33404f146102b75780637dc449c1146102cc575f5ffd5b8063036ee850146101575780630655fad61461017357806309eef43e146101925780630fac9100146101c45780633a7a53b2146101e35780634bd1c4921461020e575b5f5ffd5b61016060025481565b6040519081526020015b60405180910390f35b610160610181366004611547565b60096020525f908152604090205481565b6101b46101a0366004611547565b60046020525f908152604090205460ff1681565b604051901515815260200161016a565b6101606101d2366004611547565b60076020525f908152604090205481565b6101b46101f1366004611547565b6001600160a01b03165f9081526004602052604090205460ff1690565b61016061021c366004611567565b600860209081525f928352604080842090915290825290205481565b61024b610246366004611598565b6103e0565b6040516001600160a01b03909116815260200161016a565b610160610271366004611547565b6001600160a01b03165f9081526007602052604090205490565b61029361046d565b6040805193845260208401929092529082015260600161016a565b61016060065481565b6102ca6102c53660046115f7565b61059b565b005b6101605f5481565b6101606102e2366004611547565b6001600160a01b03165f9081526009602052604090205490565b6102ca61030a366004611547565b610630565b6101b461031d366004611547565b6001600160a01b03165f90815260096020526040902054151590565b61024b610347366004611598565b6106e9565b600a54610160565b6102ca610362366004611547565b610711565b61024b610375366004611598565b6107ea565b61016060015481565b61038b6107f9565b60405161016a9190611646565b6102ca6103a6366004611691565b610859565b6101b46103b9366004611547565b60036020525f908152604090205460ff1681565b6102ca6103db366004611598565b610d13565b5f5f821180156103f25750600a548211155b6104365760405162461bcd60e51b815260206004820152601060248201526f24b73b30b634b2103837b9b4ba34b7b760811b60448201526064015b60405180910390fd5b600a6104436001846116f9565b8154811061045357610453611712565b5f918252602090912001546001600160a01b031692915050565b6006545f808080805b6005548110156104d85760045f6005838154811061049657610496611712565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff16156104d057826104cc81611726565b9350505b600101610476565b5090925082905f5b600554811015610590575f5b600554811015610587575f60085f6005858154811061050d5761050d611712565b5f9182526020808320909101546001600160a01b031683528201929092526040018120600580549192918590811061054757610547611712565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054111561057f578261057b81611726565b9350505b6001016104ec565b506001016104e0565b508092505050909192565b5f54156105e05760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604482015260640161042d565b5f84815560018490555b818110156106295761062183838381811061060757610607611712565b905060200201602081019061061c9190611547565b610d5f565b6001016105ea565b5050505050565b6001600160a01b03811661067e5760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b604482015260640161042d565b6001600160a01b0381165f9081526003602052604090205460ff16156106dd5760405162461bcd60e51b81526020600482015260146024820152734a7564676520616c72656164792065786973747360601b604482015260640161042d565b6106e681610d5f565b50565b600a81815481106106f8575f80fd5b5f918252602090912001546001600160a01b0316905081565b6001600160a01b0381165f9081526003602052604090205460ff1661076f5760405162461bcd60e51b8152602060048201526014602482015273129d5919d948191bd95cc81b9bdd08195e1a5cdd60621b604482015260640161042d565b6001600160a01b0381165f9081526004602052604090205460ff16156107e15760405162461bcd60e51b815260206004820152602160248201527f43616e6e6f742072656d6f7665206a756467652077686f2068617320766f74656044820152601960fa1b606482015260840161042d565b6106e681610e0c565b600581815481106106f8575f80fd5b6060600a80548060200260200160405190810160405280929190818152602001828054801561084f57602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311610831575b5050505050905090565b335f9081526003602052604090205460ff166108c35760405162461bcd60e51b815260206004820152602360248201527f4f6e6c79206a75646765732063616e20706572666f726d20746869732061637460448201526234b7b760e91b606482015260840161042d565b6002544211156109155760405162461bcd60e51b815260206004820152601a60248201527f566f74696e6720646561646c696e652068617320706173736564000000000000604482015260640161042d565b82811461095d5760405162461bcd60e51b8152602060048201526016602482015275082e4e4c2f2e640d8cadccee8d040dad2e6dac2e8c6d60531b604482015260640161042d565b826109b95760405162461bcd60e51b815260206004820152602660248201527f4d75737420766f746520666f72206174206c65617374206f6e6520706172746960448201526518da5c185b9d60d21b606482015260840161042d565b335f9081526004602052604090205460ff1615610a185760405162461bcd60e51b815260206004820152601760248201527f4a756467652068617320616c726561647920766f746564000000000000000000604482015260640161042d565b5f805b84811015610c63575f848483818110610a3657610a36611712565b9050602002013511610a8a5760405162461bcd60e51b815260206004820152601d60248201527f506f696e7473206d7573742062652067726561746572207468616e2030000000604482015260640161042d565b838382818110610a9c57610a9c611712565b9050602002013582610aae919061173e565b91505f60075f888885818110610ac657610ac6611712565b9050602002016020810190610adb9190611547565b6001600160a01b03166001600160a01b031681526020019081526020015f20549050848483818110610b0f57610b0f611712565b9050602002013560075f898986818110610b2b57610b2b611712565b9050602002016020810190610b409190611547565b6001600160a01b03166001600160a01b031681526020019081526020015f205f828254610b6d919061173e565b909155508590508483818110610b8557610b85611712565b335f90815260086020908152604082209202939093013592909150898986818110610bb257610bb2611712565b9050602002016020810190610bc79190611547565b6001600160a01b0316815260208101919091526040015f2055610c5a878784818110610bf557610bf5611712565b9050602002016020810190610c0a9190611547565b8260075f8b8b88818110610c2057610c20611712565b9050602002016020810190610c359190611547565b6001600160a01b03166001600160a01b031681526020019081526020015f2054610f6b565b50600101610a1b565b505f54811115610cb55760405162461bcd60e51b815260206004820152601e60248201527f4578636565647320706f696e747320706572206a75646765206c696d69740000604482015260640161042d565b335f8181526004602052604090819020805460ff19166001179055517f67d24c36e0629b147f8ecca5be58020627f9ca82bfacf3b9156fff8fe364c51d90610d04908890889088908890611751565b60405180910390a25050505050565b60025415610d5a5760405162461bcd60e51b8152602060048201526014602482015273111958591b1a5b9948185b1c9958591e481cd95d60621b604482015260640161042d565b600255565b6001600160a01b0381165f818152600360205260408120805460ff1916600190811790915560058054918201815582527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00180546001600160a01b0319169092179091556006805491610dd183611726565b90915550506040516001600160a01b038216907fe7f15f9ffff540ae14fdebcd9b6d6cc1fc143933338656c99caa595e8b809039905f90a250565b6001600160a01b0381165f908152600360205260408120805460ff191690555b600554811015610f2057816001600160a01b031660058281548110610e5357610e53611712565b5f918252602090912001546001600160a01b031603610f185760058054610e7c906001906116f9565b81548110610e8c57610e8c611712565b5f91825260209091200154600580546001600160a01b039092169183908110610eb757610eb7611712565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055506005805480610ef357610ef36117cb565b5f8281526020902081015f1990810180546001600160a01b0319169055019055610f20565b600101610e2c565b5060068054905f610f30836117df565b90915550506040516001600160a01b038216907f32a792a099c63670df7161917c5efb2fdc734cf9f943ebddccbecef87664f2b7905f90a250565b6001600160a01b0383165f908152600960205260409020548015610f9a57610f94848484610fed565b50505050565b5f82118015610fdf5750600154600a541080610fdf575060075f610fbc61102e565b6001600160a01b03166001600160a01b031681526020019081526020015f205482115b15610f9457610f9484611077565b6001600160a01b0383165f908152600960205260409020548282111561101c576110178482611125565b610f94565b82821015610f9457610f9484826111c7565b600a545f90810361103e57505f90565b600a805461104e906001906116f9565b8154811061105e5761105e611712565b5f918252602090912001546001600160a01b0316919050565b600154600a5410156110e957600a80546001810182557fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80180546001600160a01b0319166001600160a01b03841690811790915590545f918252600960205260409091208190556106e6908290611125565b5f6110f261102e565b6001600160a01b0381165f90815260096020526040902054909150611117828261126a565b61112083611077565b505050565b805f6111326001836116f9565b90505b80156111b45760075f600a61114b6001856116f9565b8154811061115b5761115b611712565b5f9182526020808320909101546001600160a01b0390811684528382019490945260409283018220549388168252600790522054111561119d578091506111a2565b6111b4565b806111ac816117df565b915050611135565b5081811461112057611120838383611395565b80805b600a548110156112435760075f600a83815481106111ea576111ea611712565b5f9182526020808320909101546001600160a01b039081168452838201949094526040928301822054938816825260079052205410156112365761122f81600161173e565b915061123b565b611243565b6001016111ca565b5060015481111561125857611120838361126a565b81811461112057611120838383611395565b600a5481101561133657600a80545f9190611287906001906116f9565b8154811061129757611297611712565b5f918252602090912001546001600160a01b0316905080600a6112bb6001856116f9565b815481106112cb576112cb611712565b5f91825260208083209190910180546001600160a01b0319166001600160a01b039485161790559183168152600990915260409020829055600a805480611314576113146117cb565b5f8281526020902081015f1990810180546001600160a01b0319169055019055505b6001600160a01b0382165f81815260096020526040808220829055517f845de5d493589e5b262d7a53cad5ae62bd69b6cac33cd64b86eab5d1c90194a29161138991859190918252602082015260400190565b60405180910390a25050565b808214611120576001600160a01b0383165f90815260096020819052604082208390558391600a6113c76001866116f9565b815481106113d7576113d7611712565b5f9182526020808320909101546001600160a01b03168352820192909252604001812091909155600a61140b6001856116f9565b8154811061141b5761141b611712565b5f918252602090912001546001600160a01b03169050600a61143e6001846116f9565b8154811061144e5761144e611712565b5f918252602090912001546001600160a01b0316600a61146f6001866116f9565b8154811061147f5761147f611712565b5f91825260209091200180546001600160a01b0319166001600160a01b039290921691909117905580600a6114b56001856116f9565b815481106114c5576114c5611712565b5f9182526020918290200180546001600160a01b0319166001600160a01b0393841617905560408051868152918201859052918616917f845de5d493589e5b262d7a53cad5ae62bd69b6cac33cd64b86eab5d1c90194a2910160405180910390a250505050565b80356001600160a01b0381168114611542575f5ffd5b919050565b5f60208284031215611557575f5ffd5b6115608261152c565b9392505050565b5f5f60408385031215611578575f5ffd5b6115818361152c565b915061158f6020840161152c565b90509250929050565b5f602082840312156115a8575f5ffd5b5035919050565b5f5f83601f8401126115bf575f5ffd5b50813567ffffffffffffffff8111156115d6575f5ffd5b6020830191508360208260051b85010111156115f0575f5ffd5b9250929050565b5f5f5f5f6060858703121561160a575f5ffd5b8435935060208501359250604085013567ffffffffffffffff81111561162e575f5ffd5b61163a878288016115af565b95989497509550505050565b602080825282518282018190525f918401906040840190835b818110156116865783516001600160a01b031683526020938401939092019160010161165f565b509095945050505050565b5f5f5f5f604085870312156116a4575f5ffd5b843567ffffffffffffffff8111156116ba575f5ffd5b6116c6878288016115af565b909550935050602085013567ffffffffffffffff81111561162e575f5ffd5b634e487b7160e01b5f52601160045260245ffd5b8181038181111561170c5761170c6116e5565b92915050565b634e487b7160e01b5f52603260045260245ffd5b5f60018201611737576117376116e5565b5060010190565b8082018082111561170c5761170c6116e5565b604080825281018490525f8560608301825b87811015611791576001600160a01b0361177c8461152c565b16825260209283019290910190600101611763565b5083810360208501528481526001600160fb1b038511156117b0575f5ffd5b8460051b915081866020830137016020019695505050505050565b634e487b7160e01b5f52603160045260245ffd5b5f816117ed576117ed6116e5565b505f19019056fea26469706673582212208caa46ac82bbc19fa3a8d491d91e5b480fc081077e9765b424cc76bcb1e6debb64736f6c634300081c00336080604052348015600e575f5ffd5b5061243b8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610208575f3560e01c8063927cab471161011f578063c8b94787116100a9578063e8fcf72311610079578063e8fcf72314610534578063ed1224e514610553578063ed896d6614610566578063edfbf7b614610588578063f9ba1a641461059b575f5ffd5b8063c8b94787146104f0578063cde8e5d314610503578063dee9c8511461050c578063df15c37e1461051f575f5ffd5b8063accf1bd6116100ef578063accf1bd61461045e578063ae2ac4ae14610486578063b06b036814610499578063b9a53013146104ac578063c6dbe15a146104ce575f5ffd5b8063927cab47146104065780639d9ca28d14610419578063a2fb117514610443578063a46c363714610456575f5ffd5b80634bd1c492116101a057806376804a031161017057806376804a031461039757806377e5c241146103ba5780637c33404f146103c35780637dc449c1146103d6578063842cb6de146103de575f5ffd5b80634bd1c4921461031f578063522c90fa146103495780635ccbc7781461035c5780636b8e3ad614610384575f5ffd5b80630fac9100116101db5780630fac91001461029757806314a1e1c8146102b65780631a8fb550146102e15780633a7a53b2146102f4575f5ffd5b8063036ee8501461020c5780630655fad6146102285780630a744f57146102475780630a88effc14610282575b5f5ffd5b61021560025481565b6040519081526020015b60405180910390f35b610215610236366004611ed9565b600e6020525f908152604090205481565b610272610255366004611ed9565b6001600160a01b03165f9081526004602052604090205460ff1690565b604051901515815260200161021f565b610295610290366004611ef9565b6105ba565b005b6102156102a5366004611ed9565b600c6020525f908152604090205481565b6102c96102c4366004611f10565b610602565b6040516001600160a01b03909116815260200161021f565b6102156102ef366004611f80565b610636565b610272610302366004611ed9565b6001600160a01b03165f9081526005602052604090205460ff1690565b61021561032d366004612008565b600d60209081525f928352604080842090915290825290205481565b6102c9610357366004611ef9565b610674565b61021561036a366004611ed9565b6001600160a01b03165f908152600c602052604090205490565b610295610392366004612039565b6106fc565b61039f61097a565b6040805193845260208401929092529082015260600161021f565b61021560075481565b6102956103d13660046120ac565b6109ee565b6102155f5481565b6102156103ec366004611ed9565b6001600160a01b03165f908152600e602052604090205490565b610295610414366004611ed9565b610a83565b610272610427366004611ed9565b6001600160a01b03165f908152600e6020526040902054151590565b6102c9610451366004611ef9565b610b3c565b600f54610215565b61021561046c366004611ed9565b6001600160a01b03165f9081526008602052604090205490565b610295610494366004611ed9565b610b64565b6102c96104a7366004611ef9565b610c41565b6102726104ba366004611ed9565b60056020525f908152604090205460ff1681565b6102726104dc366004611ed9565b60046020525f908152604090205460ff1681565b6102156104fe366004611f10565b610c50565b61021560015481565b61029561051a366004612039565b610c7b565b61052761111d565b60405161021f91906120fb565b610215610542366004611ed9565b60086020525f908152604090205481565b610295610561366004612146565b61117d565b610272610574366004611ed9565b60036020525f908152604090205460ff1681565b610295610596366004611ef9565b6115f7565b6102156105a9366004611ed9565b600b6020525f908152604090205481565b335f9081526003602052604090205460ff166105f15760405162461bcd60e51b81526004016105e89061219a565b60405180910390fd5b335f908152600b6020526040902055565b6009602052815f5260405f20818154811061061b575f80fd5b5f918252602090912001546001600160a01b03169150829050565b5f868686868686604051602001610652969594939291906121dd565b6040516020818303038152906040528051906020012090509695505050505050565b5f5f821180156106865750600f548211155b6106c55760405162461bcd60e51b815260206004820152601060248201526f24b73b30b634b2103837b9b4ba34b7b760811b60448201526064016105e8565b600f6106d260018461226c565b815481106106e2576106e2612285565b5f918252602090912001546001600160a01b031692915050565b335f9081526003602052604090205460ff1661072a5760405162461bcd60e51b81526004016105e89061219a565b60025442111561077c5760405162461bcd60e51b815260206004820152601a60248201527f566f74696e6720646561646c696e65206861732070617373656400000000000060448201526064016105e8565b83821461079b5760405162461bcd60e51b81526004016105e890612299565b836107f75760405162461bcd60e51b815260206004820152602660248201527f4d75737420766f746520666f72206174206c65617374206f6e6520706172746960448201526518da5c185b9d60d21b60648201526084016105e8565b335f9081526004602052604090205460ff16156108565760405162461bcd60e51b815260206004820152601b60248201527f4a756467652068617320616c726561647920636f6d6d6974746564000000000060448201526064016105e8565b5f805b858110156108c6575f85858381811061087457610874612285565b90506020020135116108985760405162461bcd60e51b81526004016105e8906122c9565b8484828181106108aa576108aa612285565b90506020020135826108bc9190612300565b9150600101610859565b505f548111156108e85760405162461bcd60e51b81526004016105e890612313565b5f338787878787604051602001610904969594939291906121dd565b60408051601f198184030181528282528051602091820120335f818152600884528481208390556004845293909320805460ff19166001179055808452935090917fb7434d5379c2cc69394db30fab188026da2b3c7c3a087002c5c0ca51d4d1494191015b60405180910390a250505050505050565b6007545f8080805b6006548110156109e45760055f600683815481106109a2576109a2612285565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff16156109dc57816109d88161234a565b9250505b600101610982565b5092935f92509050565b5f5415610a335760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481a5b9a5d1a585b1a5e9959606a1b60448201526064016105e8565b5f84815560018490555b81811015610a7c57610a74838383818110610a5a57610a5a612285565b9050602002016020810190610a6f9190611ed9565b611643565b600101610a3d565b5050505050565b6001600160a01b038116610ad15760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b60448201526064016105e8565b6001600160a01b0381165f9081526003602052604090205460ff1615610b305760405162461bcd60e51b81526020600482015260146024820152734a7564676520616c72656164792065786973747360601b60448201526064016105e8565b610b3981611643565b50565b600f8181548110610b4b575f80fd5b5f918252602090912001546001600160a01b0316905081565b6001600160a01b0381165f9081526003602052604090205460ff16610bc25760405162461bcd60e51b8152602060048201526014602482015273129d5919d948191bd95cc81b9bdd08195e1a5cdd60621b60448201526064016105e8565b6001600160a01b0381165f9081526004602052604090205460ff1615610c385760405162461bcd60e51b815260206004820152602560248201527f43616e6e6f742072656d6f7665206a756467652077686f2068617320636f6d6d6044820152641a5d1d195960da1b60648201526084016105e8565b610b39816116f0565b60068181548110610b4b575f80fd5b600a602052815f5260405f208181548110610c69575f80fd5b905f5260205f20015f91509150505481565b335f9081526003602052604090205460ff16610ca95760405162461bcd60e51b81526004016105e89061219a565b6002544211610cf25760405162461bcd60e51b81526020600482015260156024820152745374696c6c20696e20636f6d6d697420706861736560581b60448201526064016105e8565b335f9081526004602052604090205460ff16610d4a5760405162461bcd60e51b8152602060048201526017602482015276135d5cdd0818dbdb5b5a5d081d9bdd195cc8199a5c9cdd604a1b60448201526064016105e8565b335f9081526005602052604090205460ff1615610da95760405162461bcd60e51b815260206004820152601a60248201527f4a756467652068617320616c72656164792072657665616c656400000000000060448201526064016105e8565b838214610dc85760405162461bcd60e51b81526004016105e890612299565b5f338686868686604051602001610de4969594939291906121dd565b60408051601f198184030181529181528151602092830120335f90815260089093529120549091508114610e505760405162461bcd60e51b8152602060048201526013602482015272086dedadad2e8dacadce840dad2e6dac2e8c6d606b1b60448201526064016105e8565b5f805b8681101561106b575f868683818110610e6e57610e6e612285565b9050602002013511610e925760405162461bcd60e51b81526004016105e8906122c9565b858582818110610ea457610ea4612285565b9050602002013582610eb69190612300565b91505f600c5f8a8a85818110610ece57610ece612285565b9050602002016020810190610ee39190611ed9565b6001600160a01b03166001600160a01b031681526020019081526020015f20549050868683818110610f1757610f17612285565b90506020020135600c5f8b8b86818110610f3357610f33612285565b9050602002016020810190610f489190611ed9565b6001600160a01b03166001600160a01b031681526020019081526020015f205f828254610f759190612300565b909155508790508683818110610f8d57610f8d612285565b335f908152600d60209081526040822092029390930135929091508b8b86818110610fba57610fba612285565b9050602002016020810190610fcf9190611ed9565b6001600160a01b0316815260208101919091526040015f2055611062898984818110610ffd57610ffd612285565b90506020020160208101906110129190611ed9565b82600c5f8d8d8881811061102857611028612285565b905060200201602081019061103d9190611ed9565b6001600160a01b03166001600160a01b031681526020019081526020015f205461184f565b50600101610e53565b505f5481111561108d5760405162461bcd60e51b81526004016105e890612313565b335f9081526009602052604090206110a6908888611e10565b50335f908152600a602052604090206110c0908686611e71565b50335f818152600b60209081526040808320879055600590915290819020805460ff19166001179055517fcf576a730e3c24bf60d013ce5190e6143f46ea37d574ea083a0abe84a7d134f590610969908a908a908a908a90612362565b6060600f80548060200260200160405190810160405280929190818152602001828054801561117357602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311611155575b5050505050905090565b335f9081526003602052604090205460ff166111ab5760405162461bcd60e51b81526004016105e89061219a565b60025442116111f45760405162461bcd60e51b81526020600482015260156024820152745374696c6c20696e20636f6d6d697420706861736560581b60448201526064016105e8565b335f9081526004602052604090205460ff1661124c5760405162461bcd60e51b8152602060048201526017602482015276135d5cdd0818dbdb5b5a5d081d9bdd195cc8199a5c9cdd604a1b60448201526064016105e8565b335f9081526005602052604090205460ff16156112ab5760405162461bcd60e51b815260206004820152601a60248201527f4a756467652068617320616c72656164792072657665616c656400000000000060448201526064016105e8565b8281146112ca5760405162461bcd60e51b81526004016105e890612299565b335f818152600b6020908152604080832054905192936112f5939092899289928992899291016121dd565b60408051601f198184030181529181528151602092830120335f908152600890935291205490915081146113615760405162461bcd60e51b8152602060048201526013602482015272086dedadad2e8dacadce840dad2e6dac2e8c6d606b1b60448201526064016105e8565b5f805b85811015611542575f85858381811061137f5761137f612285565b90506020020135116113a35760405162461bcd60e51b81526004016105e8906122c9565b8484828181106113b5576113b5612285565b90506020020135826113c79190612300565b91505f600c5f8989858181106113df576113df612285565b90506020020160208101906113f49190611ed9565b6001600160a01b03166001600160a01b031681526020019081526020015f2054905085858381811061142857611428612285565b90506020020135600c5f8a8a8681811061144457611444612285565b90506020020160208101906114599190611ed9565b6001600160a01b03166001600160a01b031681526020019081526020015f205f8282546114869190612300565b90915550869050858381811061149e5761149e612285565b335f908152600d60209081526040822092029390930135929091508a8a868181106114cb576114cb612285565b90506020020160208101906114e09190611ed9565b6001600160a01b0316815260208101919091526040015f205561153988888481811061150e5761150e612285565b90506020020160208101906115239190611ed9565b82600c5f8c8c8881811061102857611028612285565b50600101611364565b505f548111156115645760405162461bcd60e51b81526004016105e890612313565b335f90815260096020526040902061157d908787611e10565b50335f908152600a60205260409020611597908585611e71565b50335f8181526005602052604090819020805460ff19166001179055517fcf576a730e3c24bf60d013ce5190e6143f46ea37d574ea083a0abe84a7d134f5906115e7908990899089908990612362565b60405180910390a2505050505050565b6002541561163e5760405162461bcd60e51b8152602060048201526014602482015273111958591b1a5b9948185b1c9958591e481cd95d60621b60448201526064016105e8565b600255565b6001600160a01b0381165f818152600360205260408120805460ff1916600190811790915560068054918201815582527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f0180546001600160a01b03191690921790915560078054916116b58361234a565b90915550506040516001600160a01b038216907fe7f15f9ffff540ae14fdebcd9b6d6cc1fc143933338656c99caa595e8b809039905f90a250565b6001600160a01b0381165f908152600360205260408120805460ff191690555b60065481101561180457816001600160a01b03166006828154811061173757611737612285565b5f918252602090912001546001600160a01b0316036117fc57600680546117609060019061226c565b8154811061177057611770612285565b5f91825260209091200154600680546001600160a01b03909216918390811061179b5761179b612285565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060068054806117d7576117d76123dc565b5f8281526020902081015f1990810180546001600160a01b0319169055019055611804565b600101611710565b5060078054905f611814836123f0565b90915550506040516001600160a01b038216907f32a792a099c63670df7161917c5efb2fdc734cf9f943ebddccbecef87664f2b7905f90a250565b6001600160a01b0383165f908152600e6020526040902054801561187e576118788484846118d1565b50505050565b5f821180156118c35750600154600f5410806118c35750600c5f6118a0611912565b6001600160a01b03166001600160a01b031681526020019081526020015f205482115b15611878576118788461195b565b6001600160a01b0383165f908152600e602052604090205482821115611900576118fb8482611a09565b611878565b82821015611878576118788482611aab565b600f545f90810361192257505f90565b600f80546119329060019061226c565b8154811061194257611942612285565b5f918252602090912001546001600160a01b0316919050565b600154600f5410156119cd57600f80546001810182557f8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8020180546001600160a01b0319166001600160a01b03841690811790915590545f918252600e6020526040909120819055610b39908290611a09565b5f6119d6611912565b6001600160a01b0381165f908152600e60205260409020549091506119fb8282611b4e565b611a048361195b565b505050565b805f611a1660018361226c565b90505b8015611a9857600c5f600f611a2f60018561226c565b81548110611a3f57611a3f612285565b5f9182526020808320909101546001600160a01b0390811684528382019490945260409283018220549388168252600c905220541115611a8157809150611a86565b611a98565b80611a90816123f0565b915050611a19565b50818114611a0457611a04838383611c79565b80805b600f54811015611b2757600c5f600f8381548110611ace57611ace612285565b5f9182526020808320909101546001600160a01b0390811684528382019490945260409283018220549388168252600c905220541015611b1a57611b13816001612300565b9150611b1f565b611b27565b600101611aae565b50600154811115611b3c57611a048383611b4e565b818114611a0457611a04838383611c79565b600f54811015611c1a57600f80545f9190611b6b9060019061226c565b81548110611b7b57611b7b612285565b5f918252602090912001546001600160a01b0316905080600f611b9f60018561226c565b81548110611baf57611baf612285565b5f91825260208083209190910180546001600160a01b0319166001600160a01b039485161790559183168152600e90915260409020829055600f805480611bf857611bf86123dc565b5f8281526020902081015f1990810180546001600160a01b0319169055019055505b6001600160a01b0382165f818152600e6020526040808220829055517f845de5d493589e5b262d7a53cad5ae62bd69b6cac33cd64b86eab5d1c90194a291611c6d91859190918252602082015260400190565b60405180910390a25050565b808214611a04576001600160a01b0383165f908152600e6020819052604082208390558391600f611cab60018661226c565b81548110611cbb57611cbb612285565b5f9182526020808320909101546001600160a01b03168352820192909252604001812091909155600f611cef60018561226c565b81548110611cff57611cff612285565b5f918252602090912001546001600160a01b03169050600f611d2260018461226c565b81548110611d3257611d32612285565b5f918252602090912001546001600160a01b0316600f611d5360018661226c565b81548110611d6357611d63612285565b5f91825260209091200180546001600160a01b0319166001600160a01b039290921691909117905580600f611d9960018561226c565b81548110611da957611da9612285565b5f9182526020918290200180546001600160a01b0319166001600160a01b0393841617905560408051868152918201859052918616917f845de5d493589e5b262d7a53cad5ae62bd69b6cac33cd64b86eab5d1c90194a2910160405180910390a250505050565b828054828255905f5260205f20908101928215611e61579160200282015b82811115611e615781546001600160a01b0319166001600160a01b03843516178255602090920191600190910190611e2e565b50611e6d929150611eaa565b5090565b828054828255905f5260205f20908101928215611e61579160200282015b82811115611e61578235825591602001919060010190611e8f565b5b80821115611e6d575f8155600101611eab565b80356001600160a01b0381168114611ed4575f5ffd5b919050565b5f60208284031215611ee9575f5ffd5b611ef282611ebe565b9392505050565b5f60208284031215611f09575f5ffd5b5035919050565b5f5f60408385031215611f21575f5ffd5b611f2a83611ebe565b946020939093013593505050565b5f5f83601f840112611f48575f5ffd5b50813567ffffffffffffffff811115611f5f575f5ffd5b6020830191508360208260051b8501011115611f79575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215611f95575f5ffd5b611f9e87611ebe565b9550602087013567ffffffffffffffff811115611fb9575f5ffd5b611fc589828a01611f38565b909650945050604087013567ffffffffffffffff811115611fe4575f5ffd5b611ff089828a01611f38565b979a9699509497949695606090950135949350505050565b5f5f60408385031215612019575f5ffd5b61202283611ebe565b915061203060208401611ebe565b90509250929050565b5f5f5f5f5f6060868803121561204d575f5ffd5b853567ffffffffffffffff811115612063575f5ffd5b61206f88828901611f38565b909650945050602086013567ffffffffffffffff81111561208e575f5ffd5b61209a88828901611f38565b96999598509660400135949350505050565b5f5f5f5f606085870312156120bf575f5ffd5b8435935060208501359250604085013567ffffffffffffffff8111156120e3575f5ffd5b6120ef87828801611f38565b95989497509550505050565b602080825282518282018190525f918401906040840190835b8181101561213b5783516001600160a01b0316835260209384019390920191600101612114565b509095945050505050565b5f5f5f5f60408587031215612159575f5ffd5b843567ffffffffffffffff81111561216f575f5ffd5b61217b87828801611f38565b909550935050602085013567ffffffffffffffff8111156120e3575f5ffd5b60208082526023908201527f4f6e6c79206a75646765732063616e20706572666f726d20746869732061637460408201526234b7b760e91b606082015260800190565b6bffffffffffffffffffffffff198760601b1681525f60148201875f5b88811015612229576001600160a01b0361221383611ebe565b16835260209283019291909101906001016121fa565b50506001600160fb1b0385111561223e575f5ffd5b8460051b8087833701928352505060200195945050505050565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561227f5761227f612258565b92915050565b634e487b7160e01b5f52603260045260245ffd5b602080825260169082015275082e4e4c2f2e640d8cadccee8d040dad2e6dac2e8c6d60531b604082015260600190565b6020808252601d908201527f506f696e7473206d7573742062652067726561746572207468616e2030000000604082015260600190565b8082018082111561227f5761227f612258565b6020808252601e908201527f4578636565647320706f696e747320706572206a75646765206c696d69740000604082015260600190565b5f6001820161235b5761235b612258565b5060010190565b604080825281018490525f8560608301825b878110156123a2576001600160a01b0361238d84611ebe565b16825260209283019290910190600101612374565b5083810360208501528481526001600160fb1b038511156123c1575f5ffd5b8460051b915081866020830137016020019695505050505050565b634e487b7160e01b5f52603160045260245ffd5b5f816123fe576123fe612258565b505f19019056fea2646970667358221220cbe1ab33c0fb21f875b7a519672dc58b3aaac0c3af440eacea66fea67c6c8dac64736f6c634300081c00336080604052348015600e575f5ffd5b506137568061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061021e575f3560e01c80637fa417b31161012a578063b06b0368116100b4578063df15c37e11610079578063df15c37e14610597578063ed1224e5146105ac578063ed896d66146105bf578063edfbf7b6146105e1578063f9ba1a64146105f4575f5ffd5b8063b06b036814610524578063b9a5301314610537578063c6dbe15a14610559578063c8b947871461057b578063cde8e5d31461058e575f5ffd5b8063a2fb1175116100fa578063a2fb1175146104af578063a46c3637146104c2578063accf1bd6146104ca578063ae2ac4ae146104f2578063aedf1b4714610505575f5ffd5b80637fa417b314610437578063842cb6de1461044a578063927cab47146104725780639d9ca28d14610485575f5ffd5b80633a7a53b2116101ab5780636b8e3ad61161017b5780636b8e3ad6146103dd57806376804a03146103f057806377e5c241146104135780637c33404f1461041c5780637dc449c11461042f575f5ffd5b80633a7a53b21461034d5780634bd1c49214610378578063522c90fa146103a25780635ccbc778146103b5575f5ffd5b80630b4023cb116101f15780630b4023cb146102ba5780630fac9100146102cd57806314a1e1c8146102ec57806314bfa3da1461031757806338c043991461032c575f5ffd5b8063036ee850146102225780630655fad61461023e57806309eef43e1461025d5780630a744f571461028f575b5f5ffd5b61022b60025481565b6040519081526020015b60405180910390f35b61022b61024c366004611dd6565b60106020525f908152604090205481565b61027f61026b366004611dd6565b60046020525f908152604090205460ff1681565b6040519015158152602001610235565b61027f61029d366004611dd6565b6001600160a01b03165f9081526009602052604090205460ff1690565b61022b6102c8366004611e3e565b610613565b61022b6102db366004611dd6565b600e6020525f908152604090205481565b6102ff6102fa366004611ec6565b610652565b6040516001600160a01b039091168152602001610235565b61032a610325366004611eee565b610686565b005b61033f61033a366004611dd6565b610be8565b604051610235929190612008565b61027f61035b366004611dd6565b6001600160a01b03165f908152600d602052604090205460ff1690565b61022b610386366004612060565b600f60209081525f928352604080842090915290825290205481565b6102ff6103b0366004612091565b610d23565b61022b6103c3366004611dd6565b6001600160a01b03165f908152600e602052604090205490565b61032a6103eb3660046120a8565b610dab565b6103f86110b1565b60408051938452602084019290925290820152606001610235565b61022b60065481565b61032a61042a36600461211b565b611129565b61022b5f5481565b6007546102ff906001600160a01b031681565b61022b610458366004611dd6565b6001600160a01b03165f9081526010602052604090205490565b61032a610480366004611dd6565b611205565b61027f610493366004611dd6565b6001600160a01b03165f90815260106020526040902054151590565b6102ff6104bd366004612091565b6112be565b60115461022b565b61022b6104d8366004611dd6565b6001600160a01b03165f9081526008602052604090205490565b61032a610500366004611dd6565b6112e6565b61022b610513366004611dd6565b60086020525f908152604090205481565b6102ff610532366004612091565b6113c3565b61027f610545366004611dd6565b600d6020525f908152604090205460ff1681565b61027f610567366004611dd6565b60096020525f908152604090205460ff1681565b61022b610589366004611ec6565b6113d2565b61022b60015481565b61059f6113fd565b604051610235919061216a565b61032a6105ba36600461217c565b61145d565b61027f6105cd366004611dd6565b60036020525f908152604090205460ff1681565b61032a6105ef366004612091565b6114cb565b61022b610602366004611dd6565b600c6020525f908152604090205481565b5f86868686868660405160200161062f969594939291906121d0565b6040516020818303038152906040528051906020012090505b9695505050505050565b600a602052815f5260405f20818154811061066b575f80fd5b5f918252602090912001546001600160a01b03169150829050565b335f9081526003602052604090205460ff166106bd5760405162461bcd60e51b81526004016106b49061224b565b60405180910390fd5b335f9081526009602052604090205460ff1661071b5760405162461bcd60e51b815260206004820152601760248201527f4d75737420636f6d6d697420766f74657320666972737400000000000000000060448201526064016106b4565b335f908152600d602052604090205460ff161561077a5760405162461bcd60e51b815260206004820152601a60248201527f4a756467652068617320616c72656164792072657665616c656400000000000060448201526064016106b4565b8584146107c25760405162461bcd60e51b8152602060048201526016602482015275082e4e4c2f2e640d8cadccee8d040dad2e6dac2e8c6d60531b60448201526064016106b4565b5f3388888888886040516020016107de969594939291906121d0565b60408051601f198184030181529181528151602092830120335f9081526008909352912054909150811461084a5760405162461bcd60e51b8152602060048201526013602482015272086dedadad2e8dacadce840dad2e6dac2e8c6d606b1b60448201526064016106b4565b61085883838a8a8a8a611517565b6108a45760405162461bcd60e51b815260206004820152601f60248201527f496e76616c6964205a4b2070726f6f6620666f7220626174636820766f74650060448201526064016106b4565b5f805b88811015610aef575f8888838181106108c2576108c261228e565b90506020020135116109165760405162461bcd60e51b815260206004820152601d60248201527f506f696e7473206d7573742062652067726561746572207468616e203000000060448201526064016106b4565b8787828181106109285761092861228e565b905060200201358261093a91906122b6565b91505f600e5f8c8c858181106109525761095261228e565b90506020020160208101906109679190611dd6565b6001600160a01b03166001600160a01b031681526020019081526020015f2054905088888381811061099b5761099b61228e565b90506020020135600e5f8d8d868181106109b7576109b761228e565b90506020020160208101906109cc9190611dd6565b6001600160a01b03166001600160a01b031681526020019081526020015f205f8282546109f991906122b6565b909155508990508883818110610a1157610a1161228e565b335f908152600f60209081526040822092029390930135929091508d8d86818110610a3e57610a3e61228e565b9050602002016020810190610a539190611dd6565b6001600160a01b0316815260208101919091526040015f2055610ae68b8b84818110610a8157610a8161228e565b9050602002016020810190610a969190611dd6565b82600e5f8f8f88818110610aac57610aac61228e565b9050602002016020810190610ac19190611dd6565b6001600160a01b03166001600160a01b031681526020019081526020015f20546115ae565b506001016108a7565b505f54811115610b415760405162461bcd60e51b815260206004820152601e60248201527f4578636565647320706f696e747320706572206a75646765206c696d6974000060448201526064016106b4565b335f908152600a60205260409020610b5a908a8a611d00565b50335f908152600b60205260409020610b74908888611d61565b50335f818152600c60209081526040808320899055600d90915290819020805460ff19166001179055517f9238d665aa500bce2a9a2999db6f7e95e135c81a991b6c5f3ed08e35adee0dc490610bd5908c908c908c908c908b908b906122f7565b60405180910390a2505050505050505050565b6001600160a01b0381165f908152600d6020526040902054606090819060ff16610c545760405162461bcd60e51b815260206004820152601c60248201527f4a7564676520686173206e6f742072657665616c656420766f7465730000000060448201526064016106b4565b6001600160a01b0383165f908152600a60209081526040808320600b83529281902083548251818502810185019093528083529092849190830182828015610cc357602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311610ca5575b5050505050915080805480602002602001604051908101604052809291908181526020018280548015610d1357602002820191905f5260205f20905b815481526020019060010190808311610cff575b5050505050905091509150915091565b5f5f82118015610d3557506011548211155b610d745760405162461bcd60e51b815260206004820152601060248201526f24b73b30b634b2103837b9b4ba34b7b760811b60448201526064016106b4565b6011610d8160018461238b565b81548110610d9157610d9161228e565b5f918252602090912001546001600160a01b031692915050565b335f9081526003602052604090205460ff16610dd95760405162461bcd60e51b81526004016106b49061224b565b600254421115610e2b5760405162461bcd60e51b815260206004820152601a60248201527f566f74696e6720646561646c696e65206861732070617373656400000000000060448201526064016106b4565b838214610e735760405162461bcd60e51b8152602060048201526016602482015275082e4e4c2f2e640d8cadccee8d040dad2e6dac2e8c6d60531b60448201526064016106b4565b83610ecf5760405162461bcd60e51b815260206004820152602660248201527f4d75737420766f746520666f72206174206c65617374206f6e6520706172746960448201526518da5c185b9d60d21b60648201526084016106b4565b335f9081526009602052604090205460ff1615610f2e5760405162461bcd60e51b815260206004820152601b60248201527f4a756467652068617320616c726561647920636f6d6d6974746564000000000060448201526064016106b4565b5f805b85811015610fce575f858583818110610f4c57610f4c61228e565b9050602002013511610fa05760405162461bcd60e51b815260206004820152601d60248201527f506f696e7473206d7573742062652067726561746572207468616e203000000060448201526064016106b4565b848482818110610fb257610fb261228e565b9050602002013582610fc491906122b6565b9150600101610f31565b505f548111156110205760405162461bcd60e51b815260206004820152601e60248201527f4578636565647320706f696e747320706572206a75646765206c696d6974000060448201526064016106b4565b5f33878787878760405160200161103c969594939291906121d0565b60408051601f198184030181528282528051602091820120335f818152600884528481208390556009845293909320805460ff19166001179055808452935090917f3caa01a3e66450d1ef355c5d8ca94ed78d63d3b40df4f0518633636aa650bd21910160405180910390a250505050505050565b6006545f8080805b60055481101561111b57600d5f600583815481106110d9576110d961228e565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff1615611113578161110f8161239e565b9250505b6001016110b9565b506011549394909392509050565b5f541561116e5760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481a5b9a5d1a585b1a5e9959606a1b60448201526064016106b4565b5f849055600183905560405161118390611d9a565b604051809103905ff08015801561119c573d5f5f3e3d5ffd5b50600780546001600160a01b0319166001600160a01b03929092169190911790555f5b818110156111fe576111f68383838181106111dc576111dc61228e565b90506020020160208101906111f19190611dd6565b611630565b6001016111bf565b5050505050565b6001600160a01b0381166112535760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b60448201526064016106b4565b6001600160a01b0381165f9081526003602052604090205460ff16156112b25760405162461bcd60e51b81526020600482015260146024820152734a7564676520616c72656164792065786973747360601b60448201526064016106b4565b6112bb81611630565b50565b601181815481106112cd575f80fd5b5f918252602090912001546001600160a01b0316905081565b6001600160a01b0381165f9081526003602052604090205460ff166113445760405162461bcd60e51b8152602060048201526014602482015273129d5919d948191bd95cc81b9bdd08195e1a5cdd60621b60448201526064016106b4565b6001600160a01b0381165f9081526009602052604090205460ff16156113ba5760405162461bcd60e51b815260206004820152602560248201527f43616e6e6f742072656d6f7665206a756467652077686f2068617320636f6d6d6044820152641a5d1d195960da1b60648201526084016106b4565b6112bb816116dd565b600581815481106112cd575f80fd5b600b602052815f5260405f2081815481106113eb575f80fd5b905f5260205f20015f91509150505481565b6060601180548060200260200160405190810160405280929190818152602001828054801561145357602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311611435575b5050505050905090565b60405162461bcd60e51b815260206004820152603760248201527f55736520636f6d6d69744261746368566f74657320616e642072657665616c4260448201527f61746368566f74657320666f72205a4b20766f74696e6700000000000000000060648201526084016106b4565b600254156115125760405162461bcd60e51b8152602060048201526014602482015273111958591b1a5b9948185b1c9958591e481cd95d60621b60448201526064016106b4565b600255565b5f80805b83811015611551578484828181106115355761153561228e565b905060200201358261154791906122b6565b915060010161151b565b505f54811115611564575f915050610648565b5f5b8381101561159f578484828181106115805761158061228e565b905060200201355f03611597575f92505050610648565b600101611566565b50505093151595945050505050565b6001600160a01b0383165f9081526010602052604090205480156115dd576115d784848461183c565b50505050565b5f82118015611622575060015460115410806116225750600e5f6115ff61187d565b6001600160a01b03166001600160a01b031681526020019081526020015f205482115b156115d7576115d7846118c6565b6001600160a01b0381165f818152600360205260408120805460ff1916600190811790915560058054918201815582527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00180546001600160a01b03191690921790915560068054916116a28361239e565b90915550506040516001600160a01b038216907fe7f15f9ffff540ae14fdebcd9b6d6cc1fc143933338656c99caa595e8b809039905f90a250565b6001600160a01b0381165f908152600360205260408120805460ff191690555b6005548110156117f157816001600160a01b0316600582815481106117245761172461228e565b5f918252602090912001546001600160a01b0316036117e9576005805461174d9060019061238b565b8154811061175d5761175d61228e565b5f91825260209091200154600580546001600160a01b0390921691839081106117885761178861228e565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060058054806117c4576117c46123b6565b5f8281526020902081015f1990810180546001600160a01b03191690550190556117f1565b6001016116fd565b5060068054905f611801836123ca565b90915550506040516001600160a01b038216907f32a792a099c63670df7161917c5efb2fdc734cf9f943ebddccbecef87664f2b7905f90a250565b6001600160a01b0383165f908152601060205260409020548282111561186b576118668482611974565b6115d7565b828210156115d7576115d78482611a16565b6011545f90810361188d57505f90565b6011805461189d9060019061238b565b815481106118ad576118ad61228e565b5f918252602090912001546001600160a01b0316919050565b600154601154101561193857601180546001810182557f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680180546001600160a01b0319166001600160a01b03841690811790915590545f918252601060205260409091208190556112bb908290611974565b5f61194161187d565b6001600160a01b0381165f908152601060205260409020549091506119668282611ab9565b61196f836118c6565b505050565b805f61198160018361238b565b90505b8015611a0357600e5f601161199a60018561238b565b815481106119aa576119aa61228e565b5f9182526020808320909101546001600160a01b0390811684528382019490945260409283018220549388168252600e9052205411156119ec578091506119f1565b611a03565b806119fb816123ca565b915050611984565b5081811461196f5761196f838383611b9f565b80805b601154811015611a9257600e5f60118381548110611a3957611a3961228e565b5f9182526020808320909101546001600160a01b0390811684528382019490945260409283018220549388168252600e905220541015611a8557611a7e8160016122b6565b9150611a8a565b611a92565b600101611a19565b50600154811115611aa75761196f8383611ab9565b81811461196f5761196f838383611b9f565b601154811015611b8557601180545f9190611ad69060019061238b565b81548110611ae657611ae661228e565b5f918252602090912001546001600160a01b03169050806011611b0a60018561238b565b81548110611b1a57611b1a61228e565b5f91825260208083209190910180546001600160a01b0319166001600160a01b0394851617905591831681526010909152604090208290556011805480611b6357611b636123b6565b5f8281526020902081015f1990810180546001600160a01b0319169055019055505b506001600160a01b03165f90815260106020526040812055565b80821461196f576001600160a01b0383165f908152601060208190526040822083905583916011611bd160018661238b565b81548110611be157611be161228e565b5f9182526020808320909101546001600160a01b031683528201929092526040018120919091556011611c1560018561238b565b81548110611c2557611c2561228e565b5f918252602090912001546001600160a01b031690506011611c4860018461238b565b81548110611c5857611c5861228e565b5f918252602090912001546001600160a01b03166011611c7960018661238b565b81548110611c8957611c8961228e565b5f91825260209091200180546001600160a01b0319166001600160a01b0392909216919091179055806011611cbf60018561238b565b81548110611ccf57611ccf61228e565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555050505050565b828054828255905f5260205f20908101928215611d51579160200282015b82811115611d515781546001600160a01b0319166001600160a01b03843516178255602090920191600190910190611d1e565b50611d5d929150611da7565b5090565b828054828255905f5260205f20908101928215611d51579160200282015b82811115611d51578235825591602001919060010190611d7f565b611341806123e083390190565b5b80821115611d5d575f8155600101611da8565b80356001600160a01b0381168114611dd1575f5ffd5b919050565b5f60208284031215611de6575f5ffd5b611def82611dbb565b9392505050565b5f5f83601f840112611e06575f5ffd5b50813567ffffffffffffffff811115611e1d575f5ffd5b6020830191508360208260051b8501011115611e37575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215611e53575f5ffd5b611e5c87611dbb565b9550602087013567ffffffffffffffff811115611e77575f5ffd5b611e8389828a01611df6565b909650945050604087013567ffffffffffffffff811115611ea2575f5ffd5b611eae89828a01611df6565b979a9699509497949695606090950135949350505050565b5f5f60408385031215611ed7575f5ffd5b611ee083611dbb565b946020939093013593505050565b5f5f5f5f5f5f5f6080888a031215611f04575f5ffd5b873567ffffffffffffffff811115611f1a575f5ffd5b611f268a828b01611df6565b909850965050602088013567ffffffffffffffff811115611f45575f5ffd5b611f518a828b01611df6565b90965094505060408801359250606088013567ffffffffffffffff811115611f77575f5ffd5b8801601f81018a13611f87575f5ffd5b803567ffffffffffffffff811115611f9d575f5ffd5b8a6020828401011115611fae575f5ffd5b602082019350809250505092959891949750929550565b5f8151808452602084019350602083015f5b82811015611ffe5781516001600160a01b0316865260209586019590910190600101611fd7565b5093949350505050565b604081525f61201a6040830185611fc5565b82810360208401528084518083526020830191506020860192505f5b81811015612054578351835260209384019390920191600101612036565b50909695505050505050565b5f5f60408385031215612071575f5ffd5b61207a83611dbb565b915061208860208401611dbb565b90509250929050565b5f602082840312156120a1575f5ffd5b5035919050565b5f5f5f5f5f606086880312156120bc575f5ffd5b853567ffffffffffffffff8111156120d2575f5ffd5b6120de88828901611df6565b909650945050602086013567ffffffffffffffff8111156120fd575f5ffd5b61210988828901611df6565b96999598509660400135949350505050565b5f5f5f5f6060858703121561212e575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115612152575f5ffd5b61215e87828801611df6565b95989497509550505050565b602081525f611def6020830184611fc5565b5f5f5f5f6040858703121561218f575f5ffd5b843567ffffffffffffffff8111156121a5575f5ffd5b6121b187828801611df6565b909550935050602085013567ffffffffffffffff811115612152575f5ffd5b6bffffffffffffffffffffffff198760601b1681525f60148201875f5b8881101561221c576001600160a01b0361220683611dbb565b16835260209283019291909101906001016121ed565b50506001600160fb1b03851115612231575f5ffd5b8460051b8087833701928352505060200195945050505050565b60208082526023908201527f4f6e6c79206a75646765732063616e20706572666f726d20746869732061637460408201526234b7b760e91b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b808201808211156122c9576122c96122a2565b92915050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b606080825281018690525f8760808301825b89811015612337576001600160a01b0361232284611dbb565b16825260209283019290910190600101612309565b5083810360208501528681526001600160fb1b03871115612356575f5ffd5b8660051b915081886020830137018281036020908101604085015261237e90820185876122cf565b9998505050505050505050565b818103818111156122c9576122c96122a2565b5f600182016123af576123af6122a2565b5060010190565b634e487b7160e01b5f52603160045260245ffd5b5f816123d8576123d86122a2565b505f19019056fe608060405234801561000f575f5ffd5b50338061003557604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61003e8161004c565b5061004761009b565b6102c9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080518082019091525f80825260208201526100bc90600190600261022b565b50604080518082019091525f80825260208201526100de90600390600261022b565b50604080518082019091525f808252602082015261010090600590600261022b565b50604080518082019091525f808252602082015261012290600790600261022b565b505f6009819055600a81905560408051808201909152818152602081019190915261015190600b90600261022b565b50604080518082019091525f808252602082015261017390600d90600261022b565b5060408051600480825260a082019092529060208201608080368337505081516101a492600f925060200190610269565b5060408051600480825260a082019092529060208201608080368337505081516101d5926010925060200190610269565b505f5b6004811015610228575f600f82815481106101f5576101f56102b5565b905f5260205f2001819055505f60108281548110610215576102156102b5565b5f918252602090912001556001016101d8565b50565b8260028101928215610259579160200282015b8281111561025957825182559160200191906001019061023e565b506102659291506102a1565b5090565b828054828255905f5260205f20908101928215610259579160200282018281111561025957825182559160200191906001019061023e565b5b80821115610265575f81556001016102a2565b634e487b7160e01b5f52603260045260245ffd5b61106b806102d65f395ff3fe608060405234801561000f575f5ffd5b5060043610610127575f3560e01c806376f1ddb0116100a95780639933986f1161006e5780639933986f1461023e578063a3a6ed2714610251578063dfc4cd4e14610264578063edeb764514610282578063f2fde38b14610295575f5ffd5b806376f1ddb0146101e4578063788723af146101eb578063788dfe91146101fe5780637d69eed9146102115780638da5cb5b14610224575f5ffd5b806325f87d23116100ef57806325f87d231461019357806331acc54d1461019b578063521790fa146101be578063588f357d146101d1578063715018a6146101da575f5ffd5b806301e92ceb1461012b578063055baf44146101475780630c1db8191461015a578063159bd5c41461016d57806318d1632b14610180575b5f5ffd5b61013460095481565b6040519081526020015b60405180910390f35b610134610155366004610b11565b6102a8565b610134610168366004610b11565b6102be565b61013461017b366004610b11565b6102dd565b61013461018e366004610b11565b6102ec565b610134606481565b6101ae6101a9366004610d50565b6102fb565b604051901515815260200161013e565b6101346101cc366004610b11565b610508565b610134600a5481565b6101e2610517565b005b6101345f81565b6101346101f9366004610b11565b61052a565b6101ae61020c366004610dc8565b610539565b6101ae61021f366004610dea565b6105d3565b5f546040516001600160a01b03909116815260200161013e565b6101e261024c366004610e2b565b6105f1565b61013461025f366004610b11565b6106b3565b61026c6106c2565b60405161013e9a99989796959493929190610f71565b610134610290366004610b11565b6108f7565b6101e26102a3366004611008565b610906565b600581600281106102b7575f80fd5b0154905081565b601081815481106102cd575f80fd5b5f91825260209091200154905081565b600181600281106102b7575f80fd5b600381600281106102b7575f80fd5b5f5f6103078787610943565b905080610317575f9150506104ff565b60038651101561036e5760405162461bcd60e51b815260206004820152601d60248201527f496e76616c6964207075626c6963207369676e616c73206c656e67746800000060448201526064015b60405180910390fd5b828660018151811061038257610382611021565b6020026020010151146103c95760405162461bcd60e51b815260206004820152600f60248201526e0a0ded2dce8e640dad2e6dac2e8c6d608b1b6044820152606401610365565b60648311156104115760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420706f696e74732072616e676560601b6044820152606401610365565b6040516bffffffffffffffffffffffff19606087901b1660208201525f90603401604051602081830303815290604052805190602001205f1c9050808760028151811061046057610460611021565b6020026020010151146104ab5760405162461bcd60e51b8152602060048201526013602482015272094eac8ceca40d0c2e6d040dad2e6dac2e8c6d606b1b6044820152606401610365565b846001600160a01b0316866001600160a01b03167f164ad8fe51ccca8895bfc9ff121e38344eb66e2037b060dee3addb1f8ca87a2e866040516104f091815260200190565b60405180910390a36001925050505b95945050505050565b600b81600281106102b7575f80fd5b61051f6109ee565b6105285f610a1a565b565b600f81815481106102cd575f80fd5b8051515f90158061054d5750815160200151155b1561055957505f919050565b6020820151515115806105725750602082810151510151155b1561057e57505f919050565b602082810151015151158061059b57506020828101518101510151155b156105a757505f919050565b60408201515115806105bf5750604082015160200151155b156105cb57505f919050565b506001919050565b5f60648211156105e457505f6105e9565b508315155b949350505050565b6105f96109ee565b61060660018b6002610a69565b5061061460038a6002610a69565b506106226005896002610a69565b506106306007886002610a69565b506009869055600a859055610648600b856002610a69565b50610656600d846002610a69565b50815161066a90600f906020850190610aa7565b50805161067e906010906020840190610aa7565b506040517f700ad29a50ef4fb344099eee3ea1747a3082e61404dc86e753b7fde79277e806905f90a150505050505050505050565b600d81600281106102b7575f80fd5b6106ca610adf565b6106d2610adf565b6106da610adf565b6106e2610adf565b5f5f6106ec610adf565b6106f4610adf565b6060806001600360056007600954600a54600b600d600f601089600280602002604051908101604052809291908260028015610745576020028201915b815481526020019060010190808311610731575b50506040805180820191829052949e508d935060029250905082845b8154815260200190600101908083116107615750506040805180820191829052949d508c935060029250905082845b8154815260200190600101908083116107905750506040805180820191829052949c508b935060029250905082845b8154815260200190600101908083116107bf5750506040805180820191829052949b5088935060029250905082845b8154815260200190600101908083116107ee575050604080518082019182905294985087935060029250905082845b81548152602001906001019080831161081d57505050505092508180548060200260200160405190810160405280929190818152602001828054801561088057602002820191905f5260205f20905b81548152602001906001019080831161086c575b50505050509150808054806020026020016040519081016040528092919081815260200182805480156108d057602002820191905f5260205f20905b8154815260200190600101908083116108bc575b50505050509050995099509950995099509950995099509950995090919293949596979899565b600781600281106102b7575f80fd5b61090e6109ee565b6001600160a01b03811661093757604051631e4fbdf760e01b81525f6004820152602401610365565b61094081610a1a565b50565b8151515f9015806109575750825160200151155b1561096357505f6109e8565b60208301515151158061097c5750602083810151510151155b1561098857505f6109e8565b60208381015101515115806109a557506020838101518101510151155b156109b157505f6109e8565b60408301515115806109c95750604083015160200151155b156109d557505f6109e8565b81515f036109e457505f6109e8565b5060015b92915050565b5f546001600160a01b031633146105285760405163118cdaa760e01b8152336004820152602401610365565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b8260028101928215610a97579160200282015b82811115610a97578251825591602001919060010190610a7c565b50610aa3929150610afd565b5090565b828054828255905f5260205f20908101928215610a975791602002820182811115610a97578251825591602001919060010190610a7c565b60405180604001604052806002906020820280368337509192915050565b5b80821115610aa3575f8155600101610afe565b5f60208284031215610b21575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff81118282101715610b5f57610b5f610b28565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715610b8e57610b8e610b28565b604052919050565b5f60408284031215610ba6575f5ffd5b610bae610b3c565b823581526020928301359281019290925250919050565b5f82601f830112610bd4575f5ffd5b610bdc610b3c565b806040840185811115610bed575f5ffd5b845b81811015610c07578035845260209384019301610bef565b509095945050505050565b5f818303610100811215610c24575f5ffd5b6040516060810167ffffffffffffffff81118282101715610c4757610c47610b28565b604052915081610c578585610b96565b81526080603f1983011215610c6a575f5ffd5b610c72610b3c565b9150610c818560408601610bc5565b8252610c908560808601610bc5565b6020830152816020820152610ca88560c08601610b96565b6040820152505092915050565b5f82601f830112610cc4575f5ffd5b813567ffffffffffffffff811115610cde57610cde610b28565b8060051b610cee60208201610b65565b91825260208185018101929081019086841115610d09575f5ffd5b6020860192505b83831015610d2b578235825260209283019290910190610d10565b9695505050505050565b80356001600160a01b0381168114610d4b575f5ffd5b919050565b5f5f5f5f5f6101808688031215610d65575f5ffd5b610d6f8787610c12565b945061010086013567ffffffffffffffff811115610d8b575f5ffd5b610d9788828901610cb5565b945050610da76101208701610d35565b9250610db66101408701610d35565b94979396509194610160013592915050565b5f6101008284031215610dd9575f5ffd5b610de38383610c12565b9392505050565b5f5f5f5f60808587031215610dfd575f5ffd5b84359350610e0d60208601610d35565b9250610e1b60408601610d35565b9396929550929360600135925050565b5f5f5f5f5f5f5f5f5f5f6102008b8d031215610e45575f5ffd5b610e4f8c8c610bc5565b9950610e5e8c60408d01610bc5565b9850610e6d8c60808d01610bc5565b9750610e7c8c60c08d01610bc5565b96506101008b013595506101208b01359450610e9c8c6101408d01610bc5565b9350610eac8c6101808d01610bc5565b92506101c08b013567ffffffffffffffff811115610ec8575f5ffd5b610ed48d828e01610cb5565b9250506101e08b013567ffffffffffffffff811115610ef1575f5ffd5b610efd8d828e01610cb5565b9150509295989b9194979a5092959850565b805f5b6002811015610f31578151845260209384019390910190600101610f12565b50505050565b5f8151808452602084019350602083015f5b82811015610f67578151865260209586019590910190600101610f49565b5093949350505050565b610f7b818c610f0f565b610f88604082018b610f0f565b610f95608082018a610f0f565b610fa260c0820189610f0f565b8661010082015285610120820152610fbe610140820186610f0f565b610fcc610180820185610f0f565b6102006101c08201525f610fe4610200830185610f37565b8281036101e0840152610ff78185610f37565b9d9c50505050505050505050505050565b5f60208284031215611018575f5ffd5b610de382610d35565b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220f9bc302c8f5885bbcfccbb1f5fc4b0e3e617bd00c22580af95ab45c2febf228464736f6c634300081c0033a26469706673582212203482b60fbfecf3568725fa8037545610a8ab5da8b1487a62da7d499df061115764736f6c634300081c00336080604052348015600e575f5ffd5b506113b68061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061011c575f3560e01c8063927cab47116100a9578063cd6dc6871161006e578063cd6dc68714610273578063d663418514610286578063df15c37e14610299578063e61bce4a146102ae578063ed1224e5146102b7575f5ffd5b8063927cab471461021f5780639d9ca28d14610232578063a46c363714610245578063ad30909c1461024d578063ae2ac4ae14610260575f5ffd5b8063702d2dca116100ef578063702d2dca146101a157806376804a03146101c15780637c33404f146101e4578063842cb6de146101fa5780638d6859a31461020d575f5ffd5b8063158ef93e146101205780633a7a53b214610142578063522c90fa146101555780635ccbc77814610180575b5f5ffd5b60025461012d9060ff1681565b60405190151581526020015b60405180910390f35b61012d610150366004610e90565b6102ca565b610168610163366004610eab565b61033d565b6040516001600160a01b039091168152602001610139565b61019361018e366004610e90565b6103a8565b604051908152602001610139565b6101b46101af366004610f0a565b610415565b6040516101399190610fb0565b6101c96104af565b60408051938452602084019290925290820152606001610139565b6101f86101f2366004610fc2565b50505050565b005b610193610208366004610e90565b610530565b5f54610168906001600160a01b031681565b6101f861022d366004610e90565b610562565b61012d610240366004610e90565b610587565b6101936105b9565b61019361025b366004610ff9565b610632565b6101f861026e366004610e90565b610675565b6101f8610281366004611038565b6106f3565b61012d610294366004610ff9565b610826565b6102a1610866565b6040516101399190611062565b61019360015481565b6101f86102c5366004610f0a565b6108d3565b5f8054604051631d3d29d960e11b81526001600160a01b03848116600483015290911690633a7a53b2906024015b602060405180830381865afa158015610313573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061033791906110ad565b92915050565b5f8054604051632916487d60e11b8152600481018490526001600160a01b039091169063522c90fa90602401602060405180830381865afa158015610384573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061033791906110dc565b5f8054604051630b9978ef60e31b81526001600160a01b03848116600483015290911690635ccbc778906024015b602060405180830381865afa1580156103f1573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061033791906110f7565b60608382146104645760405162461bcd60e51b8152602060048201526016602482015275082e4e4c2f2e640d8cadccee8d040dad2e6dac2e8c6d60531b60448201526064015b60405180910390fd5b6104a6858590506001548585808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250610bac92505050565b95945050505050565b5f5f5f5f5f9054906101000a90046001600160a01b03166001600160a01b03166376804a036040518163ffffffff1660e01b8152600401606060405180830381865afa158015610501573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610525919061110e565b925092509250909192565b5f80546040516342165b6f60e11b81526001600160a01b0384811660048301529091169063842cb6de906024016103d6565b60025460ff166105845760405162461bcd60e51b815260040161045b90611139565b50565b5f8054604051639d9ca28d60e01b81526001600160a01b03848116600483015290911690639d9ca28d906024016102f8565b5f5f5f9054906101000a90046001600160a01b03166001600160a01b031663a46c36376040518163ffffffff1660e01b8152600401602060405180830381865afa158015610609573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061062d91906110f7565b905090565b5f61066e8383808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250610da392505050565b9392505050565b60025460ff166106975760405162461bcd60e51b815260040161045b90611139565b5f54604051635715625760e11b81526001600160a01b0383811660048301529091169063ae2ac4ae906024015f604051808303815f87803b1580156106da575f5ffd5b505af11580156106ec573d5f5f3e3d5ffd5b5050505050565b60025460ff161561073c5760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604482015260640161045b565b6001600160a01b0382166107925760405162461bcd60e51b815260206004820152601a60248201527f496e76616c6964206261736520766f74696e672073797374656d000000000000604482015260640161045b565b5f81116107f25760405162461bcd60e51b815260206004820152602860248201527f4372656469747320706572206a75646765206d75737420626520677265617465604482015267072207468616e20360c41b606482015260840161045b565b5f80546001600160a01b0319166001600160a01b03939093169290921790915560019081556002805460ff19169091179055565b5f61066e8383808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250506001549150610e099050565b5f805460408051636f8ae1bf60e11b815290516060936001600160a01b039093169263df15c37e92600480820193918290030181865afa1580156108ac573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261062d9190810190611184565b60025460ff166108f55760405162461bcd60e51b815260040161045b90611139565b82811461093d5760405162461bcd60e51b8152602060048201526016602482015275082e4e4c2f2e640d8cadccee8d040dad2e6dac2e8c6d60531b604482015260640161045b565b826109995760405162461bcd60e51b815260206004820152602660248201527f4d75737420766f746520666f72206174206c65617374206f6e6520706172746960448201526518da5c185b9d60d21b606482015260840161045b565b6109d88282808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250506001549150610e099050565b610a2e5760405162461bcd60e51b815260206004820152602160248201527f496e76616c69642071756164726174696320766f746520616c6c6f636174696f6044820152603760f91b606482015260840161045b565b5f610a6a8383808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250610da392505050565b90505f8267ffffffffffffffff811115610a8657610a86611170565b604051908082528060200260200182016040528015610aaf578160200160208202803683370190505b5090505f5b83811015610afa57848482818110610ace57610ace61124f565b90506020020135828281518110610ae757610ae761124f565b6020908102919091010152600101610ab4565b505f5460405163ed1224e560e01b81526001600160a01b039091169063ed1224e590610b2e908990899086906004016112a1565b5f604051808303815f87803b158015610b45575f5ffd5b505af1158015610b57573d5f5f3e3d5ffd5b50505050336001600160a01b03167fddb4be8a0e1c30287cc29e02e106e0241158ba880ac46633c17714946c1e13528787878787604051610b9c9594939291906112d0565b60405180910390a2505050505050565b606081518414610bfe5760405162461bcd60e51b815260206004820152601b60248201527f507265666572656e636573206c656e677468206d69736d617463680000000000604482015260640161045b565b5f8411610c585760405162461bcd60e51b815260206004820152602260248201527f4d7573742068617665206174206c65617374206f6e65207061727469636970616044820152611b9d60f21b606482015260840161045b565b5f8467ffffffffffffffff811115610c7257610c72611170565b604051908082528060200260200182016040528015610c9b578160200160208202803683370190505b5090505f805b86811015610cd857848181518110610cbb57610cbb61124f565b602002602001015182610cce9190611337565b9150600101610ca1565b50805f03610d2c575f610cf3610cee888861134a565b610e1f565b90505f5b87811015610d255781848281518110610d1257610d1261124f565b6020908102919091010152600101610cf7565b5050610d9a565b5f5b86811015610d98575f82868381518110610d4a57610d4a61124f565b602002602001015188610d5d9190611369565b610d67919061134a565b9050610d7281610e1f565b848381518110610d8457610d8461124f565b602090810291909101015250600101610d2e565b505b50949350505050565b5f80805b8351811015610e0257838181518110610dc257610dc261124f565b6020026020010151848281518110610ddc57610ddc61124f565b6020026020010151610dee9190611369565b610df89083611337565b9150600101610da7565b5092915050565b5f5f610e1484610da3565b909210159392505050565b5f815f03610e2e57505f919050565b5f6002610e3c846001611337565b610e46919061134a565b9050825b8082101561066e575080600281610e61818761134a565b610e6b9190611337565b610e75919061134a565b9150610e4a565b6001600160a01b0381168114610584575f5ffd5b5f60208284031215610ea0575f5ffd5b813561066e81610e7c565b5f60208284031215610ebb575f5ffd5b5035919050565b5f5f83601f840112610ed2575f5ffd5b50813567ffffffffffffffff811115610ee9575f5ffd5b6020830191508360208260051b8501011115610f03575f5ffd5b9250929050565b5f5f5f5f60408587031215610f1d575f5ffd5b843567ffffffffffffffff811115610f33575f5ffd5b610f3f87828801610ec2565b909550935050602085013567ffffffffffffffff811115610f5e575f5ffd5b610f6a87828801610ec2565b95989497509550505050565b5f8151808452602084019350602083015f5b82811015610fa6578151865260209586019590910190600101610f88565b5093949350505050565b602081525f61066e6020830184610f76565b5f5f5f5f60608587031215610fd5575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115610f5e575f5ffd5b5f5f6020838503121561100a575f5ffd5b823567ffffffffffffffff811115611020575f5ffd5b61102c85828601610ec2565b90969095509350505050565b5f5f60408385031215611049575f5ffd5b823561105481610e7c565b946020939093013593505050565b602080825282518282018190525f918401906040840190835b818110156110a25783516001600160a01b031683526020938401939092019160010161107b565b509095945050505050565b5f602082840312156110bd575f5ffd5b8151801515811461066e575f5ffd5b80516110d781610e7c565b919050565b5f602082840312156110ec575f5ffd5b815161066e81610e7c565b5f60208284031215611107575f5ffd5b5051919050565b5f5f5f60608486031215611120575f5ffd5b5050815160208301516040909301519094929350919050565b60208082526018908201527f436f6e7472616374206e6f7420696e697469616c697a65640000000000000000604082015260600190565b634e487b7160e01b5f52604160045260245ffd5b5f60208284031215611194575f5ffd5b815167ffffffffffffffff8111156111aa575f5ffd5b8201601f810184136111ba575f5ffd5b805167ffffffffffffffff8111156111d4576111d4611170565b8060051b604051601f19603f830116810181811067ffffffffffffffff8211171561120157611201611170565b60405291825260208184018101929081018784111561121e575f5ffd5b6020850194505b8385101561124457611236856110cc565b815260209485019401611225565b509695505050505050565b634e487b7160e01b5f52603260045260245ffd5b8183526020830192505f815f5b84811015610fa657813561128381610e7c565b6001600160a01b031686526020958601959190910190600101611270565b604081525f6112b4604083018587611263565b82810360208401526112c68185610f76565b9695505050505050565b606081525f6112e3606083018789611263565b82810360208401528481526001600160fb1b03851115611301575f5ffd5b8460051b80876020840137604093909301939093525001602001949350505050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561033757610337611323565b5f8261136457634e487b7160e01b5f52601260045260245ffd5b500490565b80820281158282048414176103375761033761132356fea2646970667358221220f265eef6c2bc7a7df1f0c9b6cb94dba12d095d89af30c745ee67e9504816bc3564736f6c634300081c0033000000000000000000000000c00779a913c9bb701a1e20f36381d860184a6b5600000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e8

Deployed Bytecode

0x6080604052600436106102bf575f3560e01c80638b52d56d1161016f578063cbf7719b116100d8578063da7bafce11610092578063e456766b1161006d578063e456766b14610a57578063e79b754714610a85578063ed440f5014610ab9578063fa71039114610ada575f5ffd5b8063da7bafce146109f1578063db5abbd014610a05578063e029983614610a24575f5ffd5b8063cbf7719b146108df578063cc60cf1f1461090a578063cda90b8814610941578063ce762f6414610974578063ce836b21146109a7578063d14b85b9146109c6575f5ffd5b8063a3c2001d11610129578063a3c2001d1461080c578063a4d3180514610821578063a717430c14610835578063aadc3b7214610848578063bff28ea814610881578063c45a0155146108ac575f5ffd5b80638b52d56d146107455780638bc6dae0146107645780638f02fc6f146107785780639dd58b58146107ab5780639f2ce678146107bf578063a074696a146107de575f5ffd5b80633fc8cef31161022b5780635c60da1b116101e55780636b792c4b116101c05780636b792c4b1461069a5780636f9948c7146106b957806373615775146106f85780637fae382714610726575f5ffd5b80635c60da1b146106155780635e2490211461064857806366cab36b14610667575f5ffd5b80633fc8cef3146104d057806340b1187d146105035780634dd218fd1461059957806351dc554b146105b8578063543f5d38146105cb57806355f8cf0d146105e1575f5ffd5b80632244d1cb1161027c5780632244d1cb146103f557806322e669d31461042c578063234c03551461045f5780632c7dc1931461047e57806332bb3ed81461049d5780633f3642dd146104bc575f5ffd5b80630368d346146102c35780630396ec101461030a5780630c2891b71461032b5780630cf79cde14610340578063138051281461037e5780632062b2a7146103a9575b5f5ffd5b3480156102ce575f5ffd5b506102f76102dd366004612bd5565b6001600160a01b03165f9081526002602052604090205490565b6040519081526020015b60405180910390f35b348015610315575f5ffd5b50610329610324366004612bee565b610aee565b005b348015610336575f5ffd5b506102f760065481565b34801561034b575f5ffd5b5061036e61035a366004612c21565b60096020525f908152604090205460ff1681565b6040519015158152602001610301565b348015610389575f5ffd5b506102f7610398366004612bd5565b600a6020525f908152604090205481565b3480156103b4575f5ffd5b506103dd6103c3366004612bd5565b60056020525f90815260409020546001600160a01b031681565b6040516001600160a01b039091168152602001610301565b348015610400575f5ffd5b506103dd61040f366004612bd5565b6001600160a01b039081165f908152600460205260409020541690565b348015610437575f5ffd5b506103dd7f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e881565b34801561046a575f5ffd5b506103dd610479366004612c21565b610e3e565b348015610489575f5ffd5b506102f7610498366004612c21565b610e66565b3480156104a8575f5ffd5b506102f76104b7366004612bd5565b6110b7565b3480156104c7575f5ffd5b5061032961128f565b3480156104db575f5ffd5b506103dd7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b34801561050e575f5ffd5b50604080516001600160a01b037f00000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e811682527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2811660208301527f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e81691810191909152606001610301565b3480156105a4575f5ffd5b506103296105b3366004612bd5565b611429565b6103dd6105c6366004612d95565b6115d0565b3480156105d6575f5ffd5b506102f762093a8081565b3480156105ec575f5ffd5b506103dd6105fb366004612bd5565b60046020525f90815260409020546001600160a01b031681565b348015610620575f5ffd5b506103dd7f000000000000000000000000c00779a913c9bb701a1e20f36381d860184a6b5681565b348015610653575f5ffd5b5061036e610662366004612e90565b6118bc565b348015610672575f5ffd5b506103dd7f000000000000000000000000e930e161738a442e5e8787e0b9709e9c882e41e081565b3480156106a5575f5ffd5b506103296106b4366004612c21565b6118e8565b3480156106c4575f5ffd5b506103dd6106d3366004612eba565b600b60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610703575f5ffd5b5061036e610712366004612bd5565b5f6020819052908152604090205460ff1681565b348015610731575f5ffd5b506103dd610740366004612eba565b61197b565b348015610750575f5ffd5b506102f761075f366004612bd5565b6119ff565b34801561076f575f5ffd5b506102f7611bae565b348015610783575f5ffd5b506103dd7f0000000000000000000000002bdc9cf04d8a5809000d1f56792c6e101d5bd9f481565b3480156107b6575f5ffd5b506102f7611c3c565b3480156107ca575f5ffd5b506103296107d9366004612ee2565b611c8a565b3480156107e9575f5ffd5b5061036e6107f8366004612bd5565b60036020525f908152604090205460ff1681565b348015610817575f5ffd5b506102f7600c5481565b34801561082c575f5ffd5b50610329611dd4565b610329610843366004612eba565b611ecf565b348015610853575f5ffd5b5061036e610862366004612e90565b600760209081525f928352604080842090915290825290205460ff1681565b34801561088c575f5ffd5b506102f761089b366004612c21565b5f9081526008602052604090205490565b3480156108b7575f5ffd5b506103dd7f000000000000000000000000bfb8d464962727d96afa07367a2150ec5e52341881565b3480156108ea575f5ffd5b506102f76108f9366004612bd5565b60026020525f908152604090205481565b348015610915575f5ffd5b506103dd610924366004612bd5565b6001600160a01b039081165f908152600560205260409020541690565b34801561094c575f5ffd5b506103dd7f00000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e81565b34801561097f575f5ffd5b506103dd7f000000000000000000000000df34dcc01b33f1f233f3a3ceeffd0ce0de8e67a581565b3480156109b2575f5ffd5b506102f76109c1366004612c21565b61201a565b3480156109d1575f5ffd5b506102f76109e0366004612c21565b60086020525f908152604090205481565b3480156109fc575f5ffd5b50600c546102f7565b348015610a10575f5ffd5b5061036e610a1f366004612bd5565b6121e1565b348015610a2f575f5ffd5b506103dd7f000000000000000000000000c743e2f72152fd99691b813a245019e5400b897a81565b348015610a62575f5ffd5b5061036e610a71366004612c21565b5f9081526009602052604090205460ff1690565b348015610a90575f5ffd5b506102f7610a9f366004612bd5565b6001600160a01b03165f908152600a602052604090205490565b348015610ac4575f5ffd5b50610acd612222565b6040516103019190612f46565b348015610ae5575f5ffd5b50610329612282565b335f9081526020819052604090205460ff16610b255760405162461bcd60e51b8152600401610b1c90612f58565b60405180910390fd5b5f8381526009602052604090205460ff1615610b535760405162461bcd60e51b8152600401610b1c90612fa1565b6006545f848152600860205260409020541015610ba75760405162461bcd60e51b8152602060048201526012602482015271496e73756666696369656e7420766f74657360701b6044820152606401610b1c565b5f838152600960205260409020805460ff191660011790556001600160a01b03821615610dc6576001600160a01b0382165f9081526020819052604090205460ff1615610d3a576001600160a01b0382165f908152602081905260408120805460ff191690555b600154811015610d0157826001600160a01b031660018281548110610c3557610c35612fd8565b5f918252602090912001546001600160a01b031603610cf95760018054610c5d908290613000565b81548110610c6d57610c6d612fd8565b5f91825260209091200154600180546001600160a01b039092169183908110610c9857610c98612fd8565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055506001805480610cd457610cd4613013565b5f8281526020902081015f1990810180546001600160a01b0319169055019055610d01565b600101610c0e565b506040516001600160a01b038316907fc19950c209d8ad1fcb5d64f3c545c4ad0782202fcbfc1b564cc1ed120025e659905f90a2610e0f565b6001600160a01b0382165f81815260208190526040808220805460ff19166001908117909155805480820182559083527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180546001600160a01b03191684179055517f0b221fa4a77e140717a07cc6edef3e54fbb0560c79d71e5fe77b8f7e618d664c9190a2610e0f565b8015610e0f57600680549082905560408051828152602081018490527f7f95e655f1cebcc68c938397e57f67e4dacc24fc905d34e073a3b65fbcfbd7fb910160405180910390a1505b60405183907f7b1bcf1ccf901a11589afff5504d59fd0a53780eed2a952adade0348985139e0905f90a2505050565b60018181548110610e4d575f80fd5b5f918252602090912001546001600160a01b0316905081565b604080516101608101825273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815273a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486020808301919091526001600160a01b037f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e88116838501525f6060808501829052608080860183905260a080870184905260c080880185905260e0808901869052610100808a01879052610120808b01889052610140808c018990528c519081018d52808601898152948101899052928301889052908201879052810186905290815288518083018a52858152808801869052808a018690528085018690528084018690528188015288518083018a52858152808801869052808a01869052808501869052808401869052818a015288518083018a52858152808801869052808a018690528085018690528084018690528185015288518083018a52858152808801869052808a018690528085018690528084018690528184015288519182018952737f86bf177dd4f3494b841a37e810a34dd56c829b825273383e6b4437b59fff47b619cba855ca29342a85599682019690965280880184905291820183905281018290529451637b1257b360e01b81529094917f00000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e1690637b1257b39061106f90869086908a9087906004016130c6565b602060405180830381865afa15801561108a573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110ae91906130f8565b95945050505050565b335f9081526020819052604081205460ff166110e55760405162461bcd60e51b8152600401610b1c90612f58565b6001600160a01b0382165f9081526020819052604090205460ff166111435760405162461bcd60e51b8152602060048201526014602482015273129d5919d948191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610b1c565b336001600160a01b038316036111a55760405162461bcd60e51b815260206004820152602160248201527f43616e6e6f742070726f706f736520746f2072656d6f766520796f757273656c6044820152603360f91b6064820152608401610b1c565b6040516a72656d6f76654a7564676560a81b60208201526bffffffffffffffffffffffff19606084901b16602b82015242603f8201525f90605f0160408051601f1981840301815291815281516020928301205f818152600990935291205490915060ff16156112275760405162461bcd60e51b8152600401610b1c90612fa1565b336001600160a01b0316817f898a80665dd7411c488cdf4a8658fc065411886e2b29e8d29f159de5651f212460405161127f906020808252600b908201526a72656d6f76654a7564676560a81b604082015260600190565b60405180910390a390505b919050565b335f9081526020819052604081205460ff166112c257335f908152600560205260409020546001600160a01b03166112c4565b335b6001600160a01b0381165f908152600260205260409020549091506113215760405162461bcd60e51b81526020600482015260136024820152724e6f207265776172647320746f20636c61696d60681b6044820152606401610b1c565b6001600160a01b0381165f9081526003602052604090205460ff16156113895760405162461bcd60e51b815260206004820152601760248201527f416c726561647920636c61696d656420726577617264730000000000000000006044820152606401610b1c565b6001600160a01b0381165f908152600260209081526040808320546003909252808320805460ff19166001179055519091339183156108fc0291849190818181858888f193505050501580156113e1573d5f5f3e3d5ffd5b50816001600160a01b03167f2b8d51946813c957a7b1714546806778b063466901c289b82d967a22caed6e028260405161141d91815260200190565b60405180910390a25050565b335f9081526020819052604090205460ff166114575760405162461bcd60e51b8152600401610b1c90612f58565b6001600160a01b0381166114ad5760405162461bcd60e51b815260206004820152601860248201527f496e76616c69642064656c6567617465206164647265737300000000000000006044820152606401610b1c565b336001600160a01b038216036115055760405162461bcd60e51b815260206004820152601b60248201527f43616e6e6f742064656c656761746520746f20796f757273656c6600000000006044820152606401610b1c565b335f908152600460205260409020546001600160a01b0316156115635760405162461bcd60e51b8152602060048201526016602482015275416c72656164792068617320612064656c656761746560501b6044820152606401610b1c565b335f81815260046020908152604080832080546001600160a01b0387166001600160a01b03199182168117909255818552600590935281842080549093168517909255519092917f0c195f050c9d1eae4d0a59bee2ef525b8a5d91975fa9342a54bfd6528ac8fb6f91a350565b5f4288116116205760405162461bcd60e51b815260206004820181905260248201527f53746172742074696d65206d75737420626520696e20746865206675747572656044820152606401610b1c565b8787116116795760405162461bcd60e51b815260206004820152602160248201527f456e642074696d65206d7573742062652061667465722073746172742074696d6044820152606560f81b6064820152608401610b1c565b5f805b85518110156116b45785818151811061169757611697612fd8565b6020026020010151826116aa919061310f565b915060010161167c565b505f6116bf82612377565b90505f6116cf826298968061241b565b90508281101561173a5760405162461bcd60e51b815260206004820152603060248201527f496e73756666696369656e7420505955534420616d6f756e7420666f7220707260448201526f34bd32903234b9ba3934b13aba34b7b760811b6064820152608401610b1c565b5f5b86518110156117cc5761176787828151811061175a5761175a612fd8565b60200260200101516121e1565b6117c45760405162461bcd60e51b815260206004820152602860248201527f53656c6563746564206a75646765206973206e6f7420696e20676c6f62616c20604482015267726567697374727960c01b6064820152608401610b1c565b60010161173c565b506117d78587612714565b506117e98c8c8c8c8c8c8c888a612938565b335f818152600a602081815260408084208054600b8452828620818752845291852080546001600160a01b0319166001600160a01b038916179055948452919052825493975092919061183b83613122565b9091555050600c8054905f61184f83613122565b9190505550336001600160a01b0316856001600160a01b03167f526b2cf7f5bb87f9a4c01a6eb8c01bf90405b9726286908ac1dfd93944da0e848f858a5f01518b602001516040516118a4949392919061316e565b60405180910390a35050505098975050505050505050565b5f8281526007602090815260408083206001600160a01b038516845290915290205460ff165b92915050565b335f9081526020819052604090205460ff166119165760405162461bcd60e51b8152600401610b1c90612f58565b604051339082156108fc029083905f818181858888f19350505050158015611940573d5f5f3e3d5ffd5b506040518181525f9033907f9495d03190a79a43e534c9e328ff322f6283261383f5f19c809564f6ad5a57b39060200160405180910390a350565b6001600160a01b0382165f908152600a602052604081205482106119d75760405162461bcd60e51b8152602060048201526013602482015272496e646578206f7574206f6620626f756e647360681b6044820152606401610b1c565b506001600160a01b039182165f908152600b6020908152604080832093835292905220541690565b335f9081526020819052604081205460ff16611a2d5760405162461bcd60e51b8152600401610b1c90612f58565b6001600160a01b038216611a7b5760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b6044820152606401610b1c565b6001600160a01b0382165f9081526020819052604090205460ff1615611ada5760405162461bcd60e51b81526020600482015260146024820152734a7564676520616c72656164792065786973747360601b6044820152606401610b1c565b604051676164644a7564676560c01b60208201526bffffffffffffffffffffffff19606084901b16602882015242603c8201525f90605c0160408051601f1981840301815291815281516020928301205f818152600990935291205490915060ff1615611b595760405162461bcd60e51b8152600401610b1c90612fa1565b336001600160a01b0316817f898a80665dd7411c488cdf4a8658fc065411886e2b29e8d29f159de5651f212460405161127f906020808252600890820152676164644a7564676560c01b604082015260600190565b6040516370a0823160e01b81523060048201525f907f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e86001600160a01b0316906370a08231906024015b602060405180830381865afa158015611c13573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c3791906130f8565b905090565b6040516370a0823160e01b81523060048201525f907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316906370a0823190602401611bf8565b335f9081526020819052604090205460ff16611cb85760405162461bcd60e51b8152600401610b1c90612f58565b5f82815260076020908152604080832033845290915290205460ff1615611d215760405162461bcd60e51b815260206004820152601e60248201527f416c726561647920766f746564206f6e20746869732070726f706f73616c00006044820152606401610b1c565b5f8281526009602052604090205460ff1615611d4f5760405162461bcd60e51b8152600401610b1c90612fa1565b5f8281526007602090815260408083203384529091529020805460ff191660011790558015611d97575f828152600860205260408120805491611d9183613122565b91905055505b6040518115158152339083907f8b40665146691327ee30f5bf56e9b2d6f445d2830d3b09b56385cd30f630ecfb9060200160405180910390a35050565b335f9081526020819052604090205460ff16611e025760405162461bcd60e51b8152600401610b1c90612f58565b335f908152600460205260409020546001600160a01b0316611e665760405162461bcd60e51b815260206004820152601760248201527f4e6f2064656c65676174696f6e20746f207265766f6b650000000000000000006044820152606401610b1c565b335f81815260046020908152604080832080546001600160a01b03198082169092556001600160a01b0316808552600590935281842080549091169055519092907f0c195f050c9d1eae4d0a59bee2ef525b8a5d91975fa9342a54bfd6528ac8fb6f908390a350565b6001600160a01b038216611f1d5760405162461bcd60e51b8152602060048201526015602482015274496e76616c6964206a75646765206164647265737360581b6044820152606401610b1c565b5f8111611f6c5760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610b1c565b80341015611fb35760405162461bcd60e51b8152602060048201526014602482015273125b9cdd59999a58da595b9d081c185e5b595b9d60621b6044820152606401610b1c565b6001600160a01b0382165f9081526002602052604081208054839290611fda90849061310f565b90915550506040518181526001600160a01b038316907fd545e22a252a1e46a3ff5d635804684897b3b9831ac8163a9bc6c54ac88e53739060200161141d565b335f9081526020819052604081205460ff166120485760405162461bcd60e51b8152600401610b1c90612f58565b5f82116120a55760405162461bcd60e51b815260206004820152602560248201527f566f746573207265717569726564206d75737420626520677265617465722074604482015264068616e20360dc1b6064820152608401610b1c565b6001548211156121095760405162461bcd60e51b815260206004820152602960248201527f566f7465732072657175697265642063616e6e6f742065786365656420746f74604482015268616c206a756467657360b81b6064820152608401610b1c565b6040517218da185b99d9559bdd195cd4995c5d5a5c9959606a1b6020820152603381018390524260538201525f9060730160408051601f1981840301815291815281516020928301205f818152600990935291205490915060ff16156121815760405162461bcd60e51b8152600401610b1c90612fa1565b336001600160a01b0316817f898a80665dd7411c488cdf4a8658fc065411886e2b29e8d29f159de5651f212460405161127f9060208082526013908201527218da185b99d9559bdd195cd4995c5d5a5c9959606a1b604082015260600190565b6001600160a01b0381165f9081526020819052604081205460ff16806118e25750506001600160a01b039081165f9081526005602052604090205416151590565b6060600180548060200260200160405190810160405280929190818152602001828054801561227857602002820191905f5260205f20905b81546001600160a01b0316815260019091019060200180831161225a575b5050505050905090565b335f9081526020819052604090205460ff166122b05760405162461bcd60e51b8152600401610b1c90612f58565b6040516370a0823160e01b81523060048201525f907f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e86001600160a01b0316906370a0823190602401602060405180830381865afa158015612314573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061233891906130f8565b90508015612374576123746001600160a01b037f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e81633836129bf565b50565b604051632c7dc19360e01b8152670de0b6b3a764000060048201525f9081903090632c7dc19390602401602060405180830381865afa1580156123bc573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123e091906130f8565b90506123ed81606461319a565b6123ff84670de0b6b3a764000061319a565b61240a90606961319a565b61241491906131b1565b9392505050565b5f5f83116124755760405162461bcd60e51b815260206004820152602160248201527f45544820616d6f756e74206d7573742062652067726561746572207468616e206044820152600360fc1b6064820152608401610b1c565b604080516101608101825273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815273a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486020808301919091526001600160a01b037f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e88116838501525f6060808501829052608080860183905260a080870184905260c080880185905260e0808901869052610100808a01879052610120808b01889052610140808c018990528c519081018d52808601898152948101899052928301889052908201879052810186905290815288518083018a52858152808801869052808a018690528085018690528084018690528188015288518083018a52858152808801869052808a01869052808501869052808401869052818a015288518083018a52858152808801869052808a018690528085018690528084018690528185015288518083018a52858152808801869052808a018690528085018690528084018690528184015288519182018952737f86bf177dd4f3494b841a37e810a34dd56c829b825273383e6b4437b59fff47b619cba855ca29342a8559968201969096528088018490529182018390528101919091529351632e4e0c7160e11b8152929391927f00000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e90911690635c9c18e2908890612686908790879084908c9089906004016131d0565b60206040518083038185885af11580156126a2573d5f5f3e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906126c791906130f8565b60408051888152602081018390529081018790529094507f12aec4492341cce30775891eceb584f6fbbd825e56d94db93c1888839677903c9060600160405180910390a150505092915050565b5f80835160038111156127295761272961313a565b0361275e576127577f000000000000000000000000e930e161738a442e5e8787e0b9709e9c882e41e0612a16565b90506127e7565b6001835160038111156127735761277361313a565b036127a1576127577f000000000000000000000000df34dcc01b33f1f233f3a3ceeffd0ce0de8e67a5612a16565b6002835160038111156127b6576127b661313a565b036127e7576127e47f000000000000000000000000c743e2f72152fd99691b813a245019e5400b897a612a16565b90505b806001600160a01b0316637c33404f84604001518560600151856040518463ffffffff1660e01b815260040161281f93929190613213565b5f604051808303815f87803b158015612836575f5ffd5b505af1158015612848573d5f5f3e3d5ffd5b505050508260200151156128ea575f6128807f0000000000000000000000002bdc9cf04d8a5809000d1f56792c6e101d5bd9f4612a16565b604080860151905163cd6dc68760e01b81526001600160a01b038581166004830152602482019290925291925082169063cd6dc687906044015f604051808303815f87803b1580156128d0575f5ffd5b505af11580156128e2573d5f5f3e3d5ffd5b509293505050505b825160208401516040516001600160a01b038416927f5c968cbcc62446275183fea8d6eaa29a00505b77145a7fad4de724257508a33c9261292a92613231565b60405180910390a292915050565b5f6129627f000000000000000000000000c00779a913c9bb701a1e20f36381d860184a6b56612a16565b90506129986001600160a01b037f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e81682856129bf565b6129b2818b8b8b8b8b8b8b6129ad8b34613000565b612a21565b9998505050505050505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052612a11908490612ab9565b505050565b5f6118e2825f612b2b565b60405162a8bae760e31b81526001600160a01b038a1690630545d738908390612a809033908d908d908d908d908d908d9030908e907f0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e89060040161324e565b5f604051808303818588803b158015612a97575f5ffd5b505af1158015612aa9573d5f5f3e3d5ffd5b5050505050505050505050505050565b5f5f60205f8451602086015f885af180612ad8576040513d5f823e3d81fd5b50505f513d91508115612aef578060011415612afc565b6001600160a01b0384163b155b15612b2557604051635274afe760e01b81526001600160a01b0385166004820152602401610b1c565b50505050565b5f81471015612b565760405163cf47918160e01b815247600482015260248101839052604401610b1c565b763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c175f526e5af43d82803e903d91602b57fd5bf38360781b176020526037600983f090506001600160a01b0381166118e25760405163b06ebf3d60e01b815260040160405180910390fd5b80356001600160a01b038116811461128a575f5ffd5b5f60208284031215612be5575f5ffd5b61241482612bbf565b5f5f5f60608486031215612c00575f5ffd5b83359250612c1060208501612bbf565b929592945050506040919091013590565b5f60208284031215612c31575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612c7557612c75612c38565b604052919050565b5f67ffffffffffffffff821115612c9657612c96612c38565b5060051b60200190565b5f82601f830112612caf575f5ffd5b8135612cc2612cbd82612c7d565b612c4c565b8082825260208201915060208360051b860101925085831115612ce3575f5ffd5b602085015b83811015612d0757612cf981612bbf565b835260209283019201612ce8565b5095945050505050565b8035801515811461128a575f5ffd5b5f60808284031215612d30575f5ffd5b6040516080810167ffffffffffffffff81118282101715612d5357612d53612c38565b604052905080823560048110612d67575f5ffd5b8152612d7560208401612d11565b602082015260408381013590820152606092830135920191909152919050565b5f5f5f5f5f5f5f5f610160898b031215612dad575f5ffd5b883597506020890135965060408901359550606089013594506080890135935060a089013567ffffffffffffffff811115612de6575f5ffd5b8901601f81018b13612df6575f5ffd5b8035612e04612cbd82612c7d565b8082825260208201915060208360051b85010192508d831115612e25575f5ffd5b6020840193505b82841015612e47578335825260209384019390910190612e2c565b955050505060c089013567ffffffffffffffff811115612e65575f5ffd5b612e718b828c01612ca0565b925050612e818a60e08b01612d20565b90509295985092959890939650565b5f5f60408385031215612ea1575f5ffd5b82359150612eb160208401612bbf565b90509250929050565b5f5f60408385031215612ecb575f5ffd5b612ed483612bbf565b946020939093013593505050565b5f5f60408385031215612ef3575f5ffd5b82359150612eb160208401612d11565b5f8151808452602084019350602083015f5b82811015612f3c5781516001600160a01b0316865260209586019590910190600101612f15565b5093949350505050565b602081525f6124146020830184612f03565b60208082526029908201527f4f6e6c7920676c6f62616c206a75646765732063616e2063616c6c207468697360408201526810333ab731ba34b7b760b91b606082015260800190565b60208082526019908201527f50726f706f73616c20616c726561647920657865637574656400000000000000604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b818103818111156118e2576118e2612fec565b634e487b7160e01b5f52603160045260245ffd5b805f5b600b811015612b255781516001600160a01b031684526020938401939091019060010161302a565b805f5b6005811015612b25578151845f5b6005811015613082578251825260209283019290910190600101613063565b50505060a0939093019260209190910190600101613055565b805f5b6005811015612b255781516001600160a01b031684526020938401939091019060010161309e565b61054081016130d58287613027565b6130e3610160830186613052565b836104808301526110ae6104a083018461309b565b5f60208284031215613108575f5ffd5b5051919050565b808201808211156118e2576118e2612fec565b5f6001820161313357613133612fec565b5060010190565b634e487b7160e01b5f52602160045260245ffd5b6004811061316a57634e487b7160e01b5f52602160045260245ffd5b9052565b8481526020810184905260808101613189604083018561314e565b821515606083015295945050505050565b80820281158282048414176118e2576118e2612fec565b5f826131cb57634e487b7160e01b5f52601260045260245ffd5b500490565b61056081016131df8288613027565b6131ed610160830187613052565b84610480830152836104a08301526132096104c083018461309b565b9695505050505050565b838152826020820152606060408201525f6110ae6060830184612f03565b6040810161323f828561314e565b82151560208301529392505050565b5f610140820160018060a01b038d1683528b60208401528a60408401528960608401528860808401528760a084015261014060c0840152808751808352610160850191506020890192505f5b818110156132b857835183526020938401939092019160010161329a565b50506001600160a01b03871660e08501528381036101008501526132dc8187612f03565b925050506132f66101208301846001600160a01b03169052565b9b9a505050505050505050505056fea2646970667358221220f25571b24249f41926de9dbd3aa3581a313f63ac8b7eb8cd47de662ec25a43fd64736f6c634300081c0033

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

000000000000000000000000c00779a913c9bb701a1e20f36381d860184a6b5600000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e8

-----Decoded View---------------
Arg [0] : _implementation (address): 0xc00779a913c9BB701a1e20F36381D860184a6b56
Arg [1] : _curveRouter (address): 0x45312ea0eFf7E09C83CBE249fa1d7598c4C8cd4e
Arg [2] : _weth (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Arg [3] : _pyusd (address): 0x6c3ea9036406852006290770BEdFcAbA0e23A0e8

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000c00779a913c9bb701a1e20f36381d860184a6b56
Arg [1] : 00000000000000000000000045312ea0eff7e09c83cbe249fa1d7598c4c8cd4e
Arg [2] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Arg [3] : 0000000000000000000000006c3ea9036406852006290770bedfcaba0e23a0e8


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.