Content Moderation DAO

What it does:
Enables decentralized, community-driven moderation of published content through on-chain proposals, voting, and enforcement actions.

Why it matters:
Replaces opaque, centralized moderation with transparent rules, reduces arbitrary censorship, aligns platform standards with community values, and creates accountable moderation decisions.

How it works:

  • Content is flagged by users for review (spam, abuse, plagiarism, misinformation)

  • Moderators or token holders stake tokens to participate in moderation votes

  • Each moderation case becomes an on-chain proposal

  • Community members vote to keep, restrict, label, or remove content

  • Decisions are executed automatically by smart contracts

  • Staked tokens can be slashed for malicious or bad-faith moderation

  • All flags, votes, and outcomes are publicly auditable

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title ContentModerationDAO
 * @author Nam
 * @notice DAO-based content moderation with staking and voting
 */
contract ContentModerationDAO is Ownable {

    IERC20 public moderationToken;
    uint256 public votingDuration;
    uint256 public minimumStake;

    enum Decision {
        None,
        Keep,
        Label,
        Restrict,
        Remove
    }

    struct Case {
        uint256 contentId;
        string reason;
        uint256 startTime;
        uint256 endTime;
        uint256 votesKeep;
        uint256 votesRemove;
        bool executed;
        Decision result;
    }

    uint256 public caseCount;
    mapping(uint256 => Case) public cases;
    mapping(uint256 => mapping(address => bool)) public hasVoted;
    mapping(address => uint256) public stakedBalance;

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

    event CaseOpened(uint256 indexed caseId, uint256 indexed contentId, string reason);
    event VoteCast(uint256 indexed caseId, address indexed voter, Decision decision, uint256 weight);
    event CaseExecuted(uint256 indexed caseId, Decision result);

    // -------------------- CONSTRUCTOR --------------------

    constructor(
        address _moderationToken,
        uint256 _votingDuration,
        uint256 _minimumStake
    ) {
        moderationToken = IERC20(_moderationToken);
        votingDuration = _votingDuration;
        minimumStake = _minimumStake;
    }

    // -------------------- STAKING --------------------

    function stake(uint256 _amount) external {
        require(_amount >= minimumStake, "Stake too low");
        moderationToken.transferFrom(msg.sender, address(this), _amount);
        stakedBalance[msg.sender] += _amount;
    }

    // -------------------- CASE MANAGEMENT --------------------

    function openCase(uint256 _contentId, string calldata _reason) external {
        caseCount++;
        cases[caseCount] = Case({
            contentId: _contentId,
            reason: _reason,
            startTime: block.timestamp,
            endTime: block.timestamp + votingDuration,
            votesKeep: 0,
            votesRemove: 0,
            executed: false,
            result: Decision.None
        });

        emit CaseOpened(caseCount, _contentId, _reason);
    }

    // -------------------- VOTING --------------------

    function vote(uint256 _caseId, Decision _decision) external {
        Case storage c = cases[_caseId];
        require(block.timestamp = minimumStake, "Insufficient stake");

        uint256 weight = stakedBalance[msg.sender];
        hasVoted[_caseId][msg.sender] = true;

        if (_decision == Decision.Keep) {
            c.votesKeep += weight;
        } else if (_decision == Decision.Remove) {
            c.votesRemove += weight;
        }

        emit VoteCast(_caseId, msg.sender, _decision, weight);
    }

    // -------------------- EXECUTION --------------------

    function executeCase(uint256 _caseId) external {
        Case storage c = cases[_caseId];
        require(block.timestamp > c.endTime, "Voting active");
        require(!c.executed, "Already executed");

        c.executed = true;

        if (c.votesRemove > c.votesKeep) {
            c.result = Decision.Remove;
        } else {
            c.result = Decision.Keep;
        }

        emit CaseExecuted(_caseId, c.result);
    }

    // -------------------- ADMIN --------------------

    function updateVotingDuration(uint256 _duration) external onlyOwner {
        votingDuration = _duration;
    }

    function updateMinimumStake(uint256 _stake) external onlyOwner {
        minimumStake = _stake;
    }
}