Linea üzerinde merkezi olmayan bir kitle fonlaması akıllı sözleşmesi oluşturun ve dağıtın

Linea üzerinde merkezi olmayan bir kitle fonlaması akıllı sözleşmesi oluşturmayı ve dağıtmayı öğrenin.

9 dk okuma
Linea üzerinde merkezi olmayan bir kitle fonlaması akıllı sözleşmesi oluşturun ve dağıtın

Kitle fonlaması, yaratıcıların destekçilerle doğrudan bağlantı kurmasına ve geleneksel finansal kurumları devre dışı bırakmasına olanak tanıyarak projelerin, toplumsal amaçların ve işletmelerin fon toplama biçimini değiştirdi. Blockchain teknolojisi, şeffaflık, merkeziyetsizlik ve değiştirilemezlik ekleyerek bu modeli geliştiriyor; hem yaratıcılar hem de katkıda bulunanlar için daha adil bir sistem sağlıyor.

Bu makale, Linea gibi EVM uyumlu blockchainler için tasarlanmış Solidity tabanlı bir Kitle Fonlaması Akıllı Sözleşmesi oluşturma sürecinde size rehberlik edecek. Sözleşme, kullanıcıların kampanya oluşturmasına, güvenli bir şekilde fon katkısında bulunmasına, hedefe ulaşıldığında fon çekmesine ve kampanya başarısız olursa geri ödeme talep etmesine olanak tanıyacak. Akıllı sözleşme otomasyonundan yararlanarak platform, aracıları ortadan kaldırır ve hesap verebilirliği zorunlu kılar.

Sözleşmenin uygulaması, özellikleri ve işlevselliği incelenerek geliştiriciler, merkeziyetsiz kitle fonlaması platformları oluşturma konusunda değerli bilgiler edinecek. İster bir blockchain meraklısı, ister geliştirici, ister girişimci olun; bu rehber, akıllı sözleşmelerin kitle fonlamasını daha güvenli, verimli ve güven gerektirmez hale getirerek nasıl devrim yaratabileceğini gösterecek.

Ön Koşullar

  • Solidity hakkında temel bir anlayışa sahip olmalısınız.

  • Bilgisayarınızda Nodejs ve Foundry kurulu olmalıdır.

Sözleşmeye Genel Bakış

Bu sözleşme şunları mümkün kılar:

  1. Kampanya oluşturma: Kullanıcılar bir hedef ve süre belirleyerek bağış kampanyaları başlatabilir.

  2. Katkılar: Kullanıcılar aktif kampanyalara fon katkısında bulunabilir.

  3. Fon çekme: Kampanya oluşturucuları, kampanya son tarihinden sonra bağış hedefi karşılanırsa fonları çekebilir.

  4. Geri ödemeler: Katkıda bulunanlar, hedef son tarih içinde karşılanmazsa katkılarını geri alabilir.

Proje Kurulumu

Bir Foundry projesi başlatmak için aşağıdaki komutu çalıştırın. Akıllı sözleşmeyi oluşturmak için Foundry çerçevesini kullanacağız.

forge init crowdfunding

Crowdfunding klasörünü VS Code'da veya favori kod düzenleyicinizde açın ve scripts/counter.s.sol, src/counter.sol ile test/counter.t.sol dosyalarını silin.

Tüm bağımlılıkları yükleyin

forge install foundry-rs/forge-std --no-commit

Kitle fonlaması akıllı sözleşmesinin tam kodu

src klasöründe bir crowdfunding.sol dosyası oluşturun ve aşağıdaki kodu dosyaya ekleyin.

