Learning Solidity with a Simple Fundraising Smart Contract

Ankit Brahmbhatt
Quick Code
Published in
8 min readFeb 21, 2019

UPDATE — Please note that this article is outdated, as Solidity has significantly evolved over the past five years. It should be read for research purposes only, as the exact solutions provided may no longer be applicable.

Once in a while, we encounter products which are not only beneficial for the creators but also benefits the community.

One such product is Kickstarter. You probably know about this, but for someone who is unfamiliar to this, its an online platform where people can raise funds for their product. They can Showcase their ideas and thoughts and depending on that, other people can contribute to projects of their interest in an exchange for future benefits that the project owner offers.

After learning about Kickstarter you probably are thinking its a perfect place for both fundraisers and contributors and there is nothing wrong with it…Right?

The Answer is NO. It certainly is a decent solution for fundraising but it faces its own unique problems

The Problem

The platform in itself has no problem whatsoever but difficulties arise when the project owner(the person who wants to raise funds…. let’s call him admin) has some kind of malicious intent.

Once the funds are transferred to the admin, they are pretty much gone from the hands of contributors. The admin can move those funds in his personal bank account.

It can even lead to a situation where the funds collected are being used for a Europe Tour and a nice Car…:)

The Solution

The main issue with the entire scenario is that funds are in the complete control of the admin. What if we can make it so that the admin can only spend the money collected if the majority of the contributors agree to it…?

In Simple terms, we can lock the funds so that the admin will require the permission of the contributors for any kind of expenditure.

But wait what..? Locking the funds ? is that even Possible ..?

Enter Solidity

Yes, It’s entirely possible to achieve the above scenario with a smart contract(a.k.a Decentralization).

A Solidity Smart Contract can be used so that the admin needs to create a Spending Request to the contributors every time he needs any amount from the funds.

How are we gonna do that? Well, that’s what I am going to show you next.

Understanding the Smart Contract

Before moving on to the code let’s understand exactly how our smart contract gonna work on a basic level.

The Smart Contract operates in the following way:

  • The admin will Start a Campaign or a Project for fundraising with a Specific Goal and Deadline.
  • Contributors will contribute to that project by sending the required Tokens(in this case ether).
  • The Admin will create a Spending Request every time he wants to use any amount from those funds
  • The Contributors will vote for that Spending Request.
  • If more than 50% of the total contributors vote for that request then the admin will get permission to use the amount mentioned in the Spending Request
  • The contributors can withdraw their ether if the required amount(Goal) was not raised within the Deadline.

Note: A spending request can only be created when the Goal is reached.

Let’s Start with the Code

There are a couple of points that I would Like to mention before:

  • This is not a production ready Smart Contract. There are a lot of things that we need to think before Production
  • I will explain to you some chunks of code but the entire contract code is loaded with comments.

Storage Variables

Let's take a look at our Storage variables First

Most of the things should be clear just from the code. Let me explain the remaining few

  • The first thing we see is a Struct definition(not a Storage Variable). It is, as the name Suggests just a definition but not a variable itself. We will create an instance of this Struct every time a new Spending request is Created.
  • The Struct definition includes various properties like description, value, recipient etc. Most of them can be easily understood but the rest will become clear further on in the contract
  • The mapping contributions is a Solidity mapping which Stores the contribution(money) of all the contributors. Consider it as a javascript object where the contributor’s Address is the key and amount he/she contributed is the value.
  • Last is a Dynamic array requests of type Request(Struct defined earlier). We need to Store all the Spending Request that has been created.

The Constructor

The Constructor is :

The Constructor for our Contract is extremely Simple

We just initialize some of our Static Storage Variables. I call them Static because they will not change with time. That's why they should be defined in the constructor as a constructor is called only once (at the time of deployment)

Another thing to notice is how we are using Block numbers for the Deadline. You can use timestamps but those timestamps can be easily manipulated or altered by the miners.

One way to counter that is by using block numbers for calculating time.

For example, let's say that the admin deployed the Contract with a deadline of a week (using the Frontend application of course). Its common knowledge that the average time it takes the network to mine a single Ethereum block is 15 seconds. So then 1 week in seconds would be 604800 s and the number of blocks that can be mined in that time Span would be 40320. So the value that would be entered for the_deadline is 40320.

Now in the constructor function, we are doing

deadline=block.number + _deadline

It means “add the time period of the campaign to the current block number”

