SmartPy Exercise - Let's Code Some More
A smart contract to crowdfund
Now that we tested a more complicated storage structure, let us 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 decentralized 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, can kick off a crowdfund with just a few clicks.
The crowdfunding smart contract
Each project will be represented by an individual Tezos smart contract. The smart contract has to fulfill three functions:
send_fund
: Every user with a Tezos address can use this function to participate in the crowdfunding. 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, andmaxTime
: This value represents the deadline by which the minAmount has to be met.
Test-driven approach
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 contract, 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 Crowdfunding
:
- The storage will need to keep track onfthe fundings;
- The contract will have an owner, who can call the
pay_off
function; - Several conditions must be met for
pay_off
andrefund
.
There are a few keywords that you will need but we have not seen so far:
sp.amount
will give you the amount ofmutez
(in microtez) in the transaction,- You can access the balance of the contract with
sp.balance
, sp.now
will give you the timestamp of the block with the transaction.
In the end, such a contract will look like this:
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):
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):
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):
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, 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.