Newer
Older
fn has_won_an_auction(para: ParaId, bidder: &u64) -> bool {
HAS_WON.with(|p| *p.borrow().get(&(para, *bidder)).unwrap_or(&false))
}
}
parameter_types! {
pub const SubmissionDeposit: u64 = 1;
pub const MinContribution: u64 = 10;
pub const CrowdloanPalletId: PalletId = PalletId(*b"py/cfund");
pub const MaxMemoLength: u8 = 32;
impl Config for Test {
type SubmissionDeposit = SubmissionDeposit;
type MinContribution = MinContribution;
type Registrar = TestRegistrar<Test>;
type Auctioneer = TestAuctioneer;
type MaxMemoLength = MaxMemoLength;
type WeightInfo = crate::crowdloan::TestWeightInfo;
use pallet_balances::Error as BalancesError;
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
}
.assimilate_storage(&mut t)
.unwrap();
let keystore = KeyStore::new();
let mut t: sp_io::TestExternalities = t.into();
t.register_extension(KeystoreExt(Arc::new(keystore)));
t
fn new_para() -> ParaId {
for i in 0.. {
let para: ParaId = i.into();
if TestRegistrar::<Test>::is_registered(para) {
continue
}
assert_ok!(TestRegistrar::<Test>::register(
1,
para,
Default::default(),
Default::default()
));
return para
fn run_to_block(n: u64) {
while System::block_number() < n {
Crowdloan::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
Crowdloan::on_initialize(System::block_number());
fn last_event() -> Event {
System::events().pop().expect("Event expected").event
}
new_test_ext().execute_with(|| {
assert_eq!(System::block_number(), 0);
assert_eq!(Crowdloan::funds(ParaId::from(0)), None);
let empty: Vec<ParaId> = Vec::new();
assert_eq!(Crowdloan::contribution_get(0u32, &1).0, 0);
assert_ok!(TestAuctioneer::new_auction(5, 0));
assert_eq!(bids(), vec![]);
assert_ok!(TestAuctioneer::place_bid(1, 2.into(), 0, 3, 6));
let b = BidPlaced {
height: 0,
bidder: 1,
para: 2.into(),
first_period: 0,
last_period: 3,
amount: 6,
};
assert_eq!(TestAuctioneer::auction_status(4), AuctionStatus::<u64>::StartingPeriod);
assert_eq!(TestAuctioneer::auction_status(5), AuctionStatus::<u64>::EndingPeriod(0, 0));
assert_eq!(TestAuctioneer::auction_status(9), AuctionStatus::<u64>::EndingPeriod(4, 0));
assert_eq!(TestAuctioneer::auction_status(11), AuctionStatus::<u64>::NotStarted);
});
}
#[test]
fn create_works() {
new_test_ext().execute_with(|| {
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 {
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,
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());
// 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 {
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,
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();
});
}
#[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);
// 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(|| {
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 49, None));
// User 1 has spent some funds to do this, transfer fees **are** taken
// Contributions are stored in the trie
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 49);
// Contributions appear in free balance of crowdloan
assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(para)), 49);
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 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())
));
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(|| {
// 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
);
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 cannot_contribute_during_vrf() {
new_test_ext().execute_with(|| {
set_vrf_delay(5);
let first_period = 1;
let last_period = 4;
assert_ok!(TestAuctioneer::new_auction(5, 0));
// Set up a crowdloan
assert_ok!(Crowdloan::create(
Origin::signed(1),
para,
1000,
first_period,
last_period,
20,
None
));
run_to_block(8);
// Can def contribute when auction is running.
assert!(TestAuctioneer::auction_status(System::block_number()).is_ending().is_some());
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 250, None));
run_to_block(10);
// Can't contribute when auction is in the VRF delay period.
assert!(TestAuctioneer::auction_status(System::block_number()).is_vrf());
assert_noop!(
Crowdloan::contribute(Origin::signed(2), para, 250, None),
Error::<Test>::VrfDelayInProgress
);
run_to_block(15);
// Its fine to contribute when no auction is running.
assert!(!TestAuctioneer::auction_status(System::block_number()).is_in_progress());
assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 250, None));
})
}
#[test]
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));
assert_ok!(Crowdloan::create(
Origin::signed(1),
para,
1000,
first_period,
last_period,
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_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 },
]
);
new_test_ext().execute_with(|| {
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 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);
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
#[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));
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
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 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
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.
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();
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));
// 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 dissolve_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));
// Can't dissolve before it ends
assert_noop!(
Crowdloan::dissolve(Origin::signed(1), para),
Error::<Test>::NotReadyToDissolve
);
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);
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);
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,
);
#[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);
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
#[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()));
});
}
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
#[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
);
});
}
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
use super::{Pallet as Crowdloan, *};
use frame_support::{assert_ok, traits::OnInitialize};
use sp_runtime::traits::{Bounded, CheckedSub};
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
Shaun Wang
committed
let events = frame_system::Pallet::<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_period = lease_period_index;
let last_period =
lease_period_index + ((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).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,
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 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 = 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))
assert_last_event::<T>(Event::<T>::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::<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))
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());
let caller: T::AccountId = whitelisted_caller();
let contributor = account("contributor", 0, 0);
contribute_fund::<T>(&contributor, fund_index);
Shaun Wang
committed
frame_system::Pallet::<T>::set_block_number(200u32.into());
}: _(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());
// Dissolve will remove at most `RemoveKeysLimit` at once.
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());
let caller: T::AccountId = whitelisted_caller();
Shaun Wang
committed
frame_system::Pallet::<T>::set_block_number(T::BlockNumber::max_value());
}: _(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();