block.number returns the block we are currently at. So if the current block number is 1000000 then the deadline of the fundraising campaign would be 1040320.

The campaign should stop accepting funds after 1040320th block.

The contribute() Function

The first thing you should notice about the contribute function is that it’s a payable function. The address which is calling this function needs to send some amount of ether. That’s exactly how a contributor contributes to the campaign.

Most of the things are explained into the code itself but there are two things that I need to mention

  • The require() statement that we are using at the beginning of the function is used to check conditions. It only allows the code to run further if it gets a truthy value inside it. Say for example the amount sent is less than the minimumContribution then the rest of the function will not execute.
  • The msg is a Global variable of Solidity which includes the data of the address who sent the transaction. That's why we are using msg.value for the amount sent through the transaction and msg.sender for the sender address

The getRefund() Function

The getRefund() function is the easiest of them all and its pretty self explanatory.

We just need to ensure a few conditions and the people should be able to get their money back. The refund should be available if the goal is not reached within the set deadline. One more Condition is that the sender should have contributed some amount in the first place.

The createSpendingRequest() Function

As mentioned earlier the admin should create a Spending request if he wants to spend some money from the funds.

Before explaining the rest of the function, I want to cover one more additional thing.

You should be able to see two words onlyAdmin and goalReached after the public keyword.

These are the modifiers I defined inside my Contract so that our code follows the DRY(Don't Repeat Yourself) principle. The code for these are :

The first modifier is to ensure that only the admin is able to create a Spending Request.

The second modifier ensures that the spending request is created only if the campaign has reached its goal(desired amount).

Moving on to the createSpendingRequest() function-

First, we instantiate a new Request Struct based on the data passed to us as function arguments. Notice how we used the keyword memory while instantiating the Requesttype variable. When we use the keyword memory like this it generally means the type of reference we are using.

For example, if we use the keyword storage it will check whether the mentioned variable exists inside the Storage and then points directly to it.

Any change that we make in the variable defined inside the function will directly affect the Storage variable.

In this case, we use the keyword memory because there is no Request type storage variable which is used on the right-hand side so it will create a copy of a Request type variable inside the memory(temporary location like your computer Ram).

It may confuse you now but things will become clear in the next function as we also use the storage keyword.

The voteForRequest() function

The First thing you should notice is that how we are using the keyword storage this time. This is exactly what I was talking about in the createSpendingRequest() function.

We are picking a specific Spending request from the requests array located into the Storage and then assigning it to a Request type variable inside the function.

That means that the variable thisRequest is a direct reference to the Spending request located at the specificindex (passed through function argument) of requests array(saved inside Storage).

Any change we make to thisRequest will also affect the Spending Request saved inside the Storage. I hope this clarifies things.

After this, we use the require() for checking the necessary conditions.

  • require(contributions[msg.sender] > 0); this checks if the person who is voting has contributed or not.
  • require(thisRequest.voters[msg.sender] == false); this checks whether the person has already voted or not. Hence making sure that one contributor votes only once.

Rest should be easy to understand.

P.S- We can access all the properties of the particular spending request using thisRequest (Can’t remember the properties ?…these are properties we declared when we defined the Structure of our Request Struct).

The makePayment() Function

Before discussing the code, we need to understand that what does this function do exactly. After a Spending request is approved, the admin can use the mentioned funds for the mentioned purpose(Like making payments to vendors and manufacturers).

So if more than 50% of the contributors vote for a Spending request, that request is approved and the admin can send that money to the mentioned recipient address.

The code should be clear to you as it is extremely similar to the previous function voteForRequest() .

We are checking whether the specific request(fetched using the index argument) has already been completed or not using the statement require(thisRequest.completed == false); . The completed property of a request will be set to true once the payment is done. It ensures that the admin uses the money only once per request

Next, we send the money thisRequest.value to the address thisRequest.recipient and then change the property thisRequest.completed to true for the Reasons mentioned just above.

Final Words

And there you have it. A basic example showing how we can decentralize fundraising. of course, there are still a lot of things that you can do and you need to do if you really want to make a production ready application but it’s still amazing.

If you want the Complete Contract Code its available on my Github repository

If you have any questions, feel free to ask them below

--

--

Ankit Brahmbhatt
Quick Code

Ankit is a programmer currently working as a freelance software engineer. Ankit has worked with multiple startups and has seen them going from an idea to a prod