Crowdfund¶
Warning
This is example code for learning purposes. Do not use in production without thorough review and testing.
Now, let’s explore a straightforward example for a crowdfunding contract where prospective participants can contribute funds to a campaign. If the total contribution to the campaign reaches or surpasses a predetermined funding goal, the funds will be sent to the beneficiary at the end of the campaign deadline. Participants will be refunded their respective contributions if the total funding does not reach its target goal.
1#pragma version >0.3.10
2
3###########################################################################
4## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
5###########################################################################
6
7# example of a crowd funding contract
8
9funders: HashMap[address, uint256]
10beneficiary: address
11deadline: public(uint256)
12goal: public(uint256)
13timelimit: public(uint256)
14finalized: bool
15
16# Setup global variables
17@deploy
18def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
19 self.beneficiary = _beneficiary
20 self.deadline = block.timestamp + _timelimit
21 self.timelimit = _timelimit
22 assert _goal > 0, "Goal must be non-zero"
23 self.goal = _goal
24
25# Participate in this crowdfunding campaign
26@external
27@payable
28def participate():
29 assert block.timestamp < self.deadline, "deadline has expired"
30 assert not self.finalized
31
32 self.funders[msg.sender] += msg.value
33
34# Enough money was raised! Send funds to the beneficiary
35@external
36def finalize():
37 assert block.timestamp >= self.deadline, "deadline has not expired yet"
38 assert self.balance >= self.goal, "goal has not been reached"
39 assert self.balance > 0
40 self.finalized = True
41
42 send(self.beneficiary, self.balance)
43
44# Let participants withdraw their fund
45@external
46def refund():
47 assert block.timestamp >= self.deadline and self.balance < self.goal
48 assert not self.finalized
49 assert self.funders[msg.sender] > 0
50
51 value: uint256 = self.funders[msg.sender]
52 self.funders[msg.sender] = 0
53
54 send(msg.sender, value)
Most of this code should be relatively straightforward after going through our previous examples. Let’s dive right in.
9funders: HashMap[address, uint256]
10beneficiary: address
11deadline: public(uint256)
12goal: public(uint256)
13timelimit: public(uint256)
14finalized: bool
Like other examples, we begin by initiating our variables. Some variables like
deadline, goal and timelimit are declared with the public function,
making them readable by external callers. Variables without public are, by
default, private.
Note
Unlike the existence of the function public(), there is no equivalent
private() function. Variables simply default to private if initiated
without the public() function.
The funders variable is initiated as a mapping where the key is an address,
and the value is a number representing the contribution of each participant.
The beneficiary will be the final receiver of the funds
once the crowdfunding period is over—as determined by the deadline and
timelimit variables. The goal variable is the target total contribution
of all participants.
16# Setup global variables
17@deploy
18def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
19 self.beneficiary = _beneficiary
20 self.deadline = block.timestamp + _timelimit
21 self.timelimit = _timelimit
22 assert _goal > 0, "Goal must be non-zero"
23 self.goal = _goal
Our constructor function takes 3 arguments: the beneficiary’s address, the goal
in wei value, and the difference in time from start to finish of the
crowdfunding. We initialize the arguments as contract variables with their
corresponding names. Additionally, a self.deadline is initialized to set
a definitive end time for the crowdfunding period.
Now let’s take a look at how a person can participate in the crowdfund.
25# Participate in this crowdfunding campaign
26@external
27@payable
28def participate():
29 assert block.timestamp < self.deadline, "deadline has expired"
30 assert not self.finalized
31
32 self.funders[msg.sender] += msg.value
Once again, we see the @payable decorator on a method, which allows a
person to send some ether along with a call to the method. In this case,
the participate() method accesses the sender’s address with msg.sender
and the corresponding amount sent with msg.value. The contribution is added
to the funders HashMap, which maps each participant’s address to their
total contribution amount.
34# Enough money was raised! Send funds to the beneficiary
35@external
36def finalize():
37 assert block.timestamp >= self.deadline, "deadline has not expired yet"
38 assert self.balance >= self.goal, "goal has not been reached"
39 assert self.balance > 0
40 self.finalized = True
41
42 send(self.beneficiary, self.balance)
The finalize() method is used to complete the crowdfunding process. However,
to complete the crowdfunding, the method first checks to see if the crowdfunding
period is over and that the balance has reached/passed its set goal. If those
two conditions pass, the contract sends the collected funds to the beneficiary.
Note
Notice that we have access to the total amount sent to the contract by
calling self.balance, a variable we never explicitly set. Similar to msg
and block, self.balance is a built-in variable that’s available in all
Vyper contracts.
We can finalize the campaign if all goes well, but what happens if the crowdfunding campaign isn’t successful? We’re going to need a way to refund all the participants.
44# Let participants withdraw their fund
45@external
46def refund():
47 assert block.timestamp >= self.deadline and self.balance < self.goal
48 assert not self.finalized
49 assert self.funders[msg.sender] > 0
50
51 value: uint256 = self.funders[msg.sender]
52 self.funders[msg.sender] = 0
53
54 send(msg.sender, value)
In the refund() method, we first check that the crowdfunding period is
indeed over and that the total collected balance is less than the goal with
the assert statement . If those two conditions pass, we let users get their
funds back using the withdraw pattern.