Newer
Older
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 101, None));
// Cannot contribute past the limit
assert_noop!(Crowdloan::contribute(Origin::signed(2), para, 900, None), Error::<Test>::CapExceeded);
// Move past end date
run_to_block(10);
// Cannot contribute to ended fund
assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, None), Error::<Test>::ContributionPeriodOver);
new_test_ext().execute_with(|| {
let first_slot = 1;
let last_slot = 4;
assert_ok!(TestAuctioneer::new_auction(5, 0));
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, first_slot, last_slot, 9, None));
let bidder = Crowdloan::fund_account_id(para);
run_to_block(1);
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
run_to_block(3);
assert_ok!(Crowdloan::contribute(Origin::signed(3), para, 150, None));
run_to_block(5);
assert_ok!(Crowdloan::contribute(Origin::signed(4), para, 200, None));
run_to_block(8);
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 250, None));
assert_eq!(bids(), vec![
BidPlaced { height: 5, amount: 250, bidder, para, first_slot, last_slot },
BidPlaced { height: 6, amount: 450, bidder, para, first_slot, last_slot },
BidPlaced { height: 9, amount: 700, bidder, para, first_slot, last_slot },
]);
new_test_ext().execute_with(|| {
// Set up two crowdloans
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
assert_ok!(Crowdloan::contribute(Origin::signed(3), para, 50, None));
let account_id = Crowdloan::fund_account_id(para);
// para has no reserved funds, indicating it did ot win the auction.
assert_eq!(Balances::reserved_balance(&account_id), 0);
// but there's still the funds in its balance.
assert_eq!(Balances::free_balance(&account_id), 150);
assert_eq!(Balances::free_balance(2), 1900);
assert_eq!(Balances::free_balance(3), 2950);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
assert_eq!(Balances::free_balance(&account_id), 50);
assert_eq!(Balances::free_balance(2), 2000);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 3, para));
assert_eq!(Balances::free_balance(&account_id), 0);
assert_eq!(Balances::free_balance(3), 3000);
fn dissolving_failed_without_contributions_works() {
new_test_ext().execute_with(|| {
// Set up two crowdloans
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
assert_noop!(Crowdloan::dissolve(Origin::signed(2), para), Error::<Test>::NotReadyToDissolve);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
assert_eq!(Balances::free_balance(1), 1000);
fn dissolving_failed_with_contributions_works() {
new_test_ext().execute_with(|| {
let para = new_para();
let issuance = Balances::total_issuance();
// Set up two crowdloans
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
assert_ok!(Crowdloan::contribute(Origin::signed(3), para, 50, None));
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
run_to_block(14);
assert_noop!(Crowdloan::dissolve(Origin::signed(1), para), Error::<Test>::NotReadyToDissolve);
run_to_block(15);
assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
assert_eq!(Balances::free_balance(1), 1000);
assert_eq!(Balances::total_issuance(), issuance - 50);
fn withdraw_from_finished_works() {
new_test_ext().execute_with(|| {
let para = new_para();
let account_id = Crowdloan::fund_account_id(para);
// Set up two crowdloans
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
// Fund crowdloans.
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
assert_ok!(Crowdloan::contribute(Origin::signed(3), para, 50, None));
// simulate the reserving of para's funds. this actually
// happens in the Slots pallet.
assert_ok!(Balances::reserve(&account_id, 150));
run_to_block(19);
assert_noop!(Crowdloan::withdraw(Origin::signed(2), 2, para), Error::<Test>::BidOrLeaseActive);
run_to_block(20);
// simulate the unreserving of para's funds, now that the lease expired. this actually
// happens in the Slots pallet.
Balances::unreserve(&account_id, 150);
// para has no reserved funds, indicating it did ot win the auction.
assert_eq!(Balances::reserved_balance(&account_id), 0);
// but there's still the funds in its balance.
assert_eq!(Balances::free_balance(&account_id), 150);
assert_eq!(Balances::free_balance(2), 1900);
assert_eq!(Balances::free_balance(3), 2950);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
assert_eq!(Balances::free_balance(&account_id), 50);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 3, para));
assert_eq!(Balances::free_balance(&account_id), 0);
fn dissolving_finished_without_contributions_works() {
new_test_ext().execute_with(|| {
// Set up two crowdloans
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
// Fund crowdloans.
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
// during this time the funds get reserved and unreserved.
run_to_block(20);
assert_noop!(Crowdloan::dissolve(Origin::signed(2), para), Error::<Test>::NotReadyToDissolve);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
assert_eq!(Balances::free_balance(1), 1000);
fn dissolving_finished_with_contributions_works() {
new_test_ext().execute_with(|| {
let para = new_para();
let issuance = Balances::total_issuance();
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None));
assert_ok!(Crowdloan::contribute(Origin::signed(3), para, 50, None));
run_to_block(20);
assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
run_to_block(24);
assert_noop!(Crowdloan::dissolve(Origin::signed(1), para), Error::<Test>::NotReadyToDissolve);
run_to_block(25);
assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
assert_eq!(Balances::free_balance(1), 1000);
assert_eq!(Balances::total_issuance(), issuance - 50);
new_test_ext().execute_with(|| {
let para_1 = new_para();
let para_2 = new_para();
// Set up crowdloans
assert_ok!(Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None));
assert_ok!(Crowdloan::create(Origin::signed(1), para_2, 1000, 1, 1, 9, None));
// Different contributions
assert_ok!(Crowdloan::contribute(Origin::signed(2), para_1, 100, None));
assert_ok!(Crowdloan::contribute(Origin::signed(3), para_2, 50, None));
// Original state
assert_eq!(Funds::<Test>::get(para_1).unwrap().raised, 100);
assert_eq!(Funds::<Test>::get(para_2).unwrap().raised, 50);
// Swap
Crowdloan::on_swap(para_1, para_2);
// Final state
assert_eq!(Funds::<Test>::get(para_2).unwrap().raised, 100);
assert_eq!(Funds::<Test>::get(para_1).unwrap().raised, 50);
fn cannot_create_fund_when_already_active() {
new_test_ext().execute_with(|| {
assert_ok!(Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None));
// Cannot create a fund again
assert_noop!(
Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None),
Error::<Test>::FundNotEnded,
);
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
use super::{*, Module as Crowdloan};
use frame_system::RawOrigin;
use frame_support::{
assert_ok,
traits::OnInitialize,
};
use sp_runtime::traits::{Bounded, CheckedSub};
use frame_benchmarking::{benchmarks, whitelisted_caller, account, impl_benchmark_test_suite};
fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
let events = frame_system::Module::<T>::events();
let system_event: <T as frame_system::Config>::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<T: Config>(id: u32, end: T::BlockNumber) -> ParaId {
let lease_period_index = T::Auctioneer::lease_period_index();
let first_slot = lease_period_index;
let last_slot = lease_period_index + 3u32.into();
let caller = account("fund_creator", id, 0);
CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
// Assume ed25519 is most complex signature format
let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
let head_data = T::Registrar::worst_head_data();
let validation_code = T::Registrar::worst_validation_code();
assert_ok!(T::Registrar::register(caller.clone(), para_id, head_data, validation_code));
assert_ok!(Crowdloan::<T>::create(
RawOrigin::Signed(caller).into(),
para_id,
cap,
first_slot,
last_slot,
end,
Some(pubkey)
));
para_id
fn contribute_fund<T: Config>(who: &T::AccountId, index: ParaId) {
CurrencyOf::<T>::make_free_balance_be(&who, BalanceOf::<T>::max_value());
let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
let payload = (index, &who, BalanceOf::<T>::default(), value);
let sig = crypto::create_ed25519_signature(&payload.encode(), pubkey);
assert_ok!(Crowdloan::<T>::contribute(RawOrigin::Signed(who.clone()).into(), index, value, Some(sig)));
let cap = BalanceOf::<T>::max_value();
let first_slot = 0u32.into();
let last_slot = 3u32.into();
let end = T::BlockNumber::max_value();
let caller: T::AccountId = whitelisted_caller();
let head_data = T::Registrar::worst_head_data();
let validation_code = T::Registrar::worst_validation_code();
let verifier = account("verifier", 0, 0);
CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
T::Registrar::register(caller.clone(), para_id, head_data, validation_code)?;
}: _(RawOrigin::Signed(caller), para_id, cap, first_slot, last_slot, end, Some(verifier))
assert_last_event::<T>(RawEvent::Created(para_id).into())
}
// Contribute has two arms: PreEnding and Ending, but both are equal complexity.
contribute {
let fund_index = create_fund::<T>(1, 100u32.into());
let caller: T::AccountId = whitelisted_caller();
let contribution = T::MinContribution::get();
CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
assert!(NewRaise::get().is_empty());
let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
let payload = (fund_index, &caller, BalanceOf::<T>::default(), contribution);
let sig = crypto::create_ed25519_signature(&payload.encode(), pubkey);
}: _(RawOrigin::Signed(caller.clone()), fund_index, contribution, Some(sig))
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::<T>(RawEvent::Contributed(caller, fund_index, contribution).into());
}
withdraw {
let fund_index = create_fund::<T>(1, 100u32.into());
let caller: T::AccountId = whitelisted_caller();
let contributor = account("contributor", 0, 0);
contribute_fund::<T>(&contributor, fund_index);
frame_system::Module::<T>::set_block_number(200u32.into());
}: _(RawOrigin::Signed(caller), contributor.clone(), fund_index)
verify {
assert_last_event::<T>(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 k in 0 .. T::RemoveKeysLimit::get();
let fund_index = create_fund::<T>(1, 100u32.into());
// Dissolve will remove at most `RemoveKeysLimit` at once.
contribute_fund::<T>(&account("contributor", i, 0), fund_index);
}
// One extra contributor so we can trigger withdraw
contribute_fund::<T>(&account("last_contributor", 0, 0), fund_index);
let caller: T::AccountId = whitelisted_caller();
frame_system::Module::<T>::set_block_number(T::BlockNumber::max_value());
Crowdloan::<T>::withdraw(
RawOrigin::Signed(caller.clone()).into(),
account("last_contributor", 0, 0),
fund_index,
)?;
}: _(RawOrigin::Signed(caller.clone()), fund_index)
verify {
assert_last_event::<T>(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();
let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
let fund_index = create_fund::<T>(i, end_block);
let contributor: T::AccountId = account("contributor", i, 0);
let contribution = T::MinContribution::get() * (i + 1).into();
let payload = (fund_index, &contributor, BalanceOf::<T>::default(), contribution);
let sig = crypto::create_ed25519_signature(&payload.encode(), pubkey.clone());
CurrencyOf::<T>::make_free_balance_be(&contributor, BalanceOf::<T>::max_value());
Crowdloan::<T>::contribute(RawOrigin::Signed(contributor).into(), fund_index, contribution, Some(sig))?;
let lease_period_index = T::Auctioneer::lease_period_index();
let duration = end_block
.checked_sub(&frame_system::Module::<T>::block_number())
.ok_or("duration of auction less than zero")?;
T::Auctioneer::new_auction(duration, lease_period_index)?;
assert_eq!(T::Auctioneer::is_ending(end_block), Some(0u32.into()));
assert_eq!(NewRaise::get().len(), n as usize);
let old_endings_count = EndingsCount::get();
}: {
Crowdloan::<T>::on_initialize(end_block);
} verify {
assert_eq!(EndingsCount::get(), old_endings_count + 1);
assert_last_event::<T>(RawEvent::HandleBidResult((n - 1).into(), Ok(())).into());
}
}
impl_benchmark_test_suite!(
Crowdloan,
crate::integration_tests::new_test_ext(),
crate::integration_tests::Test,
);