diff --git a/runtime/common/src/crowdloan.rs b/runtime/common/src/crowdloan.rs index d496617730fa..6087bc9ab08c 100644 --- a/runtime/common/src/crowdloan.rs +++ b/runtime/common/src/crowdloan.rs @@ -55,9 +55,13 @@ use crate::{ }; use frame_support::{ ensure, - pallet_prelude::Weight, + pallet_prelude::{DispatchResult, Weight}, storage::{child, ChildTriePrefixIterator}, - traits::{Currency, ExistenceRequirement::AllowDeath, Get, ReservableCurrency}, + traits::{ + Currency, + ExistenceRequirement::{self, AllowDeath, KeepAlive}, + Get, ReservableCurrency, + }, Identity, PalletId, }; pub use pallet::*; @@ -444,81 +448,7 @@ pub mod pallet { signature: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; - - ensure!(value >= T::MinContribution::get(), Error::::ContributionTooSmall); - let mut fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - fund.raised = fund.raised.checked_add(&value).ok_or(Error::::Overflow)?; - ensure!(fund.raised <= fund.cap, Error::::CapExceeded); - - // Make sure crowdloan has not ended - let now = >::block_number(); - ensure!(now < fund.end, Error::::ContributionPeriodOver); - - // Make sure crowdloan is in a valid lease period - let now = frame_system::Pallet::::block_number(); - let (current_lease_period, _) = - T::Auctioneer::lease_period_index(now).ok_or(Error::::NoLeasePeriod)?; - ensure!(current_lease_period <= fund.first_period, Error::::ContributionPeriodOver); - - // Make sure crowdloan has not already won. - let fund_account = Self::fund_account_id(index); - ensure!( - !T::Auctioneer::has_won_an_auction(index, &fund_account), - Error::::BidOrLeaseActive - ); - - // We disallow any crowdloan contributions during the VRF Period, so that people do not sneak their - // contributions into the auction when it would not impact the outcome. - ensure!(!T::Auctioneer::auction_status(now).is_vrf(), Error::::VrfDelayInProgress); - - let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who); - - if let Some(ref verifier) = fund.verifier { - let signature = signature.ok_or(Error::::InvalidSignature)?; - let payload = (index, &who, old_balance, value); - let valid = payload.using_encoded(|encoded| { - signature.verify(encoded, &verifier.clone().into_account()) - }); - ensure!(valid, Error::::InvalidSignature); - } - - CurrencyOf::::transfer(&who, &fund_account, value, AllowDeath)?; - - let balance = old_balance.saturating_add(value); - Self::contribution_put(fund.trie_index, &who, &balance, &memo); - - if T::Auctioneer::auction_status(now).is_ending().is_some() { - match fund.last_contribution { - // In ending period; must ensure that we are in NewRaise. - LastContribution::Ending(n) if n == now => { - // do nothing - already in NewRaise - }, - _ => { - NewRaise::::append(index); - fund.last_contribution = LastContribution::Ending(now); - }, - } - } else { - let endings_count = Self::endings_count(); - match fund.last_contribution { - LastContribution::PreEnding(a) if a == endings_count => { - // Not in ending period and no auctions have ended ending since our - // previous bid which was also not in an ending period. - // `NewRaise` will contain our ID still: Do nothing. - }, - _ => { - // 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::::append(index); - fund.last_contribution = LastContribution::PreEnding(endings_count); - }, - } - } - - Funds::::insert(index, &fund); - - Self::deposit_event(Event::::Contributed(who, index, value)); - Ok(()) + Self::do_contribute(who, index, value, signature, KeepAlive) } /// Withdraw full balance of a specific contributor. @@ -706,6 +636,19 @@ pub mod pallet { Self::deposit_event(Event::::AddedToNewRaise(index)); Ok(()) } + + /// Contribute your entire balance to a crowd sale. This will transfer the entire balance of a user over to fund a parachain + /// slot. It will be withdrawable when the crowdloan has ended and the funds are unused. + #[pallet::weight(T::WeightInfo::contribute())] + pub fn contribute_all( + origin: OriginFor, + #[pallet::compact] index: ParaId, + signature: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let value = CurrencyOf::::free_balance(&who); + Self::do_contribute(who, index, value, signature, AllowDeath) + } } } @@ -784,6 +727,89 @@ impl Pallet { Ok(()) } + + fn do_contribute( + who: T::AccountId, + index: ParaId, + value: BalanceOf, + signature: Option, + existence: ExistenceRequirement, + ) -> DispatchResult { + ensure!(value >= T::MinContribution::get(), Error::::ContributionTooSmall); + let mut fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + fund.raised = fund.raised.checked_add(&value).ok_or(Error::::Overflow)?; + ensure!(fund.raised <= fund.cap, Error::::CapExceeded); + + // Make sure crowdloan has not ended + let now = >::block_number(); + ensure!(now < fund.end, Error::::ContributionPeriodOver); + + // Make sure crowdloan is in a valid lease period + let now = frame_system::Pallet::::block_number(); + let (current_lease_period, _) = + T::Auctioneer::lease_period_index(now).ok_or(Error::::NoLeasePeriod)?; + ensure!(current_lease_period <= fund.first_period, Error::::ContributionPeriodOver); + + // Make sure crowdloan has not already won. + let fund_account = Self::fund_account_id(index); + ensure!( + !T::Auctioneer::has_won_an_auction(index, &fund_account), + Error::::BidOrLeaseActive + ); + + // We disallow any crowdloan contributions during the VRF Period, so that people do not sneak their + // contributions into the auction when it would not impact the outcome. + ensure!(!T::Auctioneer::auction_status(now).is_vrf(), Error::::VrfDelayInProgress); + + let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who); + + if let Some(ref verifier) = fund.verifier { + let signature = signature.ok_or(Error::::InvalidSignature)?; + let payload = (index, &who, old_balance, value); + let valid = payload.using_encoded(|encoded| { + signature.verify(encoded, &verifier.clone().into_account()) + }); + ensure!(valid, Error::::InvalidSignature); + } + + CurrencyOf::::transfer(&who, &fund_account, value, existence)?; + + let balance = old_balance.saturating_add(value); + Self::contribution_put(fund.trie_index, &who, &balance, &memo); + + if T::Auctioneer::auction_status(now).is_ending().is_some() { + match fund.last_contribution { + // In ending period; must ensure that we are in NewRaise. + LastContribution::Ending(n) if n == now => { + // do nothing - already in NewRaise + }, + _ => { + NewRaise::::append(index); + fund.last_contribution = LastContribution::Ending(now); + }, + } + } else { + let endings_count = Self::endings_count(); + match fund.last_contribution { + LastContribution::PreEnding(a) if a == endings_count => { + // Not in ending period and no auctions have ended ending since our + // previous bid which was also not in an ending period. + // `NewRaise` will contain our ID still: Do nothing. + }, + _ => { + // 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::::append(index); + fund.last_contribution = LastContribution::PreEnding(endings_count); + }, + } + } + + Funds::::insert(index, &fund); + + Self::deposit_event(Event::::Contributed(who, index, value)); + Ok(()) + } } impl crate::traits::OnSwap for Pallet {