Newer
Older
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! # Parachain Crowdfunding module
//!
//! The point of this module is to allow parachain projects to offer the ability to help fund a
//! deposit for the parachain. When the parachain is retired, the funds may be returned.
//!
//! Contributing funds is permissionless. Each fund has a child-trie which stores all
//! contributors account IDs together with the amount they contributed; the root of this can then be
//! used by the parachain to allow contributors to prove that they made some particular contribution
//! to the project (e.g. to be rewarded through some token or badge). The trie is retained for later
//! (efficient) redistribution back to the contributors.
//!
//! Contributions must be of at least `MinContribution` (to account for the resources taken in
//! tracking contributions), and may never tally greater than the fund's `cap`, set and fixed at the
//! time of creation. The `create` call may be used to create a new fund. In order to do this, then
//! a deposit must be paid of the amount `SubmissionDeposit`. Substantial resources are taken on
//! the main trie in tracking a fund and this accounts for that.
//!
//! Funds may be set up during an auction period; their closing time is fixed at creation (as a
//! block number) and if the fund is not successful by the closing time, then it will become *retired*.
//! Funds may span multiple auctions, and even auctions that sell differing periods. However, for a
//! fund to be active in bidding for an auction, it *must* have had *at least one bid* since the end
//! of the last auction. Until a fund takes a further bid following the end of an auction, then it
//! will be inactive.
//!
//! Contributors may get a refund of their contributions from retired funds. After a period (`RetirementPeriod`)
//! the fund may be dissolved entirely. At this point any non-refunded contributions are considered
//! `orphaned` and are disposed of through the `OrphanedFunds` handler (which may e.g. place them
//! into the treasury).
//!
//! Funds may accept contributions at any point before their success or retirement. When a parachain
//! slot auction enters its ending period, then parachains will each place a bid; the bid will be
//! raised once per block if the parachain had additional funds contributed since the last bid.
//!
//! Funds may set their deploy data (the code hash and head data of their parachain) at any point.
//! It may only be done once and once set cannot be changed. Good procedure would be to set them
//! ahead of receiving any contributions in order that contributors may verify that their parachain
//! contains all expected functionality. However, this is not enforced and deploy data may happen
//! at any point, even after a slot has been successfully won or, indeed, never.
//!
//! Funds that are successful winners of a slot may have their slot claimed through the `onboard`
//! call. This may only be done once and must be after the deploy data has been fixed. Successful
//! funds remain tracked (in the `Funds` storage item and the associated child trie) as long as
//! the parachain remains active. Once it does not, it is up to the parachain to ensure that the
//! funds are returned to this module's fund sub-account in order that they be redistributed back to
//! contributors. *Retirement* may be initiated by any account (using the `begin_retirement` call)
//! once the parachain is removed from the its slot.
//!
//! @WARNING: For funds to be returned, it is imperative that this module's account is provided as
//! the offboarding account for the slot. In the case that a parachain supplemented these funds in
//! order to win a later auction, then it is the parachain's duty to ensure that the right amount of
//! funds ultimately end up in module's fund sub-account.
decl_module, decl_storage, decl_event, decl_error, storage::child, ensure, traits::{
Currency, Get, OnUnbalanced, WithdrawReason, ExistenceRequirement::AllowDeath
}
use sp_runtime::{ModuleId,
traits::{AccountIdConversion, Hash, Saturating, Zero, CheckedAdd}
};
use frame_support::weights::SimpleDispatchInfo;
use crate::slots;
use codec::{Encode, Decode};
use sp_core::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX;
use primitives::parachain::Id as ParaId;
const MODULE_ID: ModuleId = ModuleId(*b"py/cfund");
pub type BalanceOf<T> =
<<T as slots::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
#[allow(dead_code)]
pub type NegativeImbalanceOf<T> =
<<T as slots::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
pub trait Trait: slots::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
/// The amount to be held on deposit by the owner of a crowdfund.
type SubmissionDeposit: Get<BalanceOf<Self>>;
/// The minimum amount that may be contributed into a crowdfund. Should almost certainly be at
/// least ExistentialDeposit.
type MinContribution: Get<BalanceOf<Self>>;
/// The period of time (in blocks) after an unsuccessful crowdfund ending when
/// contributors are able to withdraw their funds. After this period, their funds are lost.
type RetirementPeriod: Get<Self::BlockNumber>;
/// What to do with funds that were not withdrawn.
type OrphanedFunds: OnUnbalanced<NegativeImbalanceOf<Self>>;
}
/// Simple index for identifying a fund.
pub type FundIndex = u32;
#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum LastContribution<BlockNumber> {
Never,
PreEnding(slots::AuctionIndex),
Ending(BlockNumber),
}
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
struct DeployData<Hash> {
code_hash: Hash,
code_size: u32,
initial_head_data: Vec<u8>,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
#[codec(dumb_trait_bound)]
pub struct FundInfo<AccountId, Balance, Hash, BlockNumber> {
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/// The parachain that this fund has funded, if there is one. As long as this is `Some`, then
/// the funds may not be withdrawn and the fund cannot be dissolved.
parachain: Option<ParaId>,
/// The owning account who placed the deposit.
owner: AccountId,
/// The amount of deposit placed.
deposit: Balance,
/// The total amount raised.
raised: Balance,
/// Block number after which the funding must have succeeded. If not successful at this number
/// then everyone may withdraw their funds.
end: BlockNumber,
/// A hard-cap on the amount that may be contributed.
cap: Balance,
/// The most recent block that this had a contribution. Determines if we make a bid or not.
/// If this is `Never`, this fund has never received a contribution.
/// If this is `PreEnding(n)`, this fund received a contribution sometime in auction
/// number `n` before the ending period.
/// If this is `Ending(n)`, this fund received a contribution during the current ending period,
/// where `n` is how far into the ending period the contribution was made.
last_contribution: LastContribution<BlockNumber>,
/// First slot in range to bid on; it's actually a LeasePeriod, but that's the same type as
/// BlockNumber.
first_slot: BlockNumber,
/// Last slot in range to bid on; it's actually a LeasePeriod, but that's the same type as
/// BlockNumber.
last_slot: BlockNumber,
/// The deployment data associated with this fund, if any. Once set it may not be reset. First
/// is the code hash, second is the code size, third is the initial head data.
deploy_data: Option<DeployData<Hash>>,
trait Store for Module<T: Trait> as Crowdfund {
/// Info on all of the funds.
Funds get(funds):
map hasher(blake2_256) FundIndex
=> Option<FundInfo<T::AccountId, BalanceOf<T>, T::Hash, T::BlockNumber>>;
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/// The total number of funds that have so far been allocated.
FundCount get(fund_count): FundIndex;
/// The funds that have had additional contributions during the last block. This is used
/// in order to determine which funds should submit new or updated bids.
NewRaise get(new_raise): Vec<FundIndex>;
/// The number of auctions that have entered into their ending period so far.
EndingsCount get(endings_count): slots::AuctionIndex;
}
}
decl_event! {
pub enum Event<T> where
<T as system::Trait>::AccountId,
Balance = BalanceOf<T>,
{
Created(FundIndex),
Contributed(AccountId, FundIndex, Balance),
Withdrew(AccountId, FundIndex, Balance),
Retiring(FundIndex),
Dissolved(FundIndex),
DeployDataFixed(FundIndex),
Onboarded(FundIndex, ParaId),
}
}
Loading full blame...