Skip to content
crowdloan.rs 51.8 KiB
Newer Older
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, None), Error::<Test>::InvalidParaId);
			// Cannot contribute below minimum contribution
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 9, None), Error::<Test>::ContributionTooSmall);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			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);
	fn bidding_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let first_slot = 1;
			let last_slot = 4;
			assert_ok!(TestAuctioneer::new_auction(5, 0));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, first_slot, last_slot, 9, None));
			let bidder = Crowdloan::fund_account_id(para);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Fund crowdloan
			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));
			run_to_block(10);

			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 },
			]);

			// Endings count incremented
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Crowdloan::endings_count(), 1);
	fn withdraw_from_failed_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			// 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));

			run_to_block(10);
			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(|| {
			let para = new_para();
			// 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));
			run_to_block(10);
			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));

			run_to_block(10);
			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);
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(2), 2000);
			assert_ok!(Crowdloan::withdraw(Origin::signed(2), 3, para));
			assert_eq!(Balances::free_balance(&account_id), 0);
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(3), 3000);
	fn dissolving_finished_without_contributions_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			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));
			// Only crowdloan creator can dissolve when raised is zero.
			assert_noop!(Crowdloan::dissolve(Origin::signed(2), para), Error::<Test>::NotReadyToDissolve);
			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();
Shawn Tabrizi's avatar
Shawn Tabrizi committed

			// Set up a crowdloan
			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);
	fn on_swap_works() {
		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(|| {
			let para_1 = new_para();
			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,
			);
Shawn Tabrizi's avatar
Shawn Tabrizi committed

#[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};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
	use sp_std::prelude::*;

	use frame_benchmarking::{benchmarks, whitelisted_caller, account, impl_benchmark_test_suite};
Shawn Tabrizi's avatar
Shawn Tabrizi committed

	fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
		let events = frame_system::Pallet::<T>::events();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		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 {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		let cap = BalanceOf::<T>::max_value();
		let lease_period_index = T::Auctioneer::lease_period_index();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		let first_slot = lease_period_index;
		let last_slot = lease_period_index + 3u32.into();
		let para_id = id.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());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		let value = T::MinContribution::get();

		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)));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
	}

	benchmarks! {
		create {
			let para_id = ParaId::from(1);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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))
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		verify {
			assert_last_event::<T>(RawEvent::Created(para_id).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		}

		// Contribute has two arms: PreEnding and Ending, but both are equal complexity.
		contribute {
			let fund_index = create_fund::<T>(1, 100u32.into());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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))
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		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());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			let caller: T::AccountId = whitelisted_caller();
			let contributor = account("contributor", 0, 0);
			contribute_fund::<T>(&contributor, fund_index);
			frame_system::Pallet::<T>::set_block_number(200u32.into());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		}: _(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());
Shawn Tabrizi's avatar
Shawn Tabrizi committed

			// Dissolve will remove at most `RemoveKeysLimit` at once.
			for i in 0 .. k {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
				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);

Shawn Tabrizi's avatar
Shawn Tabrizi committed
			let caller: T::AccountId = whitelisted_caller();
			frame_system::Pallet::<T>::set_block_number(T::BlockNumber::max_value());
			Crowdloan::<T>::withdraw(
				RawOrigin::Signed(caller.clone()).into(),
				account("last_contributor", 0, 0),
				fund_index,
			)?;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		}: _(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());

Shawn Tabrizi's avatar
Shawn Tabrizi committed
			for i in 0 .. n {
				let fund_index = create_fund::<T>(i, end_block);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
				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::Pallet::<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()));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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,
	);