Создание платформы для благотворительности/пожертвований на блокчейне (часть 1)


Введение

В этом посте мы будем создавать смарт-контракт для платформы благотворительных пожертвований. Да, что-то вроде Gofundme, но на блокчейне.
И пользователи смогут жертвовать на отдельные кампании с помощью Ether.

Ожидания

Этот пост предполагает, что вы уже знакомы со структурой смарт-контракта и основными типами в solidity.

Необходимые инструменты

  • Remix (IDE на основе браузера)
  • Любой браузер (предпочтительно Chrome)

Обзор

Наша платформа позволит любому человеку создать благотворительную/пожертвовательную кампанию и установить для нее временные рамки или крайний срок. После создания кампании пользователь может пожертвовать на любую кампанию, которую он решит поддержать, а создатель кампании сможет вывести все средства, пожертвованные на кампанию, после истечения крайнего срока.

Хватит разговоров, давайте приступим к коду.

Сначала мы создадим файл контракта и назовем его Charity.sol. Затем заполним его приведенным ниже кодом;

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

import "@openzeppelin/contracts/utils/Counters.sol";

contract Charity {

}

Вход в полноэкранный режим Выйти из полноэкранного режима

Первая и вторая строка в приведенном выше коде определяют лицензию и версию solidity для компиляции кода. Затем мы импортируем библиотеку из openzeppelin под названием Counters.sol, которая предоставляет нам счетчики, которые могут быть только увеличены или уменьшены.
Далее мы определяем новый контракт под названием Charity.

Чтобы сориентироваться, мы создадим скелет всех необходимых нам функций, а затем реализуем их одну за другой.


contract Charity {
    using Counters for Counters.Counter;

// These are events to be emitted when specific actions are completed
    event CampaignStarted(bytes32 campaignId, address initiator);
    event WithdrawFunds(bytes32 campaignId, address initiator, uint256 amount);
    event FundsDonated(bytes32 campaignId, address donor, uint256 amount);

    // defines a variable to keep track of total number of campaigns created
    Counters.Counter public _campaignCount;

   // Campaign details to be saved
    struct Campaign {
        string title;
        string imgUrl;
        string description;
        uint256 fundsRaised;
        bool isLive;
        address initiator;
        uint256 deadline;
        uint256 balance;
    }

    // allows us to keep track of the campaigns created and it's details using a unique ID
    mapping(bytes32=>Campaign) public _campaigns;

    // allows us to keep track of the who donates to a campaign and the amount they donated
    mapping(address=>mapping(bytes32=>uint256)) public userCampaignDonations;

    // this function generated a unique ID for a campaign
//from it's title, descrition and creator address
    function generateCampaignId(address initiator, string calldata title, string calldata description) public pure returns(bytes32) { }

    // this function will be called by a user to create a new campaign
    function startCampaign( string calldata title, string calldata description, string calldata imgUrl, uint256 deadline ) public { }

    // calling this function allows users to donate to a charity campaign of their choice
    function donateToCampaign(bytes32 campaignId) public payable { }

    // returns the details of a campaign given the campaignId
    function getCampaign(bytes32 campaignId) public view returns(Campaign memory) { }

    // this function allows the creator of the campaign to withdraw all the funds donated to the campaign 
    // after the campaign has ended
    function withdrawCampaignFunds(bytes32 campaignId) public { }

}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь давайте возьмем функции одну за другой и доработаем их. Первая — функция generateCampaignId, эта функция создаст уникальный хэш из названия кампании, описания и адреса инициатора.


    function generateCampaignId(address initiator, string calldata title, string calldata description) public pure returns(bytes32) {
       bytes32 campaignId = keccak256(abi.encodePacked(title, description, initiator));
       return campaignId;
    }

Вход в полноэкранный режим Выйти из полноэкранного режима