// SPDX-License-Identifier: MIT
  pragma solidity 0.8.18;

  contract Crowdfunding {
      struct Campaign {
          address creator;
          uint256 goal;
          uint256 deadline;
          uint256 amountRaised;
          bool isWithdrawn;
          mapping(address => uint256) contributions;
      }

      uint256 public campaignCount = 0;
      mapping(uint256 => Campaign) public campaigns;
      event CampaignCreated(uint256 campaignId, address creator, uint256 goal, uint256 deadline);
      event ContributionMade(uint256 campaignId, address contributor, uint256 amount);
      event FundsWithdrawn(uint256 campaignId, uint256 amount);
      event RefundIssued(uint256 campaignId, address contributor, uint256 amount);

      modifier campaignExists(uint256 campaignId) {
          require(campaignId < campaignCount, "Campaign does not exist");
          _;
      }

      modifier onlyCreator(uint256 campaignId) {
          require(msg.sender == campaigns[campaignId].creator, "Only the campaign creator can perform this action");
          _;
      }

      modifier beforeDeadline(uint256 campaignId) {
          require(block.timestamp <= campaigns[campaignId].deadline, "Campaign deadline has passed");
          _;
      }

      modifier afterDeadline(uint256 campaignId) {
          require(block.timestamp > campaigns[campaignId].deadline, "Campaign deadline has not passed yet");
          _;
      }

      // Create a new crowdfunding campaign
      function createCampaign(uint256 goal, uint256 duration) external {
          require(goal > 0, "Goal must be greater than zero");
          require(duration > 0, "Duration must be greater than zero");
          Campaign storage newCampaign = campaigns[campaignCount];
          newCampaign.creator = msg.sender;
          newCampaign.goal = goal;
          newCampaign.deadline = block.timestamp + duration;
          emit CampaignCreated(campaignCount, msg.sender, goal, newCampaign.deadline);
          campaignCount++;
      }

      // Contribute funds to a campaign
      function contribute(uint256 campaignId) external payable campaignExists(campaignId) beforeDeadline(campaignId) {
          require(msg.value > 0, "Contribution must be greater than zero");
          Campaign storage campaign = campaigns[campaignId];
          campaign.amountRaised += msg.value;
          campaign.contributions[msg.sender] += msg.value;
          emit ContributionMade(campaignId, msg.sender, msg.value);
      }

      // Withdraw funds if the campaign meets its goal
      function withdrawFunds(uint256 campaignId) external campaignExists(campaignId) onlyCreator(campaignId) afterDeadline(campaignId) {
          Campaign storage campaign = campaigns[campaignId];
          require(campaign.amountRaised >= campaign.goal, "Funding goal not reached");
          require(!campaign.isWithdrawn, "Funds already withdrawn");
          campaign.isWithdrawn = true;
          (bool sent, ) = campaign.creator.call{value: campaign.amountRaised}("");
          require(sent, "Failed to send Ether");
          emit FundsWithdrawn(campaignId, campaign.amountRaised);
      }

      // Request a refund if the campaign fails
      function requestRefund(uint256 campaignId) external campaignExists(campaignId) afterDeadline(campaignId) {
          Campaign storage campaign = campaigns[campaignId];
          require(campaign.amountRaised < campaign.goal, "Campaign succeeded, refunds not allowed");
          uint256 contribution = campaign.contributions[msg.sender];
          require(contribution > 0, "No contributions found for this campaign");
          campaign.contributions[msg.sender] = 0;
          (bool sent, ) = msg.sender.call{value: contribution}("");
          require(sent, "Failed to send Ether");
          emit RefundIssued(campaignId, msg.sender, contribution);
      }
  }
Kopyala

Kodun temel özellikleri

Kampanya yapısı Sözleşme, kampanya ayrıntılarını depolamak için Campaign adlı bir struct kullanır.

   struct Campaign {  
       address creator;            // Campaign creator  
       uint256 goal;               // Fundraising goal  
       uint256 deadline;           // Deadline to achieve the goal  
       uint256 amountRaised;       // Total contributions raised  
       bool isWithdrawn;           // Tracks if funds were withdrawn  
       mapping(address \=\> uint256) contributions; // Tracks contributions by users  
   }  
Kopyala

Kampanya sayısı Her kampanya, artan bir campaignId ile benzersiz şekilde tanımlanır; bu da ölçeklenebilirliği sağlar ve birden fazla kampanyanın yönetimini kolaylaştırır.

    uint256 public campaignCount = 0;
    mapping(uint256 => Campaign) public campaigns;
Kopyala

Kampanya oluşturma

createCampaign fonksiyonu, bir kullanıcının kampanya başlatmasına olanak tanır. İki parametre alır: goal ve duration. goal, kullanıcının toplamak istediği hedef miktarı temsil ederken duration, bu hedefe ulaşmak için ayrılan süreyi saniye cinsinden belirtir.

Fonksiyon, geçersiz girişleri önlemek için hem goal hem de duration değerlerinin sıfırdan büyük olmasını sağlar. Kampanya ayrıntıları daha sonra Campaign struct'ında depolanır ve campaignId buna göre atanır.

