Skip to content
crowdloan.rs 57.4 KiB
Newer Older
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Fund crowdloan
			assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));

			run_to_block(10);

			// Endings count incremented
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Crowdloan::endings_count(), 1);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Onboard crowdloan
			assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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()]);
		});
	}

	#[test]
	fn onboard_handles_basic_errors() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			assert_eq!(Balances::free_balance(1), 999);

Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Fund crowdloan
			assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));

			run_to_block(10);

			// Cannot onboard invalid fund index
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::onboard(Origin::signed(1), 1, 0.into()), Error::<Test>::InvalidFundIndex);
			// Cannot onboard crowdloan without deploy data
			assert_noop!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()), Error::<Test>::UnsetDeployData);

			// Add deploy data
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(1),
				0,
				<Test as frame_system::Config>::Hash::default(),
			));

			// Cannot onboard fund with incorrect parachain id
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::onboard(Origin::signed(1), 0, 1.into()), SlotsError::<Test>::ParaNotOnboarding);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Onboard crowdloan
			assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));

			// Cannot onboard fund again
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()), Error::<Test>::AlreadyOnboard);
		});
	}

	#[test]
	fn begin_retirement_works() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			assert_eq!(Balances::free_balance(1), 999);

			// Add deploy data
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(1),
				0,
				<Test as frame_system::Config>::Hash::default(),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Fund crowdloan
			assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Onboard crowdloan
			assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
			// Fund is assigned a parachain id
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			let fund = Crowdloan::funds(0).unwrap();
			assert_eq!(fund.parachain, Some(0.into()));

Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Off-boarding is set to the crowdloan account
			assert_eq!(Slots::offboarding(ParaId::from(0)), Crowdloan::fund_account_id(0));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Retire crowdloan to remove parachain id
			assert_ok!(Crowdloan::begin_retirement(Origin::signed(1), 0));

			// Fund should no longer have parachain id
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			let fund = Crowdloan::funds(0).unwrap();
			assert_eq!(fund.parachain, None);

		});
	}

	#[test]
	fn begin_retirement_handles_basic_errors() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			assert_eq!(Balances::free_balance(1), 999);

			// Add deploy data
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(1),
				0,
				<Test as frame_system::Config>::Hash::default(),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Fund crowdloan
			assert_ok!(Crowdloan::contribute(Origin::signed(2), 0, 1000));

			run_to_block(10);

			// Cannot retire fund that is not onboarded
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 0), Error::<Test>::NotParachain);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Onboard crowdloan
			assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
			// Fund is assigned a parachain id
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			let fund = Crowdloan::funds(0).unwrap();
			assert_eq!(fund.parachain, Some(0.into()));

			// Cannot retire fund whose deposit has not been returned
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 0), Error::<Test>::ParaHasDeposit);

			run_to_block(50);

			// Cannot retire invalid fund index
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 1), Error::<Test>::InvalidFundIndex);

			// Cannot retire twice
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::begin_retirement(Origin::signed(1), 0));
			assert_noop!(Crowdloan::begin_retirement(Origin::signed(1), 0), Error::<Test>::NotParachain);
		});
	}

	#[test]
	fn withdraw_works() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			// Transfer fee is taken here
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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);

Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Anyone can trigger withdraw of a user's balance without fees
			assert_ok!(Crowdloan::withdraw(Origin::signed(1337), 1, 0));
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(1), 999);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::withdraw(Origin::signed(1337), 2, 0));
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(2), 2000);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::withdraw(Origin::signed(1337), 3, 0));
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(3), 3000);
	#[test]
	fn withdraw_handles_basic_errors() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			// Transfer fee is taken here
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 49));
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(1), 950);

			run_to_block(5);

			// Cannot withdraw before fund ends
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::withdraw(Origin::signed(1337), 1, 0), Error::<Test>::FundNotEnded);

			run_to_block(10);

			// Cannot withdraw if they did not contribute
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::withdraw(Origin::signed(1337), 2, 0), Error::<Test>::NoContributions);
			// Cannot withdraw from a non-existent fund
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::withdraw(Origin::signed(1337), 2, 1), Error::<Test>::InvalidFundIndex);
		});
	}

	#[test]
	fn dissolve_works() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			// Transfer fee is taken here
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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);
Gavin Wood's avatar
Gavin Wood committed
			// Check initiator's balance.
			assert_eq!(Balances::free_balance(1), 899);
			// Check current funds (contributions + deposit)
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 601);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Dissolve the crowdloan
			assert_ok!(Crowdloan::dissolve(Origin::signed(1), 0));

			// Fund account is emptied
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(0)), 0);
			// Deposit is returned
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(1), 900);
			// Treasury account is filled
			assert_eq!(Balances::free_balance(Treasury::account_id()), 600);

			// Storage trie is removed
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Crowdloan::contribution_get(0,&0), 0);
			// Fund storage is removed
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// 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(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			// Transfer fee is taken here
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::dissolve(Origin::signed(1), 1), Error::<Test>::InvalidFundIndex);
			// Cannot dissolve a fund in progress
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::dissolve(Origin::signed(1), 0), Error::<Test>::InRetirementPeriod);

			run_to_block(10);

			// Onboard fund
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(1),
				0,
				<Test as frame_system::Config>::Hash::default(),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));

			// Cannot dissolve an active fund
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_noop!(Crowdloan::dissolve(Origin::signed(1), 0), Error::<Test>::HasActiveParachain);
		});
	}

	#[test]
	fn fund_before_auction_works() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Create a crowdloan before an auction is created
			assert_ok!(Crowdloan::create(Origin::signed(1), 1000, 1, 4, 9));
			// Users can already contribute
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 49));
			// Fund added to NewRaise
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(1),
				0,
				<Test as frame_system::Config>::Hash::default(),
			));
			// Move to the end of auction...
			run_to_block(12);

			// Endings count incremented
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Crowdloan::endings_count(), 1);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Onboard crowdloan
			assert_ok!(Crowdloan::onboard(Origin::signed(1), 0, 0.into()));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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()]);
		});
	}

	#[test]
	fn fund_across_multiple_auctions_works() {
		new_test_ext().execute_with(|| {
			// Create an auction
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// 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
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::contribute(Origin::signed(1), 0, 300));
			assert_ok!(Crowdloan::contribute(Origin::signed(1), 1, 200));

			// Add deploy data to all
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(1),
				0,
				<Test as frame_system::Config>::Hash::default(),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::fix_deploy_data(
				Origin::signed(2),
				1,
				<Test as frame_system::Config>::Hash::default(),
			));

			// End the current auction, fund 0 wins!
			run_to_block(10);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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()]);

			// Create a second auction
			assert_ok!(Slots::new_auction(Origin::root(), 5, 1));
			// Contribute to existing funds add to NewRaise
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_ok!(Crowdloan::contribute(Origin::signed(1), 1, 10));

			// End the current auction, fund 1 wins!
			run_to_block(20);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			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()]);
		});
	}
}
Shawn Tabrizi's avatar
Shawn Tabrizi committed

