Community Savings Pool (Gamified)

What it does: A group of people deposits funds regularly into a shared smart contract. Each month, the contract randomly selects one participant to receive the pot (minus collateralized safeguards to prevent cheating).

Why it matters: Encourages savings habits and offers interest-like liquidity access without banks.

How it works:

  • Members commit to recurring deposits.

  • Deposits are locked via staking commitments.

  • Random selection uses verifiable random functions.

  • Penalties automatically apply to anyone who misses deposits.

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

/**
 * Community Savings Pool (Gamified)
 *
 * WARNING:
 * 1) Audit before mainnet.
 * 2) Use testnets first.
 * 3) Plug in a real VRF implementation (Chainlink, etc.).
 */

interface VRFConsumer {
    function requestRandom() external returns (uint256 requestId);
}

contract CommunitySavingsPool {
    struct Member {
        bool joined;
        uint256 depositedThisCycle;
        uint256 totalDeposited;
        uint256 totalWon;
        uint256 missedDeposits;
    }

    enum PoolState { Open, Active, Closed }

    address public admin;
    uint256 public cycleDuration;          // seconds (e.g., 30 days)
    uint256 public requiredDeposit;        // per cycle
    uint256 public penaltyFee;             // % (in basis points)
    uint256 public maxMembers;
    uint256 public currentCycleStart;
    uint256 public currentCycle;
    PoolState public state;

    address[] public members;
    mapping(address => Member) public memberInfo;

    // VRF randomness
    VRFConsumer public vrf;
    mapping(uint256 => bool) public randomProcessed;

    // Events
    event MemberJoined(address member);
    event DepositMade(address member, uint256 amount, uint256 cycle);
    event WinnerSelected(address winner, uint256 amount, uint256 cycle);
    event PenaltyApplied(address member, uint256 amount);
    event PoolActivated(uint256 startCycle);
    event PoolClosed();

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not authorized");
        _;
    }

    modifier onlyMember() {
        require(memberInfo[msg.sender].joined, "Not a member");
        _;
    }

    modifier inState(PoolState s) {
        require(state == s, "Invalid pool state");
        _;
    }

    constructor(
        uint256 _cycleDuration,
        uint256 _requiredDeposit,
        uint256 _penaltyFeeBps,
        uint256 _maxMembers,
        address _vrf
    ) {
        require(_maxMembers > 1, "Pool must have multiple members");
        require(_penaltyFeeBps 20%)");

        admin = msg.sender;
        cycleDuration = _cycleDuration;
        requiredDeposit = _requiredDeposit;
        penaltyFee = _penaltyFeeBps;
        maxMembers = _maxMembers;
        state = PoolState.Open;
        vrf = VRFConsumer(_vrf);
    }

    // ---- MEMBERSHIP ----
    function join() external inState(PoolState.Open) {
        require(!memberInfo[msg.sender].joined, "Already joined");
        require(members.length < maxMembers, "Pool full");

        memberInfo[msg.sender].joined = true;
        members.push(msg.sender);

        emit MemberJoined(msg.sender);

        // When full, pool becomes ready to activate
        if (members.length == maxMembers) {
            state = PoolState.Active;
            currentCycle = 1;
            currentCycleStart = block.timestamp;
            emit PoolActivated(currentCycle);
        }
    }

    // ---- DEPOSITS ----
    function deposit() external payable onlyMember inState(PoolState.Active) {
        require(block.timestamp  currentCycleStart + cycleDuration, "Cycle not finished");

        // Apply penalties and compute pot
        uint256 pot = address(this).balance;
        for (uint256 i = 0; i  members.length) {
            state = PoolState.Closed;
            emit PoolClosed();
        }
    }

    // ---- VIEW HELPERS ----
    function membersCount() external view returns (uint256) {
        return members.length;
    }

    function timeLeftInCycle() external view returns (uint256) {
        if (block.timestamp >= currentCycleStart + cycleDuration) return 0;
        return (currentCycleStart + cycleDuration) - block.timestamp;
    }

    // ---- ADMIN ----
    function closePool() external onlyAdmin {
        require(state != PoolState.Closed, "Already closed");
        state = PoolState.Closed;
        emit PoolClosed();
    }

    // Fallback
    receive() external payable {}
}