Yeni oluşturulan kampanyaların benzersiz campaignId'lere sahip olmasını ve önceki kampanyalarla çakışmamasını sağlamak için campaignCount artırılır.

   function createCampaign(uint256 goal, uint256 duration) external {
       require(goal > 0, "Goal must be greater than zero");
       require(duration > 0, "Duration must be greater than zero");

       Campaign storage newCampaign = campaigns[campaignCount];
       newCampaign.creator = msg.sender;
       newCampaign.goal = goal;
       newCampaign.deadline = block.timestamp + duration;

       emit CampaignCreated(campaignCount, msg.sender, goal, newCampaign.deadline);
       campaignCount++;
   }
Kopyala

Katkı mekanizması

contribute fonksiyonu, kullanıcıların aktif kampanyaları fonlamasına olanak tanır. Her kampanya için benzersiz tanımlayıcı olarak işlev gören ve katkının nereye tahsis edileceğini belirleyen campaignId adlı bir parametre alır.

Fonksiyon, kullanıcıların sıfır miktarda katkıda bulunmamasını sağlar. Kampanya için amountRaised değerini takip eder ve her kullanıcı tarafından yapılan toplam katkıyı kaydeder.

  function contribute(uint256 campaignId) external payable campaignExists(campaignId) beforeDeadline(campaignId) {
       require(msg.value > 0, "Contribution must be greater than zero");

       Campaign storage campaign = campaigns[campaignId];
       campaign.amountRaised += msg.value;
       campaign.contributions[msg.sender] += msg.value;

       emit ContributionMade(campaignId, msg.sender, msg.value);
   }
Kopyala

contribute fonksiyonundaki campaignExists modifier'ı, campaignId'nin geçerli olup olmadığını doğrular ve kampanya mevcut değilse hata fırlatır.

   modifier campaignExists(uint256 campaignId) {
           require(campaignId < campaignCount, "Campaign does not exist");
           _;
       }
Kopyala

beforeDeadline modifier'ı, kampanyanın son tarihinin geçip geçmediğini doğrular ve geçmişse hata fırlatır.

   modifier beforeDeadline(uint256 campaignId) {
           require(block.timestamp <= campaigns[campaignId].deadline, "Campaign deadline has passed");
           _;
       }
Kopyala

Fon çekme

Kampanya oluşturucuları, her kampanya için benzersiz tanımlayıcı olarak işlev gören campaignId adlı bir parametre alan withdrawFunds fonksiyonunu kullanarak fon çekebilir.

Yalnızca kampanya oluşturucu fon çekebilir. Başka bir kullanıcı bunu denerse fonksiyon hata fırlatır.

Fonksiyon, bağış hedefinin karşılandığını ve fonların daha önce çekilmediğini doğrular. Hedef karşılanmamışsa veya fonlar zaten çekilmişse fonksiyon hata fırlatır.

Başarılı bir çekme işleminin ardından fonksiyon, isWithdrawn durumunu true olarak günceller ve fonları kampanya oluşturucusuna aktarır.

``Solidity function withdrawFunds(uint256 campaignId) external campaignExists(campaignId) onlyCreator(campaignId) afterDeadline(campaignId) { Campaign storage campaign = campaigns[campaignId]; require(campaign.amountRaised >= campaign.goal, "Funding goal not reached"); require(!campaign.isWithdrawn, "Funds already withdrawn");

   campaign.isWithdrawn = true;
   (bool sent, ) = campaign.creator.call{value: campaign.amountRaised}("");
   require(sent, "Failed to send Ether");

   emit FundsWithdrawn(campaignId, campaign.amountRaised);
Kopyala

}

The `onlyCreator` modifier verifies whether the caller is the campaign creator and throws an error if they are not.

``Solidity
   modifier onlyCreator(uint256 campaignId) {
         require(msg.sender == campaigns[campaignId].creator, "Only the campaign creator can perform this action");
         _;
     }
Kopyala

afterDeadline modifier'ı, kampanyanın son tarihinin geçip geçmediğini doğrular ve geçmemişse hata fırlatır.

   modifier afterDeadline(uint256 campaignId) {
           require(block.timestamp > campaigns[campaignId].deadline, "Campaign deadline has not passed yet");
           _;
       }
Kopyala

Başarısız kampanyalar için geri ödemeler

Katkıda bulunanlar, kampanya başarısız olursa katkılarını geri çekebilir. Fonksiyon, her kampanyayı benzersiz şekilde tanımlayan campaignId adlı bir parametre alır.

