Skip to content
crowdloan.rs 65.9 KiB
Newer Older
			assert_eq!(TestAuctioneer::is_ending(11), None);
		});
	}

	#[test]
	fn create_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Now try to create a crowdloan campaign
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
			// This is what the initial `fund_info` should look like
			let fund_info = FundInfo {
				depositor: 1,
				verifier: None,
				deposit: 1,
				raised: 0,
				// 5 blocks length + 3 block ending period + 1 starting block
				end: 9,
				cap: 1000,
				last_contribution: LastContribution::Never,
				first_period: 1,
				last_period: 4,
				trie_index: 0,
			assert_eq!(Crowdloan::funds(para), Some(fund_info));
			// User has deposit removed from their free balance
			assert_eq!(Balances::free_balance(1), 999);
			// Deposit is placed in reserved
			assert_eq!(Balances::reserved_balance(1), 1);
			// No new raise until first contribution
			let empty: Vec<ParaId> = Vec::new();
			assert_eq!(Crowdloan::new_raise(), empty);
		});
	}

	#[test]
	fn create_with_verifier_works() {
		new_test_ext().execute_with(|| {
			let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
			let para = new_para();
			// Now try to create a crowdloan campaign
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, Some(pubkey.clone())));
			// This is what the initial `fund_info` should look like
			let fund_info = FundInfo {
				depositor: 1,
				verifier: Some(pubkey),
				deposit: 1,
				raised: 0,
				// 5 blocks length + 3 block ending period + 1 starting block
				end: 9,
				cap: 1000,
				last_contribution: LastContribution::Never,
				first_period: 1,
				last_period: 4,
				trie_index: 0,
			assert_eq!(Crowdloan::funds(ParaId::from(0)), Some(fund_info));
			// User has deposit removed from their free balance
			assert_eq!(Balances::free_balance(1), 999);
			// Deposit is placed in reserved
			assert_eq!(Balances::reserved_balance(1), 1);
			// No new raise until first contribution
			let empty: Vec<ParaId> = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			assert_eq!(Crowdloan::new_raise(), empty);
		});
	}

	#[test]
	fn create_handles_basic_errors() {
		new_test_ext().execute_with(|| {
			// Now try to create a crowdloan campaign
			let para = new_para();

			let e = Error::<Test>::InvalidParaId;
			assert_noop!(Crowdloan::create(Origin::signed(1), 1.into(), 1000, 1, 4, 9, None), e);
			// Cannot create a crowdloan with bad lease periods
			let e = Error::<Test>::LastPeriodBeforeFirstPeriod;
			assert_noop!(Crowdloan::create(Origin::signed(1), para, 1000, 4, 1, 9, None), e);
			let e = Error::<Test>::LastPeriodTooFarInFuture;
			assert_noop!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 9, 9, None), e);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Cannot create a crowdloan without some deposit funds
			assert_ok!(TestRegistrar::<Test>::register(1337, ParaId::from(1234), Default::default(), Default::default()));
			let e = BalancesError::<Test, _>::InsufficientBalance;
			assert_noop!(Crowdloan::create(Origin::signed(1337), ParaId::from(1234), 1000, 1, 3, 9, None), e);

			// Cannot create a crowdloan with nonsense end date
			// This crowdloan would end in lease period 2, but is bidding for some slot that starts in lease period 1.
			assert_noop!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 41, None), Error::<Test>::EndTooFarInFuture);
		});
	}

	#[test]
	fn contribute_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();

Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Set up a crowdloan
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));

			// No contributions yet
			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// User 1 contributes to their own crowdloan
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 49, None));
			// User 1 has spent some funds to do this, transfer fees **are** taken