#[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<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>(end: T::BlockNumber) -> FundIndex {
		let cap = BalanceOf::<T>::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::<T>::max_value());

		assert_ok!(Crowdloan::<T>::create(RawOrigin::Signed(caller).into(), cap, first_slot, last_slot, end));
		FundCount::get() - 1
	}

	fn contribute_fund<T: Config>(who: &T::AccountId, index: FundIndex) {
		T::Currency::make_free_balance_be(&who, BalanceOf::<T>::max_value());
		let value = T::MinContribution::get();
		assert_ok!(Crowdloan::<T>::contribute(RawOrigin::Signed(who.clone()).into(), index, value));
	}

	fn worst_validation_code<T: Config>() -> Vec<u8> {
		// 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::<Vec<_>>();
		validation_code
	}

	fn worst_deploy_data<T: Config>() -> DeployData<T::Hash> {
		let validation_code = worst_validation_code::<T>();
		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<T: Config>(
		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::<T>();
		Crowdloan::<T>::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::<T>::new_auction(RawOrigin::Root.into(), end_block, lease_period_index)?;
		let contributor: T::AccountId = account("contributor", 0, 0);
		contribute_fund::<T>(&contributor, fund_index);

		// TODO: Probably should use on_initialize
		//Slots::<T>::on_initialize(end_block + T::EndingPeriod::get());
		let onboarding_data = (lease_period_index, crate::slots::IncomingParachain::Unset(
			crate::slots::NewBidder {
				who: Crowdloan::<T>::fund_account_id(fund_index),
				sub: Default::default(),
			}
		));
		crate::slots::Onboarding::<T>::insert(para_id, onboarding_data);
		Ok(())
	}

	benchmarks! {
		create {
			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();

			T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
		}: _(RawOrigin::Signed(caller), cap, first_slot, last_slot, end)
		verify {
			assert_last_event::<T>(RawEvent::Created(FundCount::get() - 1).into())
		}

		// Contribute has two arms: PreEnding and Ending, but both are equal complexity.
		contribute {
			let fund_index = create_fund::<T>(100u32.into());
			let caller: T::AccountId = whitelisted_caller();
			let contribution = T::MinContribution::get();
			T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::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::<T>(RawEvent::Contributed(caller, fund_index, contribution).into());
		}

		fix_deploy_data {
			let fund_index = create_fund::<T>(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::<T>();

			whitelist_account!(caller);
		}: _(RawOrigin::Signed(caller), fund_index, code_hash, code_size, initial_head_data)
		verify {
			assert_last_event::<T>(RawEvent::DeployDataFixed(fund_index).into());
		}

		onboard {
			let end_block: T::BlockNumber = 100u32.into();
			let fund_index = create_fund::<T>(end_block);
			let para_id = Default::default();

			setup_onboarding::<T>(fund_index, para_id, end_block)?;

			let caller = whitelisted_caller();
		}: _(RawOrigin::Signed(caller), fund_index, para_id)
		verify {
			assert_last_event::<T>(RawEvent::Onboarded(fund_index, para_id).into());
		}

		begin_retirement {
			let end_block: T::BlockNumber = 100u32.into();
			let fund_index = create_fund::<T>(end_block);
			let para_id = Default::default();

			setup_onboarding::<T>(fund_index, para_id, end_block)?;

			let caller: T::AccountId = whitelisted_caller();
			Crowdloan::<T>::onboard(RawOrigin::Signed(caller.clone()).into(), fund_index, para_id)?;

			// Remove deposits to look like it is off-boarded
			crate::slots::Deposits::<T>::remove(para_id);
		}: _(RawOrigin::Signed(caller), fund_index)
		verify {
			assert_last_event::<T>(RawEvent::Retiring(fund_index).into());
		}

		withdraw {
			let fund_index = create_fund::<T>(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 fund_index = create_fund::<T>(100u32.into());

			// Dissolve will remove at most `RemoveKeysLimit` at once.
			for i in 0 .. T::RemoveKeysLimit::get() {
				contribute_fund::<T>(&account("contributor", i, 0), fund_index);
			}

			let caller: T::AccountId = whitelisted_caller();
			frame_system::Module::<T>::set_block_number(T::RetirementPeriod::get().saturating_add(200u32.into()));
		}: _(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();

			for i in 0 .. n {
				let fund_index = create_fund::<T>(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::<T>::max_value());
				Crowdloan::<T>::contribute(RawOrigin::Signed(contributor).into(), fund_index, contribution)?;
			}

			let lease_period_index = end_block / T::LeasePeriod::get();
			Slots::<T>::new_auction(RawOrigin::Root.into(), end_block, lease_period_index)?;

			assert_eq!(<slots::Module<T>>::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());
		}
	}

	#[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::<Test>());
				assert_ok!(test_benchmark_contribute::<Test>());
				assert_ok!(test_benchmark_fix_deploy_data::<Test>());
				assert_ok!(test_benchmark_onboard::<Test>());
				assert_ok!(test_benchmark_begin_retirement::<Test>());
				assert_ok!(test_benchmark_withdraw::<Test>());
				assert_ok!(test_benchmark_dissolve::<Test>());
				assert_ok!(test_benchmark_on_initialize::<Test>());
			});
		}
	}
}