Hackathon

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "settings": {
    "evmVersion": "cancun",
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": [
      "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.4.0/",
      "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.4.0/",
      "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.4.0/"
    ]
  },
  "sources": {
    "npm/@openzeppelin/contracts@5.4.0/interfaces/IERC1363.sol": {
      "content": "// 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);
}
"
    },
    "npm/@openzeppelin/contracts@5.4.0/interfaces/IERC165.sol": {
      "content": "// 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";
"
    },
    "npm/@openzeppelin/contracts@5.4.0/interfaces/IERC20.sol": {
      "content": "// 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";
"
    },
    "npm/@openzeppelin/contracts@5.4.0/token/ERC20/IERC20.sol": {
      "content": "// 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);
}
"
    },
    "npm/@openzeppelin/contracts@5.4.0/token/ERC20/utils/SafeERC20.sol": {
      "content": "// 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);
    }
}
"
    },
    "npm/@openzeppelin/contracts@5.4.0/utils/introspection/IERC165.sol": {
      "content": "// 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);
}
"
    },
    "project/contracts/Hackathon.sol": {
      "content": "// 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
        );
    }
}
"
    },
    "project/contracts/JudgingSystem.sol": {
      "content": "// 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;
    }
}
"
    },
    "project/contracts/StakeSystem.sol": {
      "content": "// 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;
    }
}
"
    },
    "project/contracts/VotingSystem.sol": {
      "content": "// 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);
        }

Tags:
ERC20, ERC165, Proxy, Swap, Voting, Upgradeable, Factory|addr:0x708b88bd7b41e54fba2348161c321c525754ace3|verified:true|block:23640323|tx:0x771b8ba484bf24137631d5ee35336285f423be2252ad085771fc28e363a194ba|first_check:1761307415

Submitted on: 2025-10-24 14:03:38

Comments

Log in to comment.

No comments yet.