Gavin Wood's avatar
Gavin Wood committed
			assert_eq!(Balances::free_balance(1), 950);
			// Contributions are stored in the trie
			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 49);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Contributions appear in free balance of crowdloan
			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(para)), 49);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			// Crowdloan is added to NewRaise
			assert_eq!(Crowdloan::new_raise(), vec![para]);
			let fund = Crowdloan::funds(para).unwrap();

			// Last contribution time recorded
			assert_eq!(fund.last_contribution, LastContribution::PreEnding(0));
			assert_eq!(fund.raised, 49);
		});
	}

	#[test]
	fn contribute_with_verifier_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
			// Set up a crowdloan
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, Some(pubkey.clone())));

			// No contributions yet
			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, None), Error::<Test>::InvalidSignature);

			let payload = (0u32, 1u64, 0u64, 49u64);
			let valid_signature = crypto::create_ed25519_signature(&payload.encode(), pubkey.clone());
			let invalid_signature = MultiSignature::default();

			// Invalid signature
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, Some(invalid_signature)), Error::<Test>::InvalidSignature);

			// Valid signature wrong parameter
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 50, Some(valid_signature.clone())), Error::<Test>::InvalidSignature);
			assert_noop!(Crowdloan::contribute(Origin::signed(2), para, 49, Some(valid_signature.clone())), Error::<Test>::InvalidSignature);
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 49, Some(valid_signature.clone())));

			// Reuse valid signature
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, Some(valid_signature)), Error::<Test>::InvalidSignature);

			let payload_2 = (0u32, 1u64, 49u64, 10u64);
			let valid_signature_2 = crypto::create_ed25519_signature(&payload_2.encode(), pubkey);

			// New valid signature
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 10, Some(valid_signature_2)));

			// Contributions appear in free balance of crowdloan
			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(para)), 59);

			// Contribution amount is correct
			let fund = Crowdloan::funds(para).unwrap();
			assert_eq!(fund.raised, 59);
		});
	}

	#[test]
	fn contribute_handles_basic_errors() {
		new_test_ext().execute_with(|| {
			let para = new_para();

			// Cannot contribute to non-existing fund
			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);

			// If a crowdloan has already won, it should not allow contributions.
			let para_2 = new_para();
			assert_ok!(Crowdloan::create(Origin::signed(1), para_2, 1000, 1, 4, 40, None));
			// Emulate a win by leasing out and putting a deposit. Slots pallet would normally do this.
			let crowdloan_account = Crowdloan::fund_account_id(para_2);
			set_winner(para_2, crowdloan_account, true);
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para_2, 49, None), Error::<Test>::BidOrLeaseActive);

			// Move past lease period 1, should not be allowed to have further contributions with a crowdloan
			// that has starting period 1.
			let para_3 = new_para();
			assert_ok!(Crowdloan::create(Origin::signed(1), para_3, 1000, 1, 4, 40, None));
			run_to_block(40);
			assert_eq!(TestAuctioneer::lease_period_index(), 2);
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para_3, 49, None), Error::<Test>::ContributionPeriodOver);
	fn bidding_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let first_period = 1;
			let last_period = 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_period, last_period, 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_period, last_period },
				BidPlaced { height: 6, amount: 450, bidder, para, first_period, last_period },
				BidPlaced { height: 9, amount: 700, bidder, para, first_period, last_period },
			// 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 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(10);
			let account_id = Crowdloan::fund_account_id(para);
			// para has no reserved funds, indicating it did not 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);
	#[test]
	fn withdraw_cannot_be_griefed() {
		new_test_ext().execute_with(|| {
			let para = new_para();

			// 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));

			run_to_block(10);
			let account_id = Crowdloan::fund_account_id(para);

			// user sends the crowdloan funds trying to make an accounting error
			assert_ok!(Balances::transfer(Origin::signed(1), account_id, 10));

			// overfunded now
			assert_eq!(Balances::free_balance(&account_id), 110);
			assert_eq!(Balances::free_balance(2), 1900);

			assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para));
			assert_eq!(Balances::free_balance(2), 2000);

			// Some funds are left over
			assert_eq!(Balances::free_balance(&account_id), 10);
			// They wil be left in the account at the end
			assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
			assert_eq!(Balances::free_balance(&account_id), 10);
		});
	}

	#[test]
	fn refund_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let account_id = Crowdloan::fund_account_id(para);

			// Set up a crowdloan ending on 9
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None));
			// Make some contributions
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 100, None));
			assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 200, None));
			assert_ok!(Crowdloan::contribute(Origin::signed(3), para, 300, None));

			assert_eq!(Balances::free_balance(account_id), 600);

			// Can't refund before the crowdloan it has ended
			assert_noop!(
				Crowdloan::refund(Origin::signed(1337), para),
				Error::<Test>::FundNotEnded,
			);

			// Move to the end of the crowdloan
			run_to_block(10);
			assert_ok!(Crowdloan::refund(Origin::signed(1337), para));

			// Funds are returned
			assert_eq!(Balances::free_balance(account_id), 0);
			// 1 deposit for the crowdloan which hasn't dissolved yet.
			assert_eq!(Balances::free_balance(1), 1000 - 1);
			assert_eq!(Balances::free_balance(2), 2000);
			assert_eq!(Balances::free_balance(3), 3000);
	fn multiple_refund_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let account_id = Crowdloan::fund_account_id(para);

			// Set up a crowdloan ending on 9
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 100000, 1, 1, 9, None));
			// Make more contributions than our limit
			for i in 1 ..= RemoveKeysLimit::get() * 2 {
				Balances::make_free_balance_be(&i.into(), (1000 * i).into());
				assert_ok!(Crowdloan::contribute(Origin::signed(i.into()), para, (i * 100).into(), None));
			}

			assert_eq!(Balances::free_balance(account_id), 21000);

			// Move to the end of the crowdloan
			run_to_block(10);
			assert_ok!(Crowdloan::refund(Origin::signed(1337), para));
			assert_eq!(last_event(), super::Event::<Test>::PartiallyRefunded(para).into());

			// Funds still left over
			assert!(!Balances::free_balance(account_id).is_zero());

			// Call again
			assert_ok!(Crowdloan::refund(Origin::signed(1337), para));
			assert_eq!(last_event(), super::Event::<Test>::AllRefunded(para).into());

			// Funds are returned
			assert_eq!(Balances::free_balance(account_id), 0);
			// 1 deposit for the crowdloan which hasn't dissolved yet.
			for i in 1 ..= RemoveKeysLimit::get() * 2 {
				assert_eq!(Balances::free_balance(&i.into()), i as u64 * 1000);
			}
		});
	}

	#[test]
	fn refund_and_dissolve_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let issuance = Balances::total_issuance();
			// 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(10);
			// All funds are refunded
			assert_ok!(Crowdloan::refund(Origin::signed(2), para));
			// Now that `fund.raised` is zero, it can be dissolved.
			assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
			assert_eq!(Balances::free_balance(1), 1000);
			assert_eq!(Balances::free_balance(2), 2000);
			assert_eq!(Balances::free_balance(3), 3000);
			assert_eq!(Balances::total_issuance(), issuance);
		new_test_ext().execute_with(|| {
			let para = new_para();
			let issuance = Balances::total_issuance();
			// 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));
			// Can't dissolve before it ends
			assert_noop!(Crowdloan::dissolve(Origin::signed(1), para), Error::<Test>::NotReadyToDissolve);

			run_to_block(10);
			set_winner(para, 1, true);
			// Can't dissolve when it won.
			assert_noop!(Crowdloan::dissolve(Origin::signed(1), para), Error::<Test>::NotReadyToDissolve);
			set_winner(para, 1, false);
			// Can't dissolve while it still has user funds
			assert_noop!(Crowdloan::dissolve(Origin::signed(1), para), Error::<Test>::NotReadyToDissolve);
			// All funds are refunded
			assert_ok!(Crowdloan::refund(Origin::signed(2), para));

			// Now that `fund.raised` is zero, it can be dissolved.
			assert_ok!(Crowdloan::dissolve(Origin::signed(1), para));
			assert_eq!(Balances::free_balance(1), 1000);
			assert_eq!(Balances::free_balance(2), 2000);
			assert_eq!(Balances::free_balance(3), 3000);
			assert_eq!(Balances::total_issuance(), issuance);
	fn withdraw_from_finished_works() {
		new_test_ext().execute_with(|| {
			let para = new_para();
			let account_id = Crowdloan::fund_account_id(para);

			// Set up a crowdloan
			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 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,
			);

	#[test]
	fn edit_works() {
		new_test_ext().execute_with(|| {
			let para_1 = new_para();

			assert_ok!(Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None));
			assert_ok!(Crowdloan::contribute(Origin::signed(2), para_1, 100, None));
			let old_crowdloan = Crowdloan::funds(para_1).unwrap();

			assert_ok!(Crowdloan::edit(Origin::root(), para_1, 1234, 2, 3, 4, None));
			let new_crowdloan = Crowdloan::funds(para_1).unwrap();

			// Some things stay the same
			assert_eq!(old_crowdloan.depositor, new_crowdloan.depositor);
			assert_eq!(old_crowdloan.deposit, new_crowdloan.deposit);
			assert_eq!(old_crowdloan.raised, new_crowdloan.raised);

			// Some things change
			assert!(old_crowdloan.cap != new_crowdloan.cap);
			assert!(old_crowdloan.first_period != new_crowdloan.first_period);
			assert!(old_crowdloan.last_period != new_crowdloan.last_period);

	#[test]
	fn add_memo_works() {
		new_test_ext().execute_with(|| {
			let para_1 = new_para();

			assert_ok!(Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None));
			// Cant add a memo before you have contributed.
			assert_noop!(
				Crowdloan::add_memo(Origin::signed(1), para_1, b"hello, world".to_vec()),
				Error::<Test>::NoContributions,
			);
			// Make a contribution. Initially no memo.
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para_1, 100, None));
			assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, vec![]));
			// Can't place a memo that is too large.
			assert_noop!(
				Crowdloan::add_memo(Origin::signed(1), para_1, vec![123; 123]),
				Error::<Test>::MemoTooLarge,
			);
			// Adding a memo to an existing contribution works
			assert_ok!(Crowdloan::add_memo(Origin::signed(1), para_1, b"hello, world".to_vec()));
			assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, b"hello, world".to_vec()));
			// Can contribute again and data persists
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para_1, 100, None));
			assert_eq!(Crowdloan::contribution_get(0u32, &1), (200, b"hello, world".to_vec()));
		});
	}

	#[test]
	fn poke_works() {
		new_test_ext().execute_with(|| {
			let para_1 = new_para();

			assert_ok!(TestAuctioneer::new_auction(5, 0));
			assert_ok!(Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None));
			// Should fail when no contributions.
			assert_noop!(
				Crowdloan::poke(Origin::signed(1), para_1),
				Error::<Test>::NoContributions
			);
			assert_ok!(Crowdloan::contribute(Origin::signed(2), para_1, 100, None));
			run_to_block(6);
			assert_ok!(Crowdloan::poke(Origin::signed(1), para_1));
			assert_eq!(Crowdloan::new_raise(), vec![para_1]);
			assert_noop!(
				Crowdloan::poke(Origin::signed(1), para_1),
				Error::<Test>::AlreadyInNewRaise
			);
		});
	}
