APE Price: $0.69 (-3.18%)

Contract Diff Checker

Contract Name:
Crowdfunding

Contract Source Code:

File 1 of 1 : Crowdfunding

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

contract Crowdfunding {
    string public name;
    string public description;
    uint256 public goal;
    uint256 public deadline;
    address public owner;
    address public feeRecipient; // Address where platform fees are sent
    bool public paused;

    uint256 public constant FEE_PERCENTAGE = 169; // Represents 1.69%, in basis points (1.69 * 100)

    enum CampaignState { Active, Successful, Failed }
    CampaignState public state;

    struct Tier {
        string name;
        uint256 amount;
        uint256 backers;
    }

    struct Backer {
        uint256 totalContribution;
        mapping(uint256 => bool) fundedTiers;
    }

    Tier[] public tiers;
    mapping(address => Backer) public backers;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }

    modifier campaignOpen() {
        require(state == CampaignState.Active, "Campaign is not active.");
        _;
    }

    modifier notPaused() {
        require(!paused, "Contract is paused.");
        _;
    }

    /**
     * @notice Initializes a new crowdfunding campaign.
     * @param _owner The creator of the campaign.
     * @param _feeRecipient The address where platform fees are sent.
     * @param _name The name of the campaign.
     * @param _description The description of the campaign.
     * @param _goal The fundraising goal in wei.
     * @param _durationInDays Duration of the campaign in days.
     */
    constructor(
        address _owner,
        address _feeRecipient,
        string memory _name,
        string memory _description,
        uint256 _goal,
        uint256 _durationInDays
    ) {
        name = _name;
        description = _description;
        goal = _goal;
        deadline = block.timestamp + (_durationInDays * 1 days);
        owner = _owner;
        feeRecipient = _feeRecipient;
        state = CampaignState.Active;
    }

    /**
     * @notice Checks and updates the campaign state based on goal and deadline.
     */
    function checkAndUpdateCampaignState() internal {
        if (state == CampaignState.Active) {
            if (block.timestamp >= deadline) {
                state = address(this).balance >= goal ? CampaignState.Successful : CampaignState.Failed;            
            } else {
                state = address(this).balance >= goal ? CampaignState.Successful : CampaignState.Active;
            }
        }
    }

    /**
     * @notice Allows the campaign owner to withdraw funds if the campaign is successful.
     */
    function withdraw() public onlyOwner {
        checkAndUpdateCampaignState();
        require(state == CampaignState.Successful, "Campaign not successful.");

        uint256 balance = address(this).balance;
        require(balance > 0, "No balance to withdraw");

        uint256 fee = (balance * FEE_PERCENTAGE) / 10000; // Calculate 1.69% fee
        uint256 finalAmount = balance - fee;

        payable(feeRecipient).transfer(fee); // Transfer fee to feeRecipient
        payable(owner).transfer(finalAmount); // Transfer remaining balance to campaign owner
    }

    /**
     * @notice Allows contributors to receive a refund if the campaign failed.
     */
    function refund() public {
        checkAndUpdateCampaignState();
        require(state == CampaignState.Failed, "Refunds not available.");
        
        uint256 amount = backers[msg.sender].totalContribution;
        require(amount > 0, "No contribution to refund");

        backers[msg.sender].totalContribution = 0;
        payable(msg.sender).transfer(amount);
    }

    /**
     * @notice Allows users to contribute to a specific tier.
     * @param _tierIndex The index of the tier to contribute to.
     */
    function fund(uint256 _tierIndex) public payable campaignOpen notPaused {
        require(_tierIndex < tiers.length, "Invalid tier.");
        require(msg.value == tiers[_tierIndex].amount, "Incorrect amount.");

        tiers[_tierIndex].backers++;
        backers[msg.sender].totalContribution += msg.value;
        backers[msg.sender].fundedTiers[_tierIndex] = true;

        checkAndUpdateCampaignState();
    }

    /**
     * @notice Adds a new funding tier.
     * @param _name The name of the tier.
     * @param _amount The amount required for the tier.
     */
    function addTier(
        string memory _name,
        uint256 _amount
    ) public onlyOwner {
        require(_amount > 0, "Amount must be greater than 0.");
        tiers.push(Tier(_name, _amount, 0));
    }

    /**
     * @notice Removes a specific tier by index.
     * @param _index The index of the tier to remove.
     */
    function removeTier(uint256 _index) public onlyOwner {
        require(_index < tiers.length, "Tier does not exist.");
        tiers[_index] = tiers[tiers.length - 1];
        tiers.pop();
    }

    /**
     * @notice Retrieves the current campaign balance.
     * @return The current balance in wei.
     */
    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }

    /**
     * @notice Checks if a backer has funded a specific tier.
     * @param _backer The address of the backer.
     * @param _tierIndex The index of the tier.
     * @return True if the backer funded the tier, otherwise false.
     */
    function hasFundedTier(address _backer, uint256 _tierIndex) public view returns (bool) {
        return backers[_backer].fundedTiers[_tierIndex];
    }

    /**
     * @notice Retrieves all funding tiers.
     * @return List of all tiers.
     */
    function getTiers() public view returns (Tier[] memory) {
        return tiers;
    }

    /**
     * @notice Toggles the paused state of the contract.
     */
    function togglePause() public onlyOwner {
        paused = !paused;
    }

    /**
     * @notice Retrieves the current campaign status.
     * @return The current campaign state.
     */
    function getCampaignStatus() public view returns (CampaignState) {
        if (state == CampaignState.Active && block.timestamp > deadline) {
            return address(this).balance >= goal ? CampaignState.Successful : CampaignState.Failed;
        }
        return state;
    }

    /**
     * @notice Extends the campaign deadline.
     * @param _daysToAdd Number of days to extend.
     */
    function extendDeadline(uint256 _daysToAdd) public onlyOwner campaignOpen {
        deadline += _daysToAdd * 1 days;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):