Simple crowdfund
Using a child trie provides two advantages over using standard storage. First, it allows for removing the entirety of the trie is a single storage write when the fund is dispensed or dissolved. Second, it allows any contributor to prove that they contributed using a Merkle Proof.
#
GoalBuild a pallet that controls multiple token accounts, storing data in child storage.
#
Use casesA simple on-chain crowdfunding app for participants to pool funds towards a common goal.
#
OverviewThis guide demonstrates how to use one trie for each active crowdfund. Any user can start a crowdfund by specifying a goal amount for the crowdfund, an end time, and a beneficiary who will receive the pooled funds if the goal is reached by the end time. If the fund is not successful, it enters into a retirement period when contributors can reclaim their pledged funds. Finally, an unsuccessful fund can be dissolved, sending any remaining tokens to the user who dissolves it.
note
This guide assumes that developers know how to create their own Errors
and Events
according to the pallet logic they're creating.
To follow this guide from scratch, use the template pallet with importing the following dependencies which we'll be needing:
#
Steps#
1. Declaring your pallet's configuration traitsIn addition to the ubiquitous Event
type, this pallet will need:
Currency
. The currency in which the crowdfunds will be denominated.SubmissionDeposit
. The amount to be held on deposit by the owner of a crowdfund.MinContribution
. The minimum amount that may be contributed into a crowdfund.RetirementPeriod
. The period of time in blocks during which contributors are able to withdraw their funds after an unsuccessful crowdfund ending.
#
2. Create a custom metadata structKeep track of the constants in your pallet by creating a struct that stores metadata about each fund:
#
3. Declare your storage itemsOur storage items will need to keep track of which user contributed to what fund as well as how much they contributed. First, deine the following types which will be used to declare our storage items:
Now, use those types to create one StorageMap
item that tracks the funds by index and a second, StorageValue
, that keeps track of FundIndex
:
#
4. Write child trie API helper functionsFirst, we'll need to create a function that provides the pallet's dispatchables with the account ID for the pot of funds. Inside impl<T: Config> Pallet<T>
, write:
Then, we'll need to generate unique [ChildInfo][childinfo-rustdocs] IDs:
Finally, we can write the following helper functions that make use of the Child API:
pub fn contribution_put
:
- record a contribution in the associated child trie using
put
pub fn contribution_get
:
- lookup a contribution in the associated child trie using
get
pub fn contribution_kil
:
- remove a contribution from an associated child trie using
kill
pub fn crowdfund_kill
:
- remove the entire record of contributions in the associated child trie in a single storage write using
kill_storage
#
5. Write dispatchable functionsThe follow steps outline how to write the dispatchables for this pallet. After various checks within the dispatchables' logic, each function alters the Funds<T>
storage map using its associated methods. Our pallet's create
function also makes use of the FundInfo
struct previously created. Learn how to use a storage struct in this how-to guide.
#
Create a new fundfn create
:
- create an imbalance variable using
T::Currency::withdraw
- update the
Funds
storage item using theFundInfo
struct from step 2 - deposit a
Created
event
#
Contribute funds to an existing fundfn contribute
:
- perform preliminary safety checks using
ensure!
- add the contribution to the fund using
T::Currency::transfer
- record the contribution in the child trie using the helper function
contribution_put
- deposit a
Contributed
event
#
Withdraw full balance of a contributor to a fundfn withdraw
:
- perform preliminary safety checks using
ensure!
- return funds by using
T::Currency::resolve_into_existing
- calculate new balances and update storage using child trie helper functions
funds
,contribution_get
andcontribution_kill
- deposit
Withdrew
event
#
Dissolve an entire crowdfund after its retirement period has expired.fn dissolve
:
- perform preliminary safety checks using
ensure!
- allow dissolver to collect funds by using
T::Currency::resolve_creating
for dissolver to collect funds - use the child trie helper function
crowdfund_kill
to remove contributor info from storage - deposit
Dissolved
event
#
Dispense a payment to the beneficiary of a successful crowdfundfn dispense
:
- use
T::Currency::resolve_creating
for beneficiary and caller (separately) to collect funds - give initial deposit to account who calls this function as an incentive to clean up storage
- remove the fund from storage using
<Funds<T>>::remove(index);
andSelf::crowdfund_kill(index);
to remove all contributors from storage in a single write