Overview

In this tutorial, we will develop a crowdfunding smart contract that allows users to create funding campaigns, contribute funds, and withdraw funds once the funding goal is reached. The contract will include features such as:

  • Campaign creation with a funding goal and deadline.
  • Contribution tracking.
  • Fund withdrawal by the campaign creator upon successful funding.
  • Security features to prevent reentrancy attacks.

Prerequisites

  • Basic understanding of Solidity and Ethereum.
  • Development environment set up with tools like Remix or Truffle.
  • An Ethereum wallet for deploying the contract.

Smart Contract Code

Below is the complete code for a simple crowdfunding smart contract.

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

contract Crowdfunding {
    struct Campaign {
        string name;
        address payable creator;
        uint goal;
        uint raisedAmount;
        uint deadline;
        bool withdrawn;
    }

    mapping(uint => Campaign) public campaigns;
    uint public campaignCount;

    event CampaignCreated(uint campaignId, string name, address creator, uint goal, uint deadline);
    event Funded(uint campaignId, address contributor, uint amount);
    event Withdrawn(uint campaignId, address creator, uint amount);

    modifier onlyCreator(uint campaignId) {
        require(msg.sender == campaigns[campaignId].creator, "Not the campaign creator");
        _;
    }

    modifier campaignActive(uint campaignId) {
        require(block.timestamp < campaigns[campaignId].deadline, "Campaign has ended");
        _;
    }

    modifier campaignEnded(uint campaignId) {
        require(block.timestamp >= campaigns[campaignId].deadline, "Campaign is still active");
        _;
    }

    function createCampaign(string memory name, uint goal, uint duration) public {
        require(goal > 0, "Goal must be greater than 0");
        require(duration > 0, "Duration must be greater than 0");

        campaignCount++;
        campaigns[campaignCount] = Campaign({
            name: name,
            creator: payable(msg.sender),
            goal: goal,
            raisedAmount: 0,
            deadline: block.timestamp + duration,
            withdrawn: false
        });

        emit CampaignCreated(campaignCount, name, msg.sender, goal, block.timestamp + duration);
    }

    function fundCampaign(uint campaignId) public payable campaignActive(campaignId) {
        require(msg.value > 0, "Contribution must be greater than 0");

        campaigns[campaignId].raisedAmount += msg.value;

        emit Funded(campaignId, msg.sender, msg.value);
    }

    function withdrawFunds(uint campaignId) public onlyCreator(campaignId) campaignEnded(campaignId) {
        Campaign storage campaign = campaigns[campaignId];
        require(!campaign.withdrawn, "Funds already withdrawn");
        require(campaign.raisedAmount >= campaign.goal, "Funding goal not reached");

        campaign.withdrawn = true;
        campaign.creator.transfer(campaign.raisedAmount);

        emit Withdrawn(campaignId, campaign.creator, campaign.raisedAmount);
    }
}

Explanation of Key Components

  1. Struct for Campaign: The Campaign struct holds essential information about each crowdfunding campaign, including the campaign's name, creator, funding goal, amount raised, deadline, and withdrawal status.
  1. Mapping and State Variables:
  • campaigns: A mapping that associates a unique campaign ID with its corresponding Campaign struct.
  • campaignCount: A counter to keep track of the total number of campaigns created.
  1. Modifiers:
  • onlyCreator: Ensures that only the campaign creator can withdraw funds.
  • campaignActive: Checks if the campaign is still active.
  • campaignEnded: Checks if the campaign has ended.
  1. Functions:
  • createCampaign: Allows users to create a new campaign with a specified goal and duration.
  • fundCampaign: Enables users to contribute funds to an active campaign.
  • withdrawFunds: Allows the creator to withdraw funds if the campaign has ended and the goal has been reached.

Security Considerations

To enhance security, the following best practices are implemented:

  • Reentrancy Guard: The withdrawFunds function ensures that funds cannot be withdrawn multiple times by tracking the withdrawn status.
  • Input Validation: The contract checks for valid input parameters to prevent erroneous states.

Testing the Contract

To ensure the contract functions as intended, you should conduct the following tests:

  1. Create a Campaign: Test the createCampaign function with valid and invalid parameters.
  2. Fund a Campaign: Verify that contributions are correctly recorded and that the Funded event is emitted.
  3. Withdraw Funds: Test the withdrawFunds function under various conditions, including successful and unsuccessful attempts.

Deployment

Once testing is complete, you can deploy the contract to an Ethereum test network (e.g., Rinkeby or Ropsten) using tools like Remix or Truffle. Make sure to have sufficient test Ether in your wallet.

Conclusion

This tutorial provided a comprehensive guide to creating a crowdfunding smart contract in Solidity. By following best practices and implementing security measures, you can ensure that your contract is robust and reliable.

Learn more with useful resources