Skip to content
crowdloan.rs 61.1 KiB
Newer Older
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
// 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/>.

Shawn Tabrizi's avatar
Shawn Tabrizi committed
//! # Parachain Crowdloaning 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.
//!
//! Successful funds remain tracked (in the `Funds` storage item and the associated child trie) as long as
//! the parachain remains active. Users can withdraw their funds once the slot is completed and funds are
//! returned to the crowdloan account.
use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
	decl_module, decl_storage, decl_event, decl_error, ensure,
	storage::child,
		Currency, ReservableCurrency, Get, OnUnbalanced, ExistenceRequirement::AllowDeath
	pallet_prelude::{Weight, DispatchResultWithPostInfo},
use frame_system::{ensure_signed, ensure_root};
use sp_runtime::{
	ModuleId, DispatchResult, RuntimeDebug, MultiSignature, MultiSigner,
		AccountIdConversion, Hash, Saturating, Zero, One, CheckedAdd, Bounded, Verify, IdentifyAccount,
use crate::traits::{Registrar, Auctioneer};
use parity_scale_codec::{Encode, Decode};
use sp_std::vec::Vec;
use primitives::v1::Id as ParaId;

type CurrencyOf<T> = <<T as Config>::Auctioneer as Auctioneer>::Currency;
type LeasePeriodOf<T> = <<T as Config>::Auctioneer as Auctioneer>::LeasePeriod;
type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
#[allow(dead_code)]
type NegativeImbalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;

type TrieIndex = u32;

pub trait WeightInfo {
	fn create() -> Weight;
	fn contribute() -> Weight;
	fn withdraw() -> Weight;
	fn dissolve(k: u32, ) -> Weight;
	fn add_memo() -> Weight;
	fn on_initialize(n: u32, ) -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
	fn create() -> Weight { 0 }
	fn contribute() -> Weight { 0 }
	fn withdraw() -> Weight { 0 }
	fn dissolve(_k: u32, ) -> Weight { 0 }
	fn edit() -> Weight { 0 }
	fn add_memo() -> Weight { 0 }
	fn on_initialize(_n: u32, ) -> Weight { 0 }
}

pub trait Config: frame_system::Config {
	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
	/// ModuleID for the crowdloan module. An appropriate value could be ```ModuleId(*b"py/cfund")```
	type ModuleId: Get<ModuleId>;

	/// The amount to be held on deposit by the depositor of a crowdloan.
	type SubmissionDeposit: Get<BalanceOf<Self>>;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
	/// The minimum amount that may be contributed into a crowdloan. Should almost certainly be at
	/// least ExistentialDeposit.
	type MinContribution: Get<BalanceOf<Self>>;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
	/// The period of time (in blocks) after an unsuccessful crowdloan 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>>;
Shawn Tabrizi's avatar
Shawn Tabrizi committed

	/// Max number of storage keys to remove per extrinsic call.
	type RemoveKeysLimit: Get<u32>;
	/// The parachain registrar type. We jus use this to ensure that only the manager of a para is able to
	/// start a crowdloan for its slot.
	type Registrar: Registrar<AccountId=Self::AccountId>;

	/// The type representing the auctioning system.
	type Auctioneer: Auctioneer<
		AccountId=Self::AccountId,
		BlockNumber=Self::BlockNumber,
		LeasePeriod=Self::BlockNumber,
	>;
	/// The maximum length for the memo attached to a crowdloan contribution.
	type MaxMemoLength: Get<u8>;

	/// Weight Information for the Extrinsics in the Pallet
	type WeightInfo: WeightInfo;
}

#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
pub enum LastContribution<BlockNumber> {
	Never,
	PreEnding(u32),
	Ending(BlockNumber),
}

/// Information on a funding effort for a pre-existing parachain. We assume that the parachain ID
/// is known as it's used for the key of the storage item for which this is the value (`Funds`).
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
#[codec(dumb_trait_bound)]
pub struct FundInfo<AccountId, Balance, BlockNumber, LeasePeriod> {
	/// True if the fund is being retired. This can only be set once and only when the current
	/// lease period is greater than the `last_period`.
	retiring: bool,
	/// The owning account who placed the deposit.
	depositor: AccountId,
	/// An optional verifier. If exists, contributions must be signed by verifier.
	verifier: Option<MultiSigner>,
	/// 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 lease period in range to bid on; it's actually a LeasePeriod, but that's the same type
	/// as BlockNumber.
	first_period: LeasePeriod,
	/// Last lease period in range to bid on; it's actually a LeasePeriod, but that's the same type
	/// as BlockNumber.
	last_period: LeasePeriod,
	/// Index used for the child trie of this fund
	trie_index: TrieIndex,
}

decl_storage! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
	trait Store for Module<T: Config> as Crowdloan {
		/// Info on all of the funds.
		Funds get(fn funds):
			map hasher(twox_64_concat) ParaId
Loading full blame...