This contract is an AlgoML version of the crowdfunding contract by Jason Weathersby, with slight modifications.
Crowdfunding applications are a great way to gain some money for a project. A typical crowdfunding application is based on three actors:
- the project creator, which defines the project, the amount of funds needed and a time window
- the donators, who donate money to the project
- a middle man, which holds the donated funds until the time window defined by the project creator ends.
Often, crowdfunding applications work with a model called all-or-nothing, which means that if the goal is not reached in time, the project creator does not receive the donated funds, and instead the donators can claim their money back.
With the aid of smart contracts, the need of a trusted middleman is eliminated, as the contract itself can keep the funds secure until the end of the donation phase.
Popular examples of crowdfunding applications are Kickstarter, Indiegogo, and GoFundMe.
The contract state is stored in the following variables:
Global variables:
start_date
is the first round in which users can donateend_date
is the last round in which users can donate, after which donors can reclaim their donations, or the receiver can claim all the donationsfund_close_date
is the first round in which the creator can delete the contractgoal
is the amount of ALGOs that must be donated for the crowdfunding to be succesful. If the goal is reached, the receiver can claim the funds, otherwise the donors can reclaim their donationsreceiver
is the receiver of the donated funds. If the goal is reached, thenreceiver
get all the donationstotal_funds
is the amount of funds currently in the escrow (those that have been donated but have not been claimed/reclaimed).
Local variables:
donated_amount
is the amount that has been donated by the user
The variables total_funds
and donated_amount
change during the contract lifetime, while the others are parameters of the contract and are immutable.
The crowdfunding contract relies on an escrow account (a stateless contract) to release funds whenever:
- the stateful contract participates in the transaction group
- the escrow does not pay any transaction fees
- the escrow does not send a rekey transaction
Any user can create the crowdfunding contract, providing as parameters the receiver
, the goal
, the round window in which users can donate funds (start_date
and end_date
), and the round after which the contract can be deleted (fund_close_date
).
Create crowdfund(int start_date, int end_date, int fund_close_date, int goal, address receiver) {
glob.start_date = start_date
glob.end_date = end_date
glob.fund_close_date = fund_close_date
glob.goal = goal
glob.receiver = receiver
glob.total_funds = 0
}
The constructor initializes total_funds
to 0, and the rest of the global variables to the corresponding actual parameters.
The Create
modifier means that the function constructs the contract, and thus can be called only from the contract creator.
Before users can donate, they must opt into the contract. When users opt into a contract, the local variables are allocated in the user account, so that the contract can record the amount of individual donations.
OptIn optin() {
loc.donated_amount = 0
}
The optin
function takes no parameters, and initializes the amount donated by the caller to 0.
The OptIn
modifier means that space for the local state variables is allocated on the caller account.
Users that have opted into the contract must be able to donate funds during the donate round window, while being sure that, if the goal is not reached, they will be able to get their funds back.
@round (glob.start_date, glob.end_date)
@pay $donated of ALGO : * -> escrow
donate() {
glob.total_funds += donated
loc.donated_amount += donated
}
The donate
function can be called by anyone who has opted into the contract, as long as the other clauses are satisfied.
In particular, the clause
@round (glob.start_date, glob.end_date)
ensures that the function can be called only in a round between start_date
and end_date
;
@pay $donated of ALGO : * -> escrow
checks that the user is sending a pay transaction to the escrow, and records the paid amount of ALGOs in the variable donated
.
The body of the function increases the amount donated by the caller and the total_funds
, by donated
ALGOs.
If the crowdfunding ends without hitting the goal, any user can reclaim the amount of funds that they have donated (or a part of it).
@assert glob.total_funds < glob.goal
@round (glob.end_date, )
@pay (, loc.donated_amount)$reclaimed of ALGO : escrow -> caller
reclaim() {
loc.donated_amount -= reclaimed
glob.total_funds -= reclaimed
}
The reclaim
function can be called by anyone who has opted into the contract, as long as the donation period is closed, and the donation goal has not been reached (and thus, as long as the funds do not belong to the receiver
).
The function body decreases the amount donated by the user and the total_funds
by the reclaimed amount. As a consequence, the user cannot reclaim more funds than donated.
If the crowdfunding is successful, the receiver can claim all the funds that were donated.
@gstate -> claimed
@assert glob.total_funds >= glob.goal
@round (glob.end_date, )
@pay glob.total_funds of ALGO : escrow -> glob.receiver
claim() {
glob.total_funds = 0
}
The claim function can be called by anyone after the donate round window has ended, by submitting a pay transaction with all the donated funds from the escrow
to the receiver
.
Since all the funds have been claimed, the body of the function sets the total funds to 0.
After the fund close date, the creator can delete the contract and close the escrow account, as long as the funds have been claimed by the receiver (if the goal is met).
@assert glob.total_funds < glob.goal
@round (glob.fund_close_date, )
@close ALGO : escrow -> creator
@from creator
Delete delete() {}
The Delete
modifier, deletes the contract after the function is called.
For the function to be called, total_funds
must be less than goal
, and therefore, either the receiver has claimed the funds, or the goal was never met.
The clause
@close ALGO : escrow -> creator
checks that the caller is closing the escrow account, sending all its ALGOs to the creator.
The project is not audited and should not be used in a production environment.