Shawn Tabrizi's avatar
Shawn Tabrizi committed

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
	use super::{*, Pallet as Crowdloan};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
	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();
		let first_period = lease_period_index;
		let last_period = lease_period_index + ((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).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));
		T::Registrar::execute_pending_transitions();

		assert_ok!(Crowdloan::<T>::create(
			RawOrigin::Signed(caller).into(),
			para_id,
			cap,
			first_period,
			last_period,
	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_period = 0u32.into();
			let last_period = 3u32.into();
			let end = T::Auctioneer::lease_period();
Shawn Tabrizi's avatar
Shawn Tabrizi committed

			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)?;
			T::Registrar::execute_pending_transitions();
		}: _(RawOrigin::Signed(caller), para_id, cap, first_period, last_period, end, Some(verifier))
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		verify {
			assert_last_event::<T>(Event::<T>::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::<T>::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::<T>::get().is_empty());
			assert_last_event::<T>(Event::<T>::Contributed(caller, fund_index, contribution).into());
			let fund_index = create_fund::<T>(1337, 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>(Event::<T>::Withdrew(contributor, fund_index, T::MinContribution::get()).into());
		// Worst case: Refund removes `RemoveKeysLimit` keys, and is fully refunded.
		refund {
			let k in 0 .. T::RemoveKeysLimit::get();
			let fund_index = create_fund::<T>(1337, 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);
			}

			let caller: T::AccountId = whitelisted_caller();
			frame_system::Pallet::<T>::set_block_number(200u32.into());
		}: _(RawOrigin::Signed(caller), fund_index)
		verify {
			assert_last_event::<T>(Event::<T>::AllRefunded(fund_index).into());
		dissolve {
			let fund_index = create_fund::<T>(1337, 100u32.into());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
			let caller: T::AccountId = whitelisted_caller();
			frame_system::Pallet::<T>::set_block_number(T::BlockNumber::max_value());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		}: _(RawOrigin::Signed(caller.clone()), fund_index)
		verify {
			assert_last_event::<T>(Event::<T>::Dissolved(fund_index).into());
		edit {
			let para_id = ParaId::from(1);
			let cap = BalanceOf::<T>::max_value();
			let first_period = 0u32.into();
			let last_period = 3u32.into();
			let end = T::Auctioneer::lease_period();

			let caller: T::AccountId = whitelisted_caller();
			let head_data = T::Registrar::worst_head_data();
			let validation_code = T::Registrar::worst_validation_code();

			let verifier: MultiSigner = 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)?;
			T::Registrar::execute_pending_transitions();

			Crowdloan::<T>::create(
				RawOrigin::Signed(caller).into(),
				para_id, cap, first_period, last_period, end, Some(verifier.clone()),
			)?;

			// Doesn't matter what we edit to, so use the same values.
		}: _(RawOrigin::Root, para_id, cap, first_period, last_period, end, Some(verifier))
			assert_last_event::<T>(Event::<T>::Edited(para_id).into())
		add_memo {
			let fund_index = create_fund::<T>(1, 100u32.into());
			let caller: T::AccountId = whitelisted_caller();
			contribute_fund::<T>(&caller, fund_index);
			let worst_memo = vec![42; T::MaxMemoLength::get().into()];
		}: _(RawOrigin::Signed(caller.clone()), fund_index, worst_memo.clone())
		verify {
			let fund = Funds::<T>::get(fund_index).expect("fund was created...");
			assert_eq!(
				Crowdloan::<T>::contribution_get(fund.trie_index, &caller),
				(T::MinContribution::get(), worst_memo),
			);
		}

		poke {
			let fund_index = create_fund::<T>(1, 100u32.into());
			let caller: T::AccountId = whitelisted_caller();
			contribute_fund::<T>(&caller, fund_index);
			NewRaise::<T>::kill();
			assert!(NewRaise::<T>::get().is_empty());
		}: _(RawOrigin::Signed(caller), fund_index)
		verify {
			assert!(!NewRaise::<T>::get().is_empty());
			assert_last_event::<T>(Event::<T>::AddedToNewRaise(fund_index).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		// 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 periods.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		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()));
			assert_eq!(NewRaise::<T>::get().len(), n as usize);
			let old_endings_count = EndingsCount::<T>::get();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
		}: {
			Crowdloan::<T>::on_initialize(end_block);
		} verify {
			assert_eq!(EndingsCount::<T>::get(), old_endings_count + 1);
			assert_last_event::<T>(Event::<T>::HandleBidResult((n - 1).into(), Ok(())).into());
	impl_benchmark_test_suite!(
		Crowdloan,
		crate::integration_tests::new_test_ext(),
		crate::integration_tests::Test,
	);