Kampanyanın bağış hedefini karşılamadığını ve kullanıcının katkıda bulunduğunu doğrular. Her iki koşul da karşılanırsa fonksiyon, kullanıcının katkısını sıfıra sıfırlar ve geri ödemeyi işler.

    function requestRefund(uint256 campaignId) external campaignExists(campaignId) afterDeadline(campaignId) {
           Campaign storage campaign = campaigns[campaignId];
           require(campaign.amountRaised < campaign.goal, "Campaign succeeded, refunds not allowed");
           uint256 contribution = campaign.contributions[msg.sender];
           require(contribution > 0, "No contributions found for this campaign");
           campaign.contributions[msg.sender] = 0;
           (bool sent, ) = msg.sender.call{value: contribution}("");
           require(sent, "Failed to send Ether");
           emit RefundIssued(campaignId, msg.sender, contribution);
       }
Kopyala

Olaylar Sözleşme, temel eylemler için olaylar yayar:

  • CampaignCreated: Yeni bir kampanyanın ayrıntılarını kaydeder.

  • ContributionMade: Bir kampanyaya yapılan katkıları kaydeder.

  • FundsWithdrawn: Başarılı fon çekme işlemlerini kaydeder.

  • RefundIssued: Katkıda bulunanlara verilen geri ödemeleri kaydeder.

       event CampaignCreated(uint256 campaignId, address creator, uint256 goal, uint256 deadline);
       event ContributionMade(uint256 campaignId, address contributor, uint256 amount);
       event FundsWithdrawn(uint256 campaignId, uint256 amount);
       event RefundIssued(uint256 campaignId, address contributor, uint256 amount);
Kopyala

Testler

Test klasöründe bir crowdfunding.t.sol dosyası oluşturun ve aşağıdaki kodu dosyaya ekleyin.

// SPDX-License-Identifier: UNLICENSED
  pragma solidity ^0.8.13;
  import {Test, console} from "forge-std/Test.sol";
  import {Crowdfunding} from "../src/Crowdfunding.sol";
  contract CounterTest is Test {
      Crowdfunding public crowd;
      address public user1 = makeAddr("user1");
      address public user2 = makeAddr("user2");
      address public user3 = makeAddr("user3");

      function setUp() public {
          crowd = new Crowdfunding();
      }

      function test_createCampaign() public {
          vm.startPrank(user1);
          crowd.createCampaign(10 ether, 10 hours);
      }

      function test_contributeCampaign() public {
          vm.startPrank(user1);
          crowd.createCampaign(10 ether, 10 hours);
          deal(user2, 10 ether);
          deal(user3, 10 ether);
          vm.startPrank(user2);
          crowd.contribute{value: 6 ether}(0);
           vm.startPrank(user3);
          crowd.contribute{value: 6 ether}(0);
      }

       function test_withdrawCampaign() public {
          vm.startPrank(user1);
          crowd.createCampaign(10 ether, 10 hours);
          deal(user2, 10 ether);
          deal(user3, 10 ether);
          vm.startPrank(user2);
          crowd.contribute{value: 6 ether}(0);
           vm.startPrank(user3);
          crowd.contribute{value: 6 ether}(0);
          skip(11 hours);
          console.log("b4", user1.balance);
          vm.startPrank(user1);
          crowd.withdrawFunds(0);
          console.log("after",user1.balance);
      }

      function test_requestCampaign() public {
          vm.startPrank(user1);
          crowd.createCampaign(10 ether, 10 hours);
          deal(user2, 10 ether);
          deal(user3, 10 ether);
          vm.startPrank(user2);
          crowd.contribute{value: 2 ether}(0);
           vm.startPrank(user3);
          crowd.contribute{value: 2 ether}(0);
          skip(11 hours);
          vm.startPrank(user2);
          crowd.requestRefund(0);
          vm.startPrank(user3);
          crowd.requestRefund(0);
          console.log("after",user2.balance);
          console.log("after",user3.balance);
      }

  }
Kopyala

Test paketi, Crowdfunding sözleşmesinin temel özellikleri genelinde doğru şekilde çalışmasını sağlar. Aşağıda testlerin dökümü yer almaktadır:

test_createCampaign Bu test, bir kullanıcının başarıyla bir kitle fonlaması kampanyası oluşturabileceğini doğrular. Kampanya hatasız oluşturulur ve fonksiyonun beklendiği gibi çalıştığı onaylanır.

