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": []
  },
  "sources": {
    "project/contracts/Hackathon.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "./StakeSystem.sol";
import "./VotingSystem.sol";
import "./JudgingSystem.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 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;

    event ParticipantRegistered(
        address indexed participant
    );

    event SubmissionMade(
        address indexed participant,
        string projectName
    );

    event PrizeDistributed(
        address indexed winner,
        uint256 amount
    );

    event HackathonEnded();

    event SponsorAdded(
        address indexed sponsor,
        uint256 contribution
    );

    event SubmissionScored(
        address indexed participant,
        uint256 score
    );

    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,
        uint256 _judgeRewardPercentage,
        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
        judgeRewardPercentage = _judgeRewardPercentage;

        // 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
    )
        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 parameters
        minimumSponsorContribution = _minimumSponsorContribution;
        uint256 _prizeClaimCooldown = 1 days; // Default prize claim cooldown
        uint256 _judgingDuration = 1 days; // Default judging duration
        uint256 _judgeRewardPercentage = 50; // Default 0.5% judge reward

        // Initialize the hackathon with the provided parameters
        _initializeHackathon(
            _hackathonId,
            _startTime,
            _endTime,
            _prizeDistribution,
            _stakeAmount,
            _prizeClaimCooldown,
            _judgingDuration,
            _judgeRewardPercentage,
            _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],
            "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
    {
        require(
            msg.sender == organizer || sponsors[msg.sender].isActive,
            "Only organizer or sponsors can distribute prizes"
        );

        require(!isActive || block.timestamp > endTime, "Hackathon is still active");
        require(_amount <= prizePool, "Amount exceeds prize pool");
        require(_amount > 0, "Amount must be greater than 0");

        prizePool -= _amount;

        payable(_winner).transfer(
            _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
        });

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

        // Add judge rewards from sponsor contribution
        uint256 judgeReward = msg.value * getJudgeRewardPercentage() / 10000;
        judgeRewardPool += judgeReward;

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

    /**
     * @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;
    }



    // ========== 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
        ];
    }
}
"
    },
    "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];
    }
}
"
    },
    "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;

contract VotingSystem {

    uint256[] public prizeDistribution;
    uint256 public pointsPerJudge;

    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], "Prize already claimed");
        require(_isWinner(_participant), "Not a winner");

        uint256 prizeAmount = _getPrizeAmount(_participant);
        hasClaimedPrize[_participant] = true;
        payable(_participant).transfer(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];
    }
}
"
    }
  }
}}

Tags:
Proxy, Swap, Voting, Upgradeable, Factory|addr:0x13cf2f2c16fde77959478d725d23d610cf9f9c4c|verified:true|block:23632045|tx:0x5da667c58d29b5b909c896b32791aeabff13c4fd69950724af39e8ad815cccff|first_check:1761239423

Submitted on: 2025-10-23 19:10:26

Comments

Log in to comment.

No comments yet.