Далее мы создаем функцию startCampaign, которая позволяет пользователю фактически создать кампанию,


    function startCampaign( string calldata title, string calldata description, string calldata imgUrl, uint256 deadline ) public {
        // first, we generate a campaignID 
        // using the title, description and the address of the initiator
        bytes32 campaignId = generateCampaignId(msg.sender, title, description);

        // get a reference to the campaign with the generated Id
        Campaign storage campaign = _campaigns[campaignId];
        // require that the campaign is not live yet.
        require(!campaign.isLive, "Campaign exists");
        // require the current time to be less than the campaign deadline
        require(block.timestamp < deadline, "Campaign ended");

        campaign.title = title;
        campaign.description = description;
        campaign.initiator = msg.sender;
        campaign.imgUrl = imgUrl;
        campaign.deadline = deadline;
        campaign.isLive = true;

        // increment the total number of charity campaigns created
        _campaignCount.increment();

        // emit an event to the blockchain
        emit CampaignStarted(campaignId, msg.sender);
    }

Войти в полноэкранный режим Выйти из полноэкранного режима

После создания кампании нам нужен способ, позволяющий пользователям пожертвовать средства на живую кампанию. Поэтому давайте создадим функцию donateToCampaign.

// allows users to donate to a charity campaign of their choice
    function donateToCampaign(bytes32 campaignId) public payable {
        // get campaign details with the given campaign
        Campaign storage campaign = _campaigns[campaignId];

        // end the campaign if the deadline is exceeded
        if(block.timestamp > campaign.deadline){
            campaign.isLive = false;
        }
        // require the campaign has not ended
        require(block.timestamp < campaign.deadline, "Campaign has ended");

        uint256 amountToDonate = msg.value;
        require(amountToDonate > 0, "Wrong ETH value");

        // increase the campaign balance by the amount donated;
        campaign.fundsRaised += amountToDonate;
        campaign.balance += amountToDonate;

        // keep track of users donation history
        userCampaignDonations[msg.sender][campaignId] = amountToDonate;

        // emit FundsDonated event
        emit FundsDonated(campaignId, msg.sender, amountToDonate);
    }

Вход в полноэкранный режим Выход из полноэкранного режима

Итак, теперь пользователи могут пожертвовать на нашу кампанию с помощью вышеуказанной функции. Но нам все еще нужен способ для создателя кампании вывести пожертвованные им Эфиры. Поэтому давайте доработаем функцию withdrawCampaignFunds.

function withdrawCampaignFunds(bytes32 campaignId) public {
        Campaign storage campaign = _campaigns[campaignId];

        // require the msg.sender is the creator of the campaign
        require(msg.sender == campaign.initiator, "Not campaign initiator");
        // require the campaign has ended
        require(!campaign.isLive, "campaign is still active");
        require(block.timestamp > campaign.deadline, "Campaign is still active");
        // require the campaign has funds to be withdrawn
        require(campaign.balance > 0, "No funds to withdraw");

        uint256 amountToWithdraw = campaign.balance;

        // zero the campaign balance
        campaign.balance = 0;

        // transfer the balance to the initiator address;
        payable(campaign.initiator).transfer(amountToWithdraw);

        // emit an event to the blockchain
        emit WithdrawFunds(campaignId, campaign.initiator, amountToWithdraw);
    }

Вход в полноэкранный режим Выход из полноэкранного режима

Круто, теперь у нас есть полный поток для процесса пожертвования.
Ниже приведен полный код для этого руководства.

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


import "@openzeppelin/contracts/utils/Counters.sol";

