Public Project Milestone Tracking

What it does:
Tracks public project milestones and automates fund disbursement based on verified completion of milestones.

Why it matters:
Ensures accountability, reduces fund misuse, and provides transparent, auditable progress for taxpayers or DAO members funding public initiatives.

How it works:

  • Project proposals are submitted with milestones, budgets, and deadlines

  • DAO or authorized auditors verify milestone completion

  • Smart contract releases funds automatically upon verification

  • Milestone progress and fund usage are logged immutably on-chain

  • Public dashboards visualize project progress and financial flows

  • Integrates with DAOs, Smart Tax Allocation, and Community Budget Allocation systems

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

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

/**
 * @title PublicProjectMilestoneTracking
 * @author Nam
 * @notice Tracks public project milestones and releases funds based on completion verification
 */
contract PublicProjectMilestoneTracking is Ownable {

    struct Milestone {
        string description;
        uint256 amount; // amount allocated for milestone
        bool completed;
    }

    struct Project {
        address payable proposer;
        string description;
        Milestone[] milestones;
        uint256 totalBudget;
        uint256 releasedAmount;
    }

    mapping(uint256 => Project) public projects;
    uint256 public projectCount;

    mapping(address => bool) public auditors;

    // -------------------- EVENTS --------------------

    event AuditorApproved(address auditor);
    event ProjectSubmitted(uint256 indexed projectId, address proposer, uint256 totalBudget);
    event MilestoneCompleted(uint256 indexed projectId, uint256 milestoneIndex, uint256 amountReleased);

    // -------------------- AUDITOR MANAGEMENT --------------------

    function approveAuditor(address _auditor) external onlyOwner {
        auditors[_auditor] = true;
        emit AuditorApproved(_auditor);
    }

    function revokeAuditor(address _auditor) external onlyOwner {
        auditors[_auditor] = false;
    }

    // -------------------- PROJECT SUBMISSION --------------------

    function submitProject(string calldata _description, string[] calldata _milestoneDescriptions, uint256[] calldata _milestoneAmounts) external payable {
        require(_milestoneDescriptions.length == _milestoneAmounts.length, "Milestone data mismatch");

        uint256 totalBudget = 0;
        for (uint256 i = 0; i < _milestoneAmounts.length; i++) {
            totalBudget += _milestoneAmounts[i];
        }
        require(msg.value == totalBudget, "Incorrect funding sent");

        projectCount += 1;
        Project storage p = projects[projectCount];
        p.proposer = payable(msg.sender);
        p.description = _description;
        p.totalBudget = totalBudget;
        p.releasedAmount = 0;

        for (uint256 i = 0; i < _milestoneDescriptions.length; i++) {
            p.milestones.push(Milestone({
                description: _milestoneDescriptions[i],
                amount: _milestoneAmounts[i],
                completed: false
            }));
        }

        emit ProjectSubmitted(projectCount, msg.sender, totalBudget);
    }

    // -------------------- MILESTONE VERIFICATION --------------------

    function completeMilestone(uint256 _projectId, uint256 _milestoneIndex) external {
        require(auditors[msg.sender], "Not authorized auditor");

        Project storage p = projects[_projectId];
        require(_milestoneIndex < p.milestones.length, "Invalid milestone index");
        Milestone storage m = p.milestones[_milestoneIndex];
        require(!m.completed, "Already completed");

        m.completed = true;
        p.releasedAmount += m.amount;
        p.proposer.transfer(m.amount);

        emit MilestoneCompleted(_projectId, _milestoneIndex, m.amount);
    }

    // -------------------- VIEW FUNCTIONS --------------------

    function getMilestones(uint256 _projectId) external view returns (Milestone[] memory) {
        return projects[_projectId].milestones;
    }
}