diff --git a/polkadot/runtime/common/src/crowdfund.rs b/polkadot/runtime/common/src/crowdloan.rs
similarity index 60%
rename from polkadot/runtime/common/src/crowdfund.rs
rename to polkadot/runtime/common/src/crowdloan.rs
index 715f6a9cfb..ad3629e8cf 100644
--- a/polkadot/runtime/common/src/crowdfund.rs
+++ b/polkadot/runtime/common/src/crowdloan.rs
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
-//! # Parachain Crowdfunding module
+//! # 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.
@@ -67,14 +67,15 @@
//! funds ultimately end up in module's fund sub-account.
use frame_support::{
- decl_module, decl_storage, decl_event, decl_error, storage::child, ensure,
+ decl_module, decl_storage, decl_event, decl_error, ensure,
+ storage::child,
traits::{
- Currency, Get, OnUnbalanced, WithdrawReasons, ExistenceRequirement::AllowDeath
+ Currency, Get, OnUnbalanced, ExistenceRequirement::AllowDeath
},
};
use frame_system::ensure_signed;
-use sp_runtime::{ModuleId,
- traits::{AccountIdConversion, Hash, Saturating, Zero, CheckedAdd}
+use sp_runtime::{ModuleId, DispatchResult,
+ traits::{AccountIdConversion, Hash, Saturating, Zero, CheckedAdd, Bounded}
};
use crate::slots;
use parity_scale_codec::{Encode, Decode};
@@ -90,22 +91,25 @@ pub type NegativeImbalanceOf =
pub trait Config: slots::Config {
type Event: From> + Into<::Event>;
- /// ModuleID for the crowdfund module. An appropriate value could be ```ModuleId(*b"py/cfund")```
+ /// ModuleID for the crowdloan module. An appropriate value could be ```ModuleId(*b"py/cfund")```
type ModuleId: Get;
- /// The amount to be held on deposit by the owner of a crowdfund.
+ /// The amount to be held on deposit by the owner of a crowdloan.
type SubmissionDeposit: Get>;
- /// The minimum amount that may be contributed into a crowdfund. Should almost certainly be at
+ /// The minimum amount that may be contributed into a crowdloan. Should almost certainly be at
/// least ExistentialDeposit.
type MinContribution: Get>;
- /// The period of time (in blocks) after an unsuccessful crowdfund ending when
+ /// 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;
/// What to do with funds that were not withdrawn.
type OrphanedFunds: OnUnbalanced>;
+
+ /// Max number of storage keys to remove per extrinsic call.
+ type RemoveKeysLimit: Get;
}
/// Simple index for identifying a fund.
@@ -164,7 +168,7 @@ pub struct FundInfo {
}
decl_storage! {
- trait Store for Module as Crowdfund {
+ trait Store for Module as Crowdloan {
/// Info on all of the funds.
Funds get(fn funds):
map hasher(twox_64_concat) FundIndex
@@ -187,7 +191,7 @@ decl_event! {
::AccountId,
Balance = BalanceOf,
{
- /// Create a new crowdfunding campaign. [fund_index]
+ /// Create a new crowdloaning campaign. [fund_index]
Created(FundIndex),
/// Contributed to a crowd sale. [who, fund_index, amount]
Contributed(AccountId, FundIndex, Balance),
@@ -195,12 +199,17 @@ decl_event! {
Withdrew(AccountId, FundIndex, Balance),
/// Fund is placed into retirement. [fund_index]
Retiring(FundIndex),
+ /// Fund is partially dissolved, i.e. there are some left over child
+ /// keys that still need to be killed. [fund_index]
+ PartiallyDissolved(FundIndex),
/// Fund is dissolved. [fund_index]
Dissolved(FundIndex),
/// The deploy data of the funded parachain is setted. [fund_index]
DeployDataFixed(FundIndex),
/// Onboarding process for a winning parachain fund is completed. [find_index, parachain_id]
Onboarded(FundIndex, ParaId),
+ /// The result of trying to submit a new bid to the Slots pallet.
+ HandleBidResult(FundIndex, DispatchResult),
}
}
@@ -231,7 +240,7 @@ decl_error! {
UnsetDeployData,
/// This fund has already been onboarded.
AlreadyOnboard,
- /// This crowdfund does not correspond to a parachain.
+ /// This crowdloan does not correspond to a parachain.
NotParachain,
/// This parachain still has its deposit. Implies that it has already been offboarded.
ParaHasDeposit,
@@ -239,11 +248,11 @@ decl_error! {
FundsNotReturned,
/// Fund has not yet retired.
FundNotRetired,
- /// The crowdfund has not yet ended.
+ /// The crowdloan has not yet ended.
FundNotEnded,
- /// There are no contributions stored in this crowdfund.
+ /// There are no contributions stored in this crowdloan.
NoContributions,
- /// This crowdfund has an active parachain and cannot be dissolved.
+ /// This crowdloan has an active parachain and cannot be dissolved.
HasActiveParachain,
/// The retirement period has not ended.
InRetirementPeriod,
@@ -258,7 +267,7 @@ decl_module! {
fn deposit_event() = default;
- /// Create a new crowdfunding campaign for a parachain slot deposit for the current auction.
+ /// Create a new crowdloaning campaign for a parachain slot deposit for the current auction.
#[weight = 100_000_000]
fn create(origin,
#[compact] cap: BalanceOf,
@@ -272,17 +281,12 @@ decl_module! {
ensure!(last_slot <= first_slot + 3u32.into(), Error::::LastSlotTooFarInFuture);
ensure!(end > >::block_number(), Error::::CannotEndInPast);
- let deposit = T::SubmissionDeposit::get();
- let transfer = WithdrawReasons::TRANSFER;
- let imb = T::Currency::withdraw(&owner, deposit, transfer, AllowDeath)?;
-
let index = FundCount::get();
let next_index = index.checked_add(1).ok_or(Error::::Overflow)?;
- FundCount::put(next_index);
- // No fees are paid here if we need to create this account; that's why we don't just
- // use the stock `transfer`.
- T::Currency::resolve_creating(&Self::fund_account_id(index), imb);
+ let deposit = T::SubmissionDeposit::get();
+ T::Currency::transfer(&owner, &Self::fund_account_id(index), deposit, AllowDeath)?;
+ FundCount::put(next_index);
>::insert(index, FundInfo {
parachain: None,
@@ -312,7 +316,7 @@ decl_module! {
fund.raised = fund.raised.checked_add(&value).ok_or(Error::::Overflow)?;
ensure!(fund.raised <= fund.cap, Error::::CapExceeded);
- // Make sure crowdfund has not ended
+ // Make sure crowdloan has not ended
let now = >::block_number();
ensure!(fund.end > now, Error::::ContributionPeriodOver);
@@ -329,7 +333,7 @@ decl_module! {
// do nothing - already in NewRaise
}
_ => {
- NewRaise::mutate(|v| v.push(index));
+ NewRaise::append(index);
fund.last_contribution = LastContribution::Ending(now);
}
}
@@ -344,7 +348,7 @@ decl_module! {
_ => {
// Not in ending period; but an auction has been ending since our previous
// bid, or we never had one to begin with. Add bid.
- NewRaise::mutate(|v| v.push(index));
+ NewRaise::append(index);
fund.last_contribution = LastContribution::PreEnding(endings_count);
}
}
@@ -393,7 +397,7 @@ decl_module! {
#[compact] index: FundIndex,
#[compact] para_id: ParaId
) {
- let _ = ensure_signed(origin)?;
+ ensure_signed(origin)?;
let mut fund = Self::funds(index).ok_or(Error::::InvalidFundIndex)?;
let DeployData { code_hash, code_size, initial_head_data }
@@ -419,7 +423,7 @@ decl_module! {
/// Note that a successful fund has lost its parachain slot, and place it into retirement.
#[weight = 0]
fn begin_retirement(origin, #[compact] index: FundIndex) {
- let _ = ensure_signed(origin)?;
+ ensure_signed(origin)?;
let mut fund = Self::funds(index).ok_or(Error::::InvalidFundIndex)?;
let parachain_id = fund.parachain.take().ok_or(Error::::NotParachain)?;
@@ -440,8 +444,8 @@ decl_module! {
/// Withdraw full balance of a contributor to an unsuccessful or off-boarded fund.
#[weight = 0]
- fn withdraw(origin, #[compact] index: FundIndex) {
- let who = ensure_signed(origin)?;
+ fn withdraw(origin, who: T::AccountId, #[compact] index: FundIndex) {
+ ensure_signed(origin)?;
let mut fund = Self::funds(index).ok_or(Error::::InvalidFundIndex)?;
ensure!(fund.parachain.is_none(), Error::::FundNotRetired);
@@ -454,10 +458,8 @@ decl_module! {
ensure!(balance > Zero::zero(), Error::::NoContributions);
// Avoid using transfer to ensure we don't pay any fees.
- let fund_account = &Self::fund_account_id(index);
- let transfer = WithdrawReasons::TRANSFER;
- let imbalance = T::Currency::withdraw(fund_account, balance, transfer, AllowDeath)?;
- let _ = T::Currency::resolve_into_existing(&who, imbalance);
+ let fund_account = Self::fund_account_id(index);
+ T::Currency::transfer(&fund_account, &who, balance, AllowDeath)?;
Self::contribution_kill(index, &who);
fund.raised = fund.raised.saturating_sub(balance);
@@ -472,7 +474,7 @@ decl_module! {
/// withdrawn into the treasury.
#[weight = 0]
fn dissolve(origin, #[compact] index: FundIndex) {
- let _ = ensure_signed(origin)?;
+ ensure_signed(origin)?;
let fund = Self::funds(index).ok_or(Error::::InvalidFundIndex)?;
ensure!(fund.parachain.is_none(), Error::::HasActiveParachain);
@@ -482,23 +484,27 @@ decl_module! {
Error::::InRetirementPeriod
);
- let account = Self::fund_account_id(index);
+ // Try killing the crowdloan child trie
+ match Self::crowdloan_kill(index) {
+ child::KillOutcome::AllRemoved => {
+ let account = Self::fund_account_id(index);
+ T::Currency::transfer(&account, &fund.owner, fund.deposit, AllowDeath)?;
- // Avoid using transfer to ensure we don't pay any fees.
- let transfer = WithdrawReasons::TRANSFER;
- let imbalance = T::Currency::withdraw(&account, fund.deposit, transfer, AllowDeath)?;
- let _ = T::Currency::resolve_into_existing(&fund.owner, imbalance);
+ // Remove all other balance from the account into orphaned funds.
+ let (imbalance, _) = T::Currency::slash(&account, BalanceOf::::max_value());
+ T::OrphanedFunds::on_unbalanced(imbalance);
- let imbalance = T::Currency::withdraw(&account, fund.raised, transfer, AllowDeath)?;
- T::OrphanedFunds::on_unbalanced(imbalance);
+ >::remove(index);
- Self::crowdfund_kill(index);
- >::remove(index);
-
- Self::deposit_event(RawEvent::Dissolved(index));
+ Self::deposit_event(RawEvent::Dissolved(index));
+ },
+ child::KillOutcome::SomeRemaining => {
+ Self::deposit_event(RawEvent::PartiallyDissolved(index));
+ }
+ }
}
- fn on_finalize(n: T::BlockNumber) {
+ fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight {
if let Some(n) = >::is_ending(n) {
let auction_index = >::auction_counter();
if n.is_zero() {
@@ -513,17 +519,21 @@ decl_module! {
sub: index,
});
- // Care needs to be taken by the crowdfund creator that this function will succeed given
- // the crowdfunding configuration. We do some checks ahead of time in crowdfund `create`.
- let _ = >::handle_bid(
+ // Care needs to be taken by the crowdloan creator that this function will succeed given
+ // the crowdloaning configuration. We do some checks ahead of time in crowdloan `create`.
+ let result = >::handle_bid(
bidder,
auction_index,
fund.first_slot,
fund.last_slot,
fund.raised,
);
+
+ Self::deposit_event(RawEvent::HandleBidResult(index, result));
}
}
+
+ 0
}
}
}
@@ -539,7 +549,7 @@ impl Module {
pub fn id_from_index(index: FundIndex) -> child::ChildInfo {
let mut buf = Vec::new();
- buf.extend_from_slice(b"crowdfund");
+ buf.extend_from_slice(b"crowdloan");
buf.extend_from_slice(&index.to_le_bytes()[..]);
child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
}
@@ -559,8 +569,8 @@ impl Module {
who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
}
- pub fn crowdfund_kill(index: FundIndex) {
- child::kill_storage(&Self::id_from_index(index), None);
+ pub fn crowdloan_kill(index: FundIndex) -> child::KillOutcome {
+ child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
}
}
@@ -570,7 +580,7 @@ mod tests {
use std::{collections::HashMap, cell::RefCell};
use frame_support::{
- impl_outer_origin, assert_ok, assert_noop, parameter_types,
+ impl_outer_origin, impl_outer_event, assert_ok, assert_noop, parameter_types,
traits::{OnInitialize, OnFinalize},
};
use sp_core::H256;
@@ -578,7 +588,7 @@ mod tests {
// The testing primitives are very useful for avoiding having to work with signatures
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
use sp_runtime::{
- Permill, testing::Header, DispatchResult,
+ Permill, testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
use crate::slots::Registrar;
@@ -587,6 +597,24 @@ mod tests {
pub enum Origin for Test {}
}
+ mod runtime_common_slots {
+ pub use crate::slots::Event;
+ }
+
+ mod runtime_common_crowdloan {
+ pub use crate::crowdloan::Event;
+ }
+
+ impl_outer_event! {
+ pub enum Event for Test {
+ frame_system,
+ pallet_balances,
+ pallet_treasury,
+ runtime_common_slots,
+ runtime_common_crowdloan,
+ }
+ }
+
// For testing the module, we construct most of a mock runtime. This means
// first constructing a configuration type (`Test`) which `impl`s each of the
// configuration traits of modules we want to use.
@@ -610,7 +638,7 @@ mod tests {
type AccountId = u64;
type Lookup = IdentityLookup;
type Header = Header;
- type Event = ();
+ type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = ();
@@ -625,7 +653,7 @@ mod tests {
}
impl pallet_balances::Config for Test {
type Balance = u64;
- type Event = ();
+ type Event = Event;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
@@ -644,7 +672,7 @@ mod tests {
type Currency = pallet_balances::Module;
type ApproveOrigin = frame_system::EnsureRoot;
type RejectOrigin = frame_system::EnsureRoot;
- type Event = ();
+ type Event = Event;
type OnSlash = ();
type ProposalBond = ProposalBond;
type ProposalBondMinimum = ProposalBondMinimum;
@@ -713,7 +741,7 @@ mod tests {
pub const EndingPeriod: u64 = 3;
}
impl slots::Config for Test {
- type Event = ();
+ type Event = Event;
type Currency = Balances;
type Parachains = TestParachains;
type LeasePeriod = LeasePeriod;
@@ -724,29 +752,31 @@ mod tests {
pub const SubmissionDeposit: u64 = 1;
pub const MinContribution: u64 = 10;
pub const RetirementPeriod: u64 = 5;
- pub const CrowdfundModuleId: ModuleId = ModuleId(*b"py/cfund");
+ pub const CrowdloanModuleId: ModuleId = ModuleId(*b"py/cfund");
+ pub const RemoveKeysLimit: u32 = 10;
}
impl Config for Test {
- type Event = ();
+ type Event = Event;
type SubmissionDeposit = SubmissionDeposit;
type MinContribution = MinContribution;
type RetirementPeriod = RetirementPeriod;
type OrphanedFunds = Treasury;
- type ModuleId = CrowdfundModuleId;
+ type ModuleId = CrowdloanModuleId;
+ type RemoveKeysLimit = RemoveKeysLimit;
}
type System = frame_system::Module;
type Balances = pallet_balances::Module;
type Slots = slots::Module;
type Treasury = pallet_treasury::Module;
- type Crowdfund = Module;
+ type Crowdloan = Module;
type RandomnessCollectiveFlip = pallet_randomness_collective_flip::Module;
use pallet_balances::Error as BalancesError;
use slots::Error as SlotsError;
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
- fn new_test_ext() -> sp_io::TestExternalities {
+ pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap();
pallet_balances::GenesisConfig::{
balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
@@ -756,7 +786,7 @@ mod tests {
fn run_to_block(n: u64) {
while System::block_number() < n {
- Crowdfund::on_finalize(System::block_number());
+ Crowdloan::on_finalize(System::block_number());
Treasury::on_finalize(System::block_number());
Slots::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
@@ -766,7 +796,7 @@ mod tests {
Balances::on_initialize(System::block_number());
Slots::on_initialize(System::block_number());
Treasury::on_initialize(System::block_number());
- Crowdfund::on_initialize(System::block_number());
+ Crowdloan::on_initialize(System::block_number());
}
}
@@ -774,21 +804,21 @@ mod tests {
fn basic_setup_works() {
new_test_ext().execute_with(|| {
assert_eq!(System::block_number(), 0);
- assert_eq!(Crowdfund::fund_count(), 0);
- assert_eq!(Crowdfund::funds(0), None);
+ assert_eq!(Crowdloan::fund_count(), 0);
+ assert_eq!(Crowdloan::funds(0), None);
let empty: Vec = Vec::new();
- assert_eq!(Crowdfund::new_raise(), empty);
- assert_eq!(Crowdfund::contribution_get(0, &1), 0);
- assert_eq!(Crowdfund::endings_count(), 0);
+ assert_eq!(Crowdloan::new_raise(), empty);
+ assert_eq!(Crowdloan::contribution_get(0, &1), 0);
+ assert_eq!(Crowdloan::endings_count(), 0);
});
}
#[test]
fn create_works() {
new_test_ext().execute_with(|| {
- // Now try to create a crowdfund campaign
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
- assert_eq!(Crowdfund::fund_count(), 1);
+ // Now try to create a crowdloan campaign
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_eq!(Crowdloan::fund_count(), 1);
// This is what the initial `fund_info` should look like
let fund_info = FundInfo {
parachain: None,
@@ -803,33 +833,33 @@ mod tests {
last_slot: 4,
deploy_data: None,
};
- assert_eq!(Crowdfund::funds(0), Some(fund_info));
+ assert_eq!(Crowdloan::funds(0), Some(fund_info));
// User has deposit removed from their free balance
assert_eq!(Balances::free_balance(1), 999);
- // Deposit is placed in crowdfund free balance
- assert_eq!(Balances::free_balance(Crowdfund::fund_account_id(0)), 1);
+ // Deposit is placed in crowdloan free balance
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 1);
// No new raise until first contribution
let empty: Vec = Vec::new();
- assert_eq!(Crowdfund::new_raise(), empty);
+ assert_eq!(Crowdloan::new_raise(), empty);
});
}
#[test]
fn create_handles_basic_errors() {
new_test_ext().execute_with(|| {
- // Cannot create a crowdfund with bad slots
+ // Cannot create a crowdloan with bad slots
assert_noop!(
- Crowdfund::create(Origin::signed(1), 1000, 4, 1, 9),
+ Crowdloan::create(Origin::signed(1), 1000, 4, 1, 9),
Error::::LastSlotBeforeFirstSlot
);
assert_noop!(
- Crowdfund::create(Origin::signed(1), 1000, 1, 5, 9),
+ Crowdloan::create(Origin::signed(1), 1000, 1, 5, 9),
Error::::LastSlotTooFarInFuture
);
- // Cannot create a crowdfund without some deposit funds
+ // Cannot create a crowdloan without some deposit funds
assert_noop!(
- Crowdfund::create(Origin::signed(1337), 1000, 1, 3, 9),
+ Crowdloan::create(Origin::signed(1337), 1000, 1, 3, 9),
BalancesError::::InsufficientBalance
);
});
@@ -838,26 +868,26 @@ mod tests {
#[test]
fn contribute_works() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ // Set up a crowdloan
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
- assert_eq!(Balances::free_balance(Crowdfund::fund_account_id(0)), 1);
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 1);
// No contributions yet
- assert_eq!(Crowdfund::contribution_get(0, &1), 0);
+ assert_eq!(Crowdloan::contribution_get(0, &1), 0);
- // User 1 contributes to their own crowdfund
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 49));
+ // User 1 contributes to their own crowdloan
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 49));
// User 1 has spent some funds to do this, transfer fees **are** taken
assert_eq!(Balances::free_balance(1), 950);
// Contributions are stored in the trie
- assert_eq!(Crowdfund::contribution_get(0, &1), 49);
- // Contributions appear in free balance of crowdfund
- assert_eq!(Balances::free_balance(Crowdfund::fund_account_id(0)), 50);
- // Crowdfund is added to NewRaise
- assert_eq!(Crowdfund::new_raise(), vec![0]);
+ assert_eq!(Crowdloan::contribution_get(0, &1), 49);
+ // Contributions appear in free balance of crowdloan
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 50);
+ // Crowdloan is added to NewRaise
+ assert_eq!(Crowdloan::new_raise(), vec![0]);
- let fund = Crowdfund::funds(0).unwrap();
+ let fund = Crowdloan::funds(0).unwrap();
// Last contribution time recorded
assert_eq!(fund.last_contribution, LastContribution::PreEnding(0));
@@ -869,34 +899,34 @@ mod tests {
fn contribute_handles_basic_errors() {
new_test_ext().execute_with(|| {
// Cannot contribute to non-existing fund
- assert_noop!(Crowdfund::contribute(Origin::signed(1), 0, 49), Error::::InvalidFundIndex);
+ assert_noop!(Crowdloan::contribute(Origin::signed(1), 0, 49), Error::::InvalidFundIndex);
// Cannot contribute below minimum contribution
- assert_noop!(Crowdfund::contribute(Origin::signed(1), 0, 9), Error::::ContributionTooSmall);
+ assert_noop!(Crowdloan::contribute(Origin::signed(1), 0, 9), Error::::ContributionTooSmall);
- // Set up a crowdfund
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 101));
+ // Set up a crowdloan
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 101));
// Cannot contribute past the limit
- assert_noop!(Crowdfund::contribute(Origin::signed(2), 0, 900), Error::::CapExceeded);
+ assert_noop!(Crowdloan::contribute(Origin::signed(2), 0, 900), Error::::CapExceeded);
// Move past end date
run_to_block(10);
// Cannot contribute to ended fund
- assert_noop!(Crowdfund::contribute(Origin::signed(1), 0, 49), Error::::ContributionPeriodOver);
+ assert_noop!(Crowdloan::contribute(Origin::signed(1), 0, 49), Error::::ContributionPeriodOver);
});
}
#[test]
fn fix_deploy_data_works() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ // Set up a crowdloan
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
// Add deploy data
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -904,7 +934,7 @@ mod tests {
vec![0].into()
));
- let fund = Crowdfund::funds(0).unwrap();
+ let fund = Crowdloan::funds(0).unwrap();
// Confirm deploy data is stored correctly
assert_eq!(
@@ -921,12 +951,12 @@ mod tests {
#[test]
fn fix_deploy_data_handles_basic_errors() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ // Set up a crowdloan
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
// Cannot set deploy data by non-owner
- assert_noop!(Crowdfund::fix_deploy_data(
+ assert_noop!(Crowdloan::fix_deploy_data(
Origin::signed(2),
0,
::Hash::default(),
@@ -936,7 +966,7 @@ mod tests {
);
// Cannot set deploy data to an invalid index
- assert_noop!(Crowdfund::fix_deploy_data(
+ assert_noop!(Crowdloan::fix_deploy_data(
Origin::signed(1),
1,
::Hash::default(),
@@ -946,7 +976,7 @@ mod tests {
);
// Cannot set deploy data after it already has been set
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -954,7 +984,7 @@ mod tests {
vec![0].into(),
));
- assert_noop!(Crowdfund::fix_deploy_data(
+ assert_noop!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -968,13 +998,13 @@ mod tests {
#[test]
fn onboard_works() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
// Add deploy data
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -982,19 +1012,19 @@ mod tests {
vec![0].into(),
));
- // Fund crowdfund
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 1000));
+ // Fund crowdloan
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));
run_to_block(10);
// Endings count incremented
- assert_eq!(Crowdfund::endings_count(), 1);
+ assert_eq!(Crowdloan::endings_count(), 1);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
- let fund = Crowdfund::funds(0).unwrap();
- // Crowdfund is now assigned a parachain id
+ let fund = Crowdloan::funds(0).unwrap();
+ // Crowdloan is now assigned a parachain id
assert_eq!(fund.parachain, Some(0.into()));
// This parachain is managed by Slots
assert_eq!(Slots::managed_ids(), vec![0.into()]);
@@ -1004,23 +1034,23 @@ mod tests {
#[test]
fn onboard_handles_basic_errors() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
- // Fund crowdfund
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 1000));
+ // Fund crowdloan
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));
run_to_block(10);
// Cannot onboard invalid fund index
- assert_noop!(Crowdfund::onboard(Origin::signed(1), 1, 0.into()), Error::::InvalidFundIndex);
- // Cannot onboard crowdfund without deploy data
- assert_noop!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()), Error::::UnsetDeployData);
+ assert_noop!(Crowdloan::onboard(Origin::signed(1), 1, 0.into()), Error::::InvalidFundIndex);
+ // Cannot onboard crowdloan without deploy data
+ assert_noop!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()), Error::::UnsetDeployData);
// Add deploy data
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -1029,26 +1059,26 @@ mod tests {
));
// Cannot onboard fund with incorrect parachain id
- assert_noop!(Crowdfund::onboard(Origin::signed(1), 0, 1.into()), SlotsError::::ParaNotOnboarding);
+ assert_noop!(Crowdloan::onboard(Origin::signed(1), 0, 1.into()), SlotsError::::ParaNotOnboarding);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
// Cannot onboard fund again
- assert_noop!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()), Error::::AlreadyOnboard);
+ assert_noop!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()), Error::::AlreadyOnboard);
});
}
#[test]
fn begin_retirement_works() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
// Add deploy data
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -1056,27 +1086,27 @@ mod tests {
vec![0].into(),
));
- // Fund crowdfund
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 1000));
+ // Fund crowdloan
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));
run_to_block(10);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
// Fund is assigned a parachain id
- let fund = Crowdfund::funds(0).unwrap();
+ let fund = Crowdloan::funds(0).unwrap();
assert_eq!(fund.parachain, Some(0.into()));
- // Off-boarding is set to the crowdfund account
- assert_eq!(Slots::offboarding(ParaId::from(0)), Crowdfund::fund_account_id(0));
+ // Off-boarding is set to the crowdloan account
+ assert_eq!(Slots::offboarding(ParaId::from(0)), Crowdloan::fund_account_id(0));
run_to_block(50);
- // Retire crowdfund to remove parachain id
- assert_ok!(Crowdfund::begin_retirement(Origin::signed(1), 0));
+ // Retire crowdloan to remove parachain id
+ assert_ok!(Crowdloan::begin_retirement(Origin::signed(1), 0));
// Fund should no longer have parachain id
- let fund = Crowdfund::funds(0).unwrap();
+ let fund = Crowdloan::funds(0).unwrap();
assert_eq!(fund.parachain, None);
});
@@ -1085,13 +1115,13 @@ mod tests {
#[test]
fn begin_retirement_handles_basic_errors() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
assert_eq!(Balances::free_balance(1), 999);
// Add deploy data
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -1099,56 +1129,56 @@ mod tests {
vec![0].into(),
));
- // Fund crowdfund
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 1000));
+ // Fund crowdloan
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));
run_to_block(10);
// Cannot retire fund that is not onboarded
- assert_noop!(Crowdfund::begin_retirement(Origin::signed(1), 0), Error::::NotParachain);
+ assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 0), Error::::NotParachain);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
// Fund is assigned a parachain id
- let fund = Crowdfund::funds(0).unwrap();
+ let fund = Crowdloan::funds(0).unwrap();
assert_eq!(fund.parachain, Some(0.into()));
// Cannot retire fund whose deposit has not been returned
- assert_noop!(Crowdfund::begin_retirement(Origin::signed(1), 0), Error::::ParaHasDeposit);
+ assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 0), Error::::ParaHasDeposit);
run_to_block(50);
// Cannot retire invalid fund index
- assert_noop!(Crowdfund::begin_retirement(Origin::signed(1), 1), Error::::InvalidFundIndex);
+ assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 1), Error::::InvalidFundIndex);
// Cannot retire twice
- assert_ok!(Crowdfund::begin_retirement(Origin::signed(1), 0));
- assert_noop!(Crowdfund::begin_retirement(Origin::signed(1), 0), Error::::NotParachain);
+ assert_ok!(Crowdloan::begin_retirement(Origin::signed(1), 0));
+ assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 0), Error::::NotParachain);
});
}
#[test]
fn withdraw_works() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
// Transfer fee is taken here
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 100));
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 200));
- assert_ok!(Crowdfund::contribute(Origin::signed(3), 0, 300));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 100));
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 200));
+ assert_ok!(Crowdloan::contribute(Origin::signed(3), 0, 300));
// Skip all the way to the end
run_to_block(50);
- // User can withdraw their full balance without fees
- assert_ok!(Crowdfund::withdraw(Origin::signed(1), 0));
+ // Anyone can trigger withdraw of a user's balance without fees
+ assert_ok!(Crowdloan::withdraw(Origin::signed(1337), 1, 0));
assert_eq!(Balances::free_balance(1), 999);
- assert_ok!(Crowdfund::withdraw(Origin::signed(2), 0));
+ assert_ok!(Crowdloan::withdraw(Origin::signed(1337), 2, 0));
assert_eq!(Balances::free_balance(2), 2000);
- assert_ok!(Crowdfund::withdraw(Origin::signed(3), 0));
+ assert_ok!(Crowdloan::withdraw(Origin::signed(1337), 3, 0));
assert_eq!(Balances::free_balance(3), 3000);
});
}
@@ -1156,37 +1186,37 @@ mod tests {
#[test]
fn withdraw_handles_basic_errors() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
// Transfer fee is taken here
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 49));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 49));
assert_eq!(Balances::free_balance(1), 950);
run_to_block(5);
// Cannot withdraw before fund ends
- assert_noop!(Crowdfund::withdraw(Origin::signed(1), 0), Error::::FundNotEnded);
+ assert_noop!(Crowdloan::withdraw(Origin::signed(1337), 1, 0), Error::::FundNotEnded);
run_to_block(10);
// Cannot withdraw if they did not contribute
- assert_noop!(Crowdfund::withdraw(Origin::signed(2), 0), Error::::NoContributions);
+ assert_noop!(Crowdloan::withdraw(Origin::signed(1337), 2, 0), Error::::NoContributions);
// Cannot withdraw from a non-existent fund
- assert_noop!(Crowdfund::withdraw(Origin::signed(1), 1), Error::::InvalidFundIndex);
+ assert_noop!(Crowdloan::withdraw(Origin::signed(1337), 2, 1), Error::::InvalidFundIndex);
});
}
#[test]
fn dissolve_works() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
// Transfer fee is taken here
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 100));
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 200));
- assert_ok!(Crowdfund::contribute(Origin::signed(3), 0, 300));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 100));
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 200));
+ assert_ok!(Crowdloan::contribute(Origin::signed(3), 0, 300));
// Skip all the way to the end
run_to_block(50);
@@ -1194,75 +1224,140 @@ mod tests {
// Check initiator's balance.
assert_eq!(Balances::free_balance(1), 899);
// Check current funds (contributions + deposit)
- assert_eq!(Balances::free_balance(Crowdfund::fund_account_id(0)), 601);
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 601);
- // Dissolve the crowdfund
- assert_ok!(Crowdfund::dissolve(Origin::signed(1), 0));
+ // Dissolve the crowdloan
+ assert_ok!(Crowdloan::dissolve(Origin::signed(1), 0));
// Fund account is emptied
- assert_eq!(Balances::free_balance(Crowdfund::fund_account_id(0)), 0);
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 0);
// Deposit is returned
assert_eq!(Balances::free_balance(1), 900);
// Treasury account is filled
assert_eq!(Balances::free_balance(Treasury::account_id()), 600);
// Storage trie is removed
- assert_eq!(Crowdfund::contribution_get(0,&0), 0);
+ assert_eq!(Crowdloan::contribution_get(0,&0), 0);
// Fund storage is removed
- assert_eq!(Crowdfund::funds(0), None);
+ assert_eq!(Crowdloan::funds(0), None);
});
}
+ #[test]
+ fn partial_dissolve_works() {
+ let mut ext = new_test_ext();
+ ext.execute_with(|| {
+ // Set up a crowdloan
+ assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 100_000, 1, 4, 9));
+
+ // Add lots of contributors, beyond what we can delete in one go.
+ for i in 0 .. 30 {
+ Balances::make_free_balance_be(&i, 300);
+ assert_ok!(Crowdloan::contribute(Origin::signed(i), 0, 100));
+ assert_eq!(Crowdloan::contribution_get(0, &i), 100);
+ }
+
+ // Skip all the way to the end
+ run_to_block(50);
+
+ // Check current funds (contributions + deposit)
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 100 * 30 + 1);
+ });
+
+ ext.commit_all().unwrap();
+ ext.execute_with(|| {
+ // Partially dissolve the crowdloan
+ assert_ok!(Crowdloan::dissolve(Origin::signed(1), 0));
+ for i in 0 .. 10 {
+ assert_eq!(Crowdloan::contribution_get(0, &i), 0);
+ }
+ for i in 10 .. 30 {
+ assert_eq!(Crowdloan::contribution_get(0, &i), 100);
+ }
+ });
+
+ ext.commit_all().unwrap();
+ ext.execute_with(|| {
+ // Partially dissolve the crowdloan, again
+ assert_ok!(Crowdloan::dissolve(Origin::signed(1), 0));
+ for i in 0 .. 20 {
+ assert_eq!(Crowdloan::contribution_get(0, &i), 0);
+ }
+ for i in 20 .. 30 {
+ assert_eq!(Crowdloan::contribution_get(0, &i), 100);
+ }
+ });
+
+ ext.commit_all().unwrap();
+ ext.execute_with(|| {
+ // Fully dissolve the crowdloan
+ assert_ok!(Crowdloan::dissolve(Origin::signed(1), 0));
+ for i in 0 .. 30 {
+ assert_eq!(Crowdloan::contribution_get(0, &i), 0);
+ }
+
+ // Fund account is emptied
+ assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 0);
+ // Deposit is returned
+ assert_eq!(Balances::free_balance(1), 201);
+ // Treasury account is filled
+ assert_eq!(Balances::free_balance(Treasury::account_id()), 100 * 30);
+ // Fund storage is removed
+ assert_eq!(Crowdloan::funds(0), None);
+ });
+ }
+
#[test]
fn dissolve_handles_basic_errors() {
new_test_ext().execute_with(|| {
- // Set up a crowdfund
+ // Set up a crowdloan
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
// Transfer fee is taken here
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 100));
- assert_ok!(Crowdfund::contribute(Origin::signed(2), 0, 200));
- assert_ok!(Crowdfund::contribute(Origin::signed(3), 0, 300));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 100));
+ assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 200));
+ assert_ok!(Crowdloan::contribute(Origin::signed(3), 0, 300));
// Cannot dissolve an invalid fund index
- assert_noop!(Crowdfund::dissolve(Origin::signed(1), 1), Error::::InvalidFundIndex);
+ assert_noop!(Crowdloan::dissolve(Origin::signed(1), 1), Error::::InvalidFundIndex);
// Cannot dissolve a fund in progress
- assert_noop!(Crowdfund::dissolve(Origin::signed(1), 0), Error::::InRetirementPeriod);
+ assert_noop!(Crowdloan::dissolve(Origin::signed(1), 0), Error::::InRetirementPeriod);
run_to_block(10);
// Onboard fund
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
0,
vec![0].into(),
));
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
// Cannot dissolve an active fund
- assert_noop!(Crowdfund::dissolve(Origin::signed(1), 0), Error::::HasActiveParachain);
+ assert_noop!(Crowdloan::dissolve(Origin::signed(1), 0), Error::::HasActiveParachain);
});
}
#[test]
fn fund_before_auction_works() {
new_test_ext().execute_with(|| {
- // Create a crowdfund before an auction is created
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 9));
+ // Create a crowdloan before an auction is created
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
// Users can already contribute
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 49));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 49));
// Fund added to NewRaise
- assert_eq!(Crowdfund::new_raise(), vec![0]);
+ assert_eq!(Crowdloan::new_raise(), vec![0]);
// Some blocks later...
run_to_block(2);
// Create an auction
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
// Add deploy data
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
@@ -1273,13 +1368,13 @@ mod tests {
run_to_block(12);
// Endings count incremented
- assert_eq!(Crowdfund::endings_count(), 1);
+ assert_eq!(Crowdloan::endings_count(), 1);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
- let fund = Crowdfund::funds(0).unwrap();
- // Crowdfund is now assigned a parachain id
+ let fund = Crowdloan::funds(0).unwrap();
+ // Crowdloan is now assigned a parachain id
assert_eq!(fund.parachain, Some(0.into()));
// This parachain is managed by Slots
assert_eq!(Slots::managed_ids(), vec![0.into()]);
@@ -1291,24 +1386,24 @@ mod tests {
new_test_ext().execute_with(|| {
// Create an auction
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
- // Create two competing crowdfunds, with end dates across multiple auctions
- // Each crowdfund is competing for the same slots, so only one can win
- assert_ok!(Crowdfund::create(Origin::signed(1), 1000, 1, 4, 30));
- assert_ok!(Crowdfund::create(Origin::signed(2), 1000, 1, 4, 30));
+ // Create two competing crowdloans, with end dates across multiple auctions
+ // Each crowdloan is competing for the same slots, so only one can win
+ assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 30));
+ assert_ok!(Crowdloan::create(Origin::signed(2), 1000, 1, 4, 30));
// Contribute to all, but more money to 0, less to 1
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 0, 300));
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 1, 200));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 300));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 1, 200));
// Add deploy data to all
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(1),
0,
::Hash::default(),
0,
vec![0].into(),
));
- assert_ok!(Crowdfund::fix_deploy_data(
+ assert_ok!(Crowdloan::fix_deploy_data(
Origin::signed(2),
1,
::Hash::default(),
@@ -1318,11 +1413,11 @@ mod tests {
// End the current auction, fund 0 wins!
run_to_block(10);
- assert_eq!(Crowdfund::endings_count(), 1);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
- let fund = Crowdfund::funds(0).unwrap();
- // Crowdfund is now assigned a parachain id
+ assert_eq!(Crowdloan::endings_count(), 1);
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
+ let fund = Crowdloan::funds(0).unwrap();
+ // Crowdloan is now assigned a parachain id
assert_eq!(fund.parachain, Some(0.into()));
// This parachain is managed by Slots
assert_eq!(Slots::managed_ids(), vec![0.into()]);
@@ -1330,18 +1425,270 @@ mod tests {
// Create a second auction
assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
// Contribute to existing funds add to NewRaise
- assert_ok!(Crowdfund::contribute(Origin::signed(1), 1, 10));
+ assert_ok!(Crowdloan::contribute(Origin::signed(1), 1, 10));
// End the current auction, fund 1 wins!
run_to_block(20);
- assert_eq!(Crowdfund::endings_count(), 2);
- // Onboard crowdfund
- assert_ok!(Crowdfund::onboard(Origin::signed(2), 1, 1.into()));
- let fund = Crowdfund::funds(1).unwrap();
- // Crowdfund is now assigned a parachain id
+ assert_eq!(Crowdloan::endings_count(), 2);
+ // Onboard crowdloan
+ assert_ok!(Crowdloan::onboard(Origin::signed(2), 1, 1.into()));
+ let fund = Crowdloan::funds(1).unwrap();
+ // Crowdloan is now assigned a parachain id
assert_eq!(fund.parachain, Some(1.into()));
// This parachain is managed by Slots
assert_eq!(Slots::managed_ids(), vec![0.into(), 1.into()]);
});
}
}
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking {
+ use super::{*, Module as Crowdloan};
+ use crate::slots::Module as Slots;
+ use frame_system::RawOrigin;
+ use frame_support::{
+ assert_ok,
+ traits::OnInitialize,
+ };
+ use sp_runtime::traits::Bounded;
+ use sp_std::prelude::*;
+
+ use frame_benchmarking::{benchmarks, whitelisted_caller, account, whitelist_account};
+
+ // TODO: replace with T::Parachains::MAX_CODE_SIZE
+ const MAX_CODE_SIZE: u32 = 10;
+ const MAX_HEAD_DATA_SIZE: u32 = 10;
+
+ fn assert_last_event(generic_event: ::Event) {
+ let events = frame_system::Module::::events();
+ let system_event: ::Event = generic_event.into();
+ // compare to the last event record
+ let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
+ assert_eq!(event, &system_event);
+ }
+
+ fn create_fund(end: T::BlockNumber) -> FundIndex {
+ let cap = BalanceOf::::max_value();
+ let lease_period_index = end / T::LeasePeriod::get();
+ let first_slot = lease_period_index;
+ let last_slot = lease_period_index + 3u32.into();
+
+ let caller = account("fund_creator", 0, 0);
+ T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value());
+
+ assert_ok!(Crowdloan::::create(RawOrigin::Signed(caller).into(), cap, first_slot, last_slot, end));
+ FundCount::get() - 1
+ }
+
+ fn contribute_fund(who: &T::AccountId, index: FundIndex) {
+ T::Currency::make_free_balance_be(&who, BalanceOf::::max_value());
+ let value = T::MinContribution::get();
+ assert_ok!(Crowdloan::::contribute(RawOrigin::Signed(who.clone()).into(), index, value));
+ }
+
+ fn worst_validation_code() -> Vec {
+ // TODO: replace with T::Parachains::MAX_CODE_SIZE
+ let mut validation_code = vec![0u8; MAX_CODE_SIZE as usize];
+ // Replace first bytes of code with "WASM_MAGIC" to pass validation test.
+ let _ = validation_code.splice(
+ ..crate::WASM_MAGIC.len(),
+ crate::WASM_MAGIC.iter().cloned(),
+ ).collect::>();
+ validation_code
+ }
+
+ fn worst_deploy_data() -> DeployData {
+ let validation_code = worst_validation_code::();
+ let code = primitives::v1::ValidationCode(validation_code);
+ // TODO: replace with T::Parachains::MAX_HEAD_DATA_SIZE
+ let head_data = HeadData(vec![0u8; MAX_HEAD_DATA_SIZE as usize]);
+
+ DeployData {
+ code_hash: T::Hashing::hash(&code.0),
+ // TODO: replace with T::Parachains::MAX_CODE_SIZE
+ code_size: MAX_CODE_SIZE,
+ initial_head_data: head_data,
+ }
+ }
+
+ fn setup_onboarding(
+ fund_index: FundIndex,
+ para_id: ParaId,
+ end_block: T::BlockNumber,
+ ) -> DispatchResult {
+ // Matches fund creator in `create_fund`
+ let fund_creator = account("fund_creator", 0, 0);
+ let DeployData { code_hash, code_size, initial_head_data } = worst_deploy_data::();
+ Crowdloan::::fix_deploy_data(
+ RawOrigin::Signed(fund_creator).into(),
+ fund_index,
+ code_hash,
+ code_size,
+ initial_head_data
+ )?;
+
+ let lease_period_index = end_block / T::LeasePeriod::get();
+ Slots::::new_auction(RawOrigin::Root.into(), end_block, lease_period_index)?;
+ let contributor: T::AccountId = account("contributor", 0, 0);
+ contribute_fund::(&contributor, fund_index);
+
+ // TODO: Probably should use on_initialize
+ //Slots::::on_initialize(end_block + T::EndingPeriod::get());
+ let onboarding_data = (lease_period_index, crate::slots::IncomingParachain::Unset(
+ crate::slots::NewBidder {
+ who: Crowdloan::::fund_account_id(fund_index),
+ sub: Default::default(),
+ }
+ ));
+ crate::slots::Onboarding::::insert(para_id, onboarding_data);
+ Ok(())
+ }
+
+ benchmarks! {
+ create {
+ let cap = BalanceOf::::max_value();
+ let first_slot = 0u32.into();
+ let last_slot = 3u32.into();
+ let end = T::BlockNumber::max_value();
+
+ let caller: T::AccountId = whitelisted_caller();
+
+ T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value());
+ }: _(RawOrigin::Signed(caller), cap, first_slot, last_slot, end)
+ verify {
+ assert_last_event::(RawEvent::Created(FundCount::get() - 1).into())
+ }
+
+ // Contribute has two arms: PreEnding and Ending, but both are equal complexity.
+ contribute {
+ let fund_index = create_fund::(100u32.into());
+ let caller: T::AccountId = whitelisted_caller();
+ let contribution = T::MinContribution::get();
+ T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value());
+ }: _(RawOrigin::Signed(caller.clone()), fund_index, contribution)
+ verify {
+ // NewRaise is appended to, so we don't need to fill it up for worst case scenario.
+ assert!(!NewRaise::get().is_empty());
+ assert_last_event::(RawEvent::Contributed(caller, fund_index, contribution).into());
+ }
+
+ fix_deploy_data {
+ let fund_index = create_fund::(100u32.into());
+ // Matches fund creator in `create_fund`
+ let caller = account("fund_creator", 0, 0);
+
+ let DeployData { code_hash, code_size, initial_head_data } = worst_deploy_data::();
+
+ whitelist_account!(caller);
+ }: _(RawOrigin::Signed(caller), fund_index, code_hash, code_size, initial_head_data)
+ verify {
+ assert_last_event::(RawEvent::DeployDataFixed(fund_index).into());
+ }
+
+ onboard {
+ let end_block: T::BlockNumber = 100u32.into();
+ let fund_index = create_fund::(end_block);
+ let para_id = Default::default();
+
+ setup_onboarding::(fund_index, para_id, end_block)?;
+
+ let caller = whitelisted_caller();
+ }: _(RawOrigin::Signed(caller), fund_index, para_id)
+ verify {
+ assert_last_event::(RawEvent::Onboarded(fund_index, para_id).into());
+ }
+
+ begin_retirement {
+ let end_block: T::BlockNumber = 100u32.into();
+ let fund_index = create_fund::(end_block);
+ let para_id = Default::default();
+
+ setup_onboarding::(fund_index, para_id, end_block)?;
+
+ let caller: T::AccountId = whitelisted_caller();
+ Crowdloan::::onboard(RawOrigin::Signed(caller.clone()).into(), fund_index, para_id)?;
+
+ // Remove deposits to look like it is off-boarded
+ crate::slots::Deposits::::remove(para_id);
+ }: _(RawOrigin::Signed(caller), fund_index)
+ verify {
+ assert_last_event::(RawEvent::Retiring(fund_index).into());
+ }
+
+ withdraw {
+ let fund_index = create_fund::(100u32.into());
+ let caller: T::AccountId = whitelisted_caller();
+ let contributor = account("contributor", 0, 0);
+ contribute_fund::(&contributor, fund_index);
+ frame_system::Module::::set_block_number(200u32.into());
+ }: _(RawOrigin::Signed(caller), contributor.clone(), fund_index)
+ verify {
+ assert_last_event::(RawEvent::Withdrew(contributor, fund_index, T::MinContribution::get()).into());
+ }
+
+ // Worst case: Dissolve removes `RemoveKeysLimit` keys, and then finishes up the dissolution of the fund.
+ dissolve {
+ let fund_index = create_fund::(100u32.into());
+
+ // Dissolve will remove at most `RemoveKeysLimit` at once.
+ for i in 0 .. T::RemoveKeysLimit::get() {
+ contribute_fund::(&account("contributor", i, 0), fund_index);
+ }
+
+ let caller: T::AccountId = whitelisted_caller();
+ frame_system::Module::::set_block_number(T::RetirementPeriod::get().saturating_add(200u32.into()));
+ }: _(RawOrigin::Signed(caller.clone()), fund_index)
+ verify {
+ assert_last_event::(RawEvent::Dissolved(fund_index).into());
+ }
+
+ // Worst case scenario: N funds are all in the `NewRaise` list, we are
+ // in the beginning of the ending period, and each fund outbids the next
+ // over the same slot.
+ on_initialize {
+ // We test the complexity over different number of new raise
+ let n in 2 .. 100;
+ let end_block: T::BlockNumber = 100u32.into();
+
+ for i in 0 .. n {
+ let fund_index = create_fund::(end_block);
+ let contributor: T::AccountId = account("contributor", i, 0);
+ let contribution = T::MinContribution::get() * (i + 1).into();
+ T::Currency::make_free_balance_be(&contributor, BalanceOf::::max_value());
+ Crowdloan::::contribute(RawOrigin::Signed(contributor).into(), fund_index, contribution)?;
+ }
+
+ let lease_period_index = end_block / T::LeasePeriod::get();
+ Slots::::new_auction(RawOrigin::Root.into(), end_block, lease_period_index)?;
+
+ assert_eq!(>::is_ending(end_block), Some(0u32.into()));
+ assert_eq!(NewRaise::get().len(), n as usize);
+ let old_endings_count = EndingsCount::get();
+ }: {
+ Crowdloan::::on_initialize(end_block);
+ } verify {
+ assert_eq!(EndingsCount::get(), old_endings_count + 1);
+ assert_last_event::(RawEvent::HandleBidResult((n - 1).into(), Ok(())).into());
+ }
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+ use crate::crowdloan::tests::{new_test_ext, Test};
+
+ #[test]
+ fn test_benchmarks() {
+ new_test_ext().execute_with(|| {
+ assert_ok!(test_benchmark_create::());
+ assert_ok!(test_benchmark_contribute::());
+ assert_ok!(test_benchmark_fix_deploy_data::());
+ assert_ok!(test_benchmark_onboard::());
+ assert_ok!(test_benchmark_begin_retirement::());
+ assert_ok!(test_benchmark_withdraw::());
+ assert_ok!(test_benchmark_dissolve::());
+ assert_ok!(test_benchmark_on_initialize::());
+ });
+ }
+ }
+}
diff --git a/polkadot/runtime/common/src/lib.rs b/polkadot/runtime/common/src/lib.rs
index c85e5224e0..45f1a8856b 100644
--- a/polkadot/runtime/common/src/lib.rs
+++ b/polkadot/runtime/common/src/lib.rs
@@ -21,7 +21,7 @@
pub mod claims;
pub mod slot_range;
pub mod slots;
-pub mod crowdfund;
+pub mod crowdloan;
pub mod purchase;
pub mod impls;
pub mod paras_sudo_wrapper;
diff --git a/polkadot/runtime/common/src/slots.rs b/polkadot/runtime/common/src/slots.rs
index 66d4d9640f..9813d48581 100644
--- a/polkadot/runtime/common/src/slots.rs
+++ b/polkadot/runtime/common/src/slots.rs
@@ -579,9 +579,9 @@ impl Module {
/// ending. An immediately subsequent call with the same argument will always return `None`.
fn check_auction_end(now: T::BlockNumber) -> Option<(WinningData, LeasePeriodOf)> {
if let Some((lease_period_index, early_end)) = >::get() {
- if early_end + T::EndingPeriod::get() == now {
+ let ending_period = T::EndingPeriod::get();
+ if early_end + ending_period == now {
// Just ended!
- let ending_period = T::EndingPeriod::get();
let offset = T::BlockNumber::decode(&mut T::Randomness::random_seed().as_ref())
.expect("secure hashes always bigger than block numbers; qed") % ending_period;
let res = >::get(offset).unwrap_or_default();