SmartPy Exercise - Let's Code Some More
A smart contract to crowdfund
Now that we tested a more complicated storage structure, let's dive into another example.
Does crowdfunding sound familiar to you?
The topic is very interesting because there are many forms in which to conduct crowdfunding. The basic idea is to finance a project or enterprise through the contributions of many, i.e. "the crowd", instead of convincing a closed group of traditional financing institutions (i.e. banks, venture capital funds, investment firms, etc.) to provide capital for a project.
We want to explore the possibilities of a decentralised crowdfunding approach.
For this, a public blockchain like Tezos is well-fitted. The openness of a public blockchain platform should be suitable as crowdfunding is in its nature an open, transparent, and public form of financing. The main idea is that a project initiator, i.e. the founder, is able to kick off a crowdfund with just a few clicks.
Each project will be represented by an individual Tezos smart contract. The smart contract has to fulfil three functions:
- send_fund: Every user with a Tezos address can use this function to participate in the crowdfund. For reasons of simplicity, each address can only make one payment;
- pay_off: The project initiator can transfer the collected funds to a different address. This can only be done if the funding target is met. For reasons of simplicity, the owner can call this function only after the deadline has passed;
- refund: In case the set funding goal is not met in a specified time frame, each participant can request a refund.
Moreover, the storage has to contain the following information:
- minAmount: This is the minimum limit to be achieved for a crowdfund to be considered successful, and
- maxTime: This value represents the deadline by which the minAmount has to be met.
Surely, you already have some ideas for the smart contract, and maybe you also started seeing possible difficulties and stumbling blocks.
So, we start with the test ensuring a functioning smart contracts fulfilling the requirements as specified before.
@sp.add_test(name = "successful Crowdfunding") def successful(): # dummy addresses owner= sp.address("tz1xowner") user1= sp.address("tz1xuser1") user2= sp.address("tz1xuser2") user3= sp.address("tz1xuser3") # minAmonut= 20 # deadline= now + 3d contract = Crowdfunding(owner, sp.tez(20), sp.timestamp(int(time.time())+259200)) scenario = sp.test_scenario() scenario+= contract scenario+= contract.send_fund().run(sender=user1, amount=sp.tez(10), now = sp.timestamp(int(time.time())+100)) scenario+= contract.send_fund().run(sender=user2, amount=sp.tez(8), now = sp.timestamp(int(time.time())+200)) # will fail because user1 already sent scenario += contract.send_fund().run(sender=user1, amount=sp.tez(12), now = sp.timestamp(int(time.time())+300), valid = False) # will fail because 2 tez missing scenario += contract.pay_off().run(sender=owner, now = sp.timestamp(int(time.time())+300), valid = False) scenario += contract.send_fund().run(sender=user3, amount=sp.tez(15), now = sp.timestamp(int(time.time())+400)) # will fail because deadline not reached scenario += contract.pay_off().run(sender=owner, now = sp.timestamp(int(time.time())+500), valid = False) scenario += contract.pay_off().run(sender=owner, now = sp.timestamp(int(time.time())+259200))
Try to implement a contract, which can pass the tests.
As you can see, you will need to implement a class called
- The storage will need to keep track on the fundings;
- The contract will have an owner, who can call the
- Several conditions must be met for
There are a few keywords that you will need but we have not seen so far:
sp.amountwill give you the amount of
mutez(in microtez) in the transaction,
- You can access the balance of the contract with
sp.nowwill give you the timestamp of the block with the transaction.
At the end, such a contract will look like:
import time import smartpy as sp class Crowdfunding(sp.Contract): def __init__(self, owner, minAmount, maxTime): self.init(funding=sp.map(tkey=sp.TAddress, tvalue=None), owner=owner, minAmount=minAmount, maxTime=maxTime) @sp.entry_point def send_fund(self, params): sp.verify(self.data.maxTime >= sp.now) sp.verify(~self.data.funding.contains(sp.sender)) self.data.funding[sp.sender]= sp.amount @sp.entry_point def pay_off(self, params): sp.verify(self.data.owner==sp.sender) sp.verify(self.data.minAmount <= sp.balance) sp.verify(self.data.maxTime <= sp.now) sp.send(self.data.owner, sp.balance) @sp.entry_point def refund(self, params): sp.verify(self.data.funding.contains(sp.sender)) sp.verify(self.data.maxTime < sp.now) sp.verify(self.data.minAmount > sp.balance) sp.send(sp.sender, self.data.funding[sp.sender]) del self.data.funding[sp.sender]
The smart contract allows:
- A user with a Tezos address to send funds - Remember, a payment can only be made once;
- To transfer the collected funds to a different address of the initiator once the funding target is met;
- The crowdfunding participants to request a refund, if the funding target isn't met;
- For the storage to contain the minAmount and maxTime.