contract Charity {
    using Counters for Counters.Counter;

    event CampaignStarted(bytes32 campaignId, address initiator);
    event WithdrawFunds(bytes32 campaignId, address initiator, uint256 amount);
    event FundsDonated(bytes32 campaignId, address donor, uint256 amount);

    Counters.Counter public _campaignCount;

    struct Campaign {
        string title;
        string imgUrl;
        string description;
        uint256 fundsRaised;
        bool isLive;
        address initiator;
        uint256 deadline;
        uint256 balance;
    }

    mapping(bytes32=>Campaign) public _campaigns;
    mapping(address=>mapping(bytes32=>uint256)) public userCampaignDonations;

    constructor(){

    }

    function generateCampaignId(address initiator, string calldata title, string calldata description) public pure returns(bytes32) {
       bytes32 campaignId = keccak256(abi.encodePacked(title, description, initiator));
       return campaignId;
    }

    function startCampaign( string calldata title, string calldata description, string calldata imgUrl, uint256 deadline ) public {
        // generate a campaignID 
        // using the title, description and the address of the initiator
        bytes32 campaignId = generateCampaignId(msg.sender, title, description);

        // get a reference to the campaign with the generated Id
        Campaign storage campaign = _campaigns[campaignId];
        // require that the campaign is not live yet.
        require(!campaign.isLive, "Campaign exists");
        // require the current time to be less than the campaign deadline
        require(block.timestamp < deadline, "Campaign ended");

        campaign.title = title;
        campaign.description = description;
        campaign.initiator = msg.sender;
        campaign.imgUrl = imgUrl;
        campaign.deadline = deadline;
        campaign.isLive = true;

        // increment the total number of charity campaigns created
        _campaignCount.increment();

        // emit an event to the blockchain
        emit CampaignStarted(campaignId, msg.sender);
    }

    function endCampaign() public {

    }

    // allows users to donate to a charity campaign of their choice
    function donateToCampaign(bytes32 campaignId) public payable {
        // get campaign details with the given campaign
        Campaign storage campaign = _campaigns[campaignId];

        // end the campaign if the deadline is exceeded
        if(block.timestamp > campaign.deadline){
            campaign.isLive = false;
        }
        // require the campaign has not ended
        require(block.timestamp < campaign.deadline, "Campaign has ended");

        uint256 amountToDonate = msg.value;
        require(amountToDonate > 0, "Wrong ETH value");

        // increase the campaign balance by the amount donated;
        campaign.fundsRaised += amountToDonate;
        campaign.balance += amountToDonate;

        // keep track of users donation history
        userCampaignDonations[msg.sender][campaignId] = amountToDonate;

        // emit FundsDonated event
        emit FundsDonated(campaignId, msg.sender, amountToDonate);
    }

    // returns the details of a campaign given the campaignId
    function getCampaign(bytes32 campaignId) public view returns(Campaign memory) {
        return _campaigns[campaignId];
    }

    function withdrawCampaignFunds(bytes32 campaignId) public {
        Campaign storage campaign = _campaigns[campaignId];

        // require the msg.sender is the creator of the campaign
        require(msg.sender == campaign.initiator, "Not campaign initiator");
        // require the campaign has ended
        require(!campaign.isLive, "campaign is still active");
        require(block.timestamp > campaign.deadline, "Campaign is still active");
        // require the campaign has funds to be withdrawn
        require(campaign.balance > 0, "No funds to withdraw");

        uint256 amountToWithdraw = campaign.balance;

        // zero the campaign balance
        campaign.balance = 0;

        // transfer the balance to the initiator address;
        payable(campaign.initiator).transfer(amountToWithdraw);

        // emit an event to the blockchain
        emit WithdrawFunds(campaignId, campaign.initiator, amountToWithdraw);
    }

}

Вход в полноэкранный режим Выход из полноэкранного режима

Отличная работа!!! 🎉🎉🎉
Если вы смогли добраться сюда, я надеюсь, что вы смогли узнать больше о создании смарт-контрактов.
В следующей части этой серии я буду создавать пользовательский интерфейс для нашего смарт-контракта с помощью React или Next.js.

Не стесняйтесь обращаться ко мне на codementor, если у вас есть какие-либо предложения, вопросы или вы просто хотите поздороваться.

Оцените статью
Procodings.ru
Добавить комментарий