test_contributeCampaign Bu test, birden fazla kullanıcının belirli bir kampanyaya katkıda bulunabileceğini doğrular. Testte user1 bir kampanya oluştururken user2 ve user3, Foundry'nin deal fonksiyonunu kullanarak her biri 10 ether alır. Hem user2 hem de user3 kampanyaya 6 ether katkıda bulunur ve sözleşme, kampanyanın toplanan toplam miktarını doğru şekilde günceller.

test_withdrawCampaign Bu test, kampanya oluşturucusunun kampanya bağış hedefini karşıladıktan ve son tarih geçtikten sonra fon çekebileceğini doğrular. user2 ve user3 kampanyaya katkıda bulunduktan ve 11 saat geçtikten sonra (skip kullanılarak simüle edilir), user1 toplanan fonları çekmek için withdrawFunds'ı çağırır. Fonlar başarıyla aktarılır ve sözleşmenin bakiyesi çekme işlemini yansıtır.

test_requestCampaign Bu test, kampanyanın bağış hedefini karşılayamaması durumunda katkıda bulunanların geri ödeme talep edebileceğini doğrular. user2 ve user3 kampanyaya katkıda bulunduktan ve 11 saat geçtikten sonra (skip kullanılarak simüle edilir), hem user2 hem de user3 requestRefund'ı çağırır. Her kullanıcı, katkıda bulunduğu ether'i başarıyla geri alır.

Testi çalıştırın

forge test

Linea'da Dağıtım ve Doğrulama

Gerekli ortam değişkenlerini .env dosyasına ekleyin; dosyanın projenin kök dizininde yer aldığından emin olun. Bu değişkenler, blockchain etkileşimleri için gereken hassas kimlik bilgilerini depolar.

LINEA_RPC_URL=https://linea-mainnet.infura.io/v3/ // Infura RPC endpoint for connecting to Linea 
PRIVATE_KEY=************************************************* // Wallet private key for signing transactions LINEA_API_KEY=************************* // API key for accessing Linea services via Infura
Kopyala

Aşağıdaki ayrıntıları foundry.toml dosyasına ekleyin.

   [etherscan]
   linea = { key = "${LINEA_API_KEY}", url = "https://api-sepolia.lineascan.build/api" }
Kopyala

Bir Makefile oluşturun ve aşağıdaki ayrıntıları ekleyin.

   deploy:
       forge create src/Crowdfunding.sol:Crowdfunding --rpc-url $(LINEA_RPC_URL) --private-key $(PRIVATE_KEY)

   verify:; forge verify-contract --rpc-url $(LINEA_RPC_URL) --chain linea <contract address> src/Crowdfunding.sol:Crowdfunding
Kopyala

Dağıtım

make deploy

Doğrulama

make verify

Sonuç

Bu rehber, Linea blockchain üzerinde merkeziyetsiz bir kitle fonlaması akıllı sözleşmesi oluşturma ve dağıtmanın temellerini ele aldı. Kampanya oluşturma, katkılar, para çekme ve geri ödemeler gibi temel sözleşme işlevlerini inceledik; sözleşmenin davranışını doğrulamak için testler yazdık ve çalıştırdık. Son olarak, sözleşmeyi Linea'da yayına almak için dağıtım ve doğrulama sürecini adım adım anlattık.

Blockchain geliştirmeye yönelik daha fazla uygulamalı eğitim ve derinlemesine içerik için MetaMask Developer YouTube kanalına göz atın ve Twitter üzerinden bizi takip ederek en güncel bilgilerden haberdar olun.

Yapay zeka tarafından çevrildi. Hatalar içerebilir. Lütfen her zaman bilgileri doğrulayın.

Çeviriyi değerlendir
  • Kingsley Okonkwo
    Kingsley Okonkwo

    Kingsley, a certified Ethereum Blockchain developer, serves as a technical writer at Consensys, with a special focus on developer tools. Before joining Consensys, Kingsley worked as a freelance backend developer on sevral platforms including Gigster and Braintrust. Boasting 5 years of software development experience, he's proficient with tools such as JavaScript (Node.js & React), GoLang, SQL, and Docker. Kingsley excels in producing step-by-step tutorials and how-to guides, utilizing the latest in web3 technology to help fellow developers build better dapps. Currently based in Dubai, Kingsley enjoys playing basketball and soccer in his free time

    Tüm makaleleri oku