Files
pezkuwi-sdk/pezcumulus/teyrchains/pezpallets/presale/src/tests.rs
T
pezkuwichain 4c8f281051 style: Migrate to stable-only rustfmt configuration
- Remove nightly-only features from .rustfmt.toml and vendor/ss58-registry/rustfmt.toml
- Removed features: imports_granularity, wrap_comments, comment_width,
  reorder_impl_items, spaces_around_ranges, binop_separator,
  match_arm_blocks, trailing_semicolon, trailing_comma
- Format all 898 affected files with stable rustfmt
- Ensures long-term reliability without nightly toolchain dependency
2025-12-23 09:37:11 +03:00

1401 lines
34 KiB
Rust

use crate::{
mock::*, ContributionLimits, Error, Event, PresaleCreationParams, PresaleStatus, RefundConfig,
VestingSchedule,
};
use pezframe_support::{assert_noop, assert_ok};
/// Helper function to create presale params with common defaults
#[allow(clippy::too_many_arguments)]
fn make_presale_params(
tokens_for_sale: u128,
duration: u64,
is_whitelist: bool,
min_contribution: u128,
max_contribution: u128,
soft_cap: u128,
hard_cap: u128,
enable_vesting: bool,
vesting_immediate_percent: u8,
vesting_duration_blocks: u64,
vesting_cliff_blocks: u64,
grace_period_blocks: u64,
refund_fee_percent: u8,
grace_refund_fee_percent: u8,
) -> PresaleCreationParams<u64> {
let vesting = if enable_vesting {
Some(VestingSchedule {
immediate_release_percent: vesting_immediate_percent,
vesting_duration_blocks,
cliff_blocks: vesting_cliff_blocks,
})
} else {
None
};
PresaleCreationParams {
tokens_for_sale,
duration,
is_whitelist,
limits: ContributionLimits { min_contribution, max_contribution, soft_cap, hard_cap },
vesting,
refund_config: RefundConfig {
grace_period_blocks,
refund_fee_percent,
grace_refund_fee_percent,
},
}
}
#[test]
fn create_presale_works() {
new_test_ext().execute_with(|| {
create_assets();
// Mint reward tokens to Alice (presale owner)
mint_assets(1, 1, 100_000_000_000_000_000_000); // 100,000 PEZ
// Alice creates a presale
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2, // wUSDT payment asset
1, // PEZ reward asset
make_presale_params(
10_000_000_000_000_000_000, // 10,000 PEZ tokens for sale (10^12 decimals)
100, // 100 blocks duration
false, // public presale
10_000_000, // min 10 USDT (10^6 decimals)
1_000_000_000, // max 1000 USDT
5_000_000_000, // soft cap 5,000 USDT
10_000_000_000, // hard cap 10,000 USDT
false, // no vesting
0,
0,
0,
24, // 24 blocks grace period
5, // 5% refund fee
2, // 2% grace refund fee
),
));
// Check presale created
let presale = Presale::presales(0).unwrap();
assert_eq!(presale.owner, 1);
assert_eq!(presale.payment_asset, 2);
assert_eq!(presale.reward_asset, 1);
assert_eq!(presale.tokens_for_sale, 10_000_000_000_000_000_000);
assert_eq!(presale.duration, 100);
// Check event
System::assert_last_event(
Event::PresaleCreated { presale_id: 0, owner: 1, payment_asset: 2, reward_asset: 1 }
.into(),
);
// Check NextPresaleId incremented
assert_eq!(Presale::next_presale_id(), 1);
});
}
#[test]
fn create_multiple_presales_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 1_000_000_000_000_000_000_000);
mint_assets(1, 2, 1_000_000_000_000_000_000_000);
// Alice creates first presale
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Bob creates second presale
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(2),
2,
1,
make_presale_params(
20_000_000_000_000_000_000,
200,
false,
20_000_000,
2_000_000_000,
10_000_000_000,
20_000_000_000,
false,
0,
0,
0,
48,
10,
5
)
));
// Check both presales exist
assert!(Presale::presales(0).is_some());
assert!(Presale::presales(1).is_some());
// Check owners
assert_eq!(Presale::presales(0).unwrap().owner, 1);
assert_eq!(Presale::presales(1).unwrap().owner, 2);
// Check NextPresaleId
assert_eq!(Presale::next_presale_id(), 2);
});
}
#[test]
fn contribute_works() {
new_test_ext().execute_with(|| {
create_assets();
// Setup: Alice creates presale
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Mint wUSDT to Bob
mint_assets(2, 2, 1_000_000_000); // 1000 USDT
// Bob contributes 100 USDT
let contribution = 100_000_000;
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, contribution));
// Check contribution tracked (gross amount)
let contribution_info = Presale::contributions(0, 2).unwrap();
assert_eq!(contribution_info.amount, contribution);
// Platform fee: 2% of 100_000_000 = 2_000_000
let platform_fee = contribution * 2 / 100;
let net_amount = contribution - platform_fee; // 98_000_000
// Check total raised (tracks gross amount)
assert_eq!(Presale::total_raised(0), contribution);
// Check contributors list
let contributors = Presale::contributors(0);
assert_eq!(contributors.len(), 1);
assert_eq!(contributors[0], 2);
// Check wUSDT transferred to presale treasury (net amount after platform fee)
let treasury = presale_treasury(0);
let balance = Assets::balance(2, treasury);
assert_eq!(balance, net_amount);
// Verify platform fee distribution (50% treasury, 25% staking, 25% burned)
let expected_to_treasury = platform_fee * 50 / 100; // 1_000_000
let expected_to_staking = platform_fee * 25 / 100; // 500_000
let _expected_burned = platform_fee * 25 / 100; // 500_000
// Check platform treasury received 50%
assert_eq!(Assets::balance(2, 999), expected_to_treasury);
// Check staking pool received 25%
assert_eq!(Assets::balance(2, 998), expected_to_staking);
// Note: Burn is verified by the fact that total supply decreased
// Initial supply to Bob was 1_000_000_000, after contribution:
// Bob spent: 100_000_000
// Presale treasury got: 98_000_000
// Platform treasury got: 1_000_000
// Staking pool got: 500_000
// Burned: 500_000
// Total accounted: 100_000_000 ✓
// Check event
System::assert_last_event(
Event::Contributed { presale_id: 0, who: 2, amount: contribution, bonus_amount: 0 }
.into(),
);
});
}
#[test]
fn contribute_multiple_times_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// First contribution
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 50_000_000));
let contribution_info = Presale::contributions(0, 2).unwrap();
assert_eq!(contribution_info.amount, 50_000_000);
// Second contribution
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 30_000_000));
let contribution_info = Presale::contributions(0, 2).unwrap();
assert_eq!(contribution_info.amount, 80_000_000);
// Contributors list should still have only 1 entry
assert_eq!(Presale::contributors(0).len(), 1);
// Total raised should be sum of gross amounts
assert_eq!(Presale::total_raised(0), 80_000_000);
});
}
#[test]
fn contribute_to_different_presales_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 1_000_000_000_000_000_000_000);
mint_assets(2, 2, 2_000_000_000); // 2000 USDT
// Create two presales
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Fund presale 0 treasury with reward tokens
mint_assets(1, presale_treasury(0), 10_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
15_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Fund presale 1 treasury with reward tokens
mint_assets(1, presale_treasury(1), 15_000_000_000_000_000_000);
// Bob contributes to both presales
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 100_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 1, 200_000_000));
// Check contributions tracked separately (gross amounts)
let contribution_info_0 = Presale::contributions(0, 2).unwrap();
assert_eq!(contribution_info_0.amount, 100_000_000);
let contribution_info_1 = Presale::contributions(1, 2).unwrap();
assert_eq!(contribution_info_1.amount, 200_000_000);
// Calculate net amounts after 2% platform fee (what goes to treasury)
let net_amount_0 = 100_000_000 * 98 / 100; // 98_000_000
let net_amount_1 = 200_000_000 * 98 / 100; // 196_000_000
// Check total raised per presale (gross amounts)
assert_eq!(Presale::total_raised(0), 100_000_000);
assert_eq!(Presale::total_raised(1), 200_000_000);
// Check balances in separate treasuries (net amounts after platform fee)
assert_eq!(Assets::balance(2, presale_treasury(0)), net_amount_0);
assert_eq!(Assets::balance(2, presale_treasury(1)), net_amount_1);
});
}
#[test]
fn contribute_below_min_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Try to contribute less than minimum (10 USDT)
assert_noop!(
Presale::contribute(RuntimeOrigin::signed(2), 0, 5_000_000),
Error::<Test>::BelowMinContribution
);
});
}
#[test]
fn contribute_above_max_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 5_000_000_000); // 5000 USDT
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Try to contribute more than maximum (1000 USDT)
assert_noop!(
Presale::contribute(RuntimeOrigin::signed(2), 0, 2_000_000_000),
Error::<Test>::AboveMaxContribution
);
});
}
#[test]
fn contribute_exceeding_hard_cap_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 15_000_000_000); // 15,000 USDT
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000, // Soft cap: 5,000 USDT, Hard cap: 10,000 USDT
false,
0,
0,
0,
24,
5,
2,
)
));
// Multiple contributors reach near hard cap (9,000 USDT total)
// Max contribution is 1000 USDT each, so need 9 contributors
for i in 3..12 {
mint_assets(2, i, 1_010_000_000); // 1000 USDT + 10 ED
assert_ok!(Presale::contribute(RuntimeOrigin::signed(i), 0, 1_000_000_000));
}
// Bob tries to contribute 2,000 USDT but max is 1000
// Even 1000 would exceed hard cap (9000 + 1000 = 10000 is ok, but 9000 + 2000 = 11000
// exceeds) So contribute 500 to not exceed max, then try to contribute another 1000 to
// exceed hard cap
mint_assets(2, 2, 2_010_000_000);
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 1_000_000_000)); // Now at 10,000 hard cap
// Try to contribute more (should fail with HardCapReached)
mint_assets(2, 13, 1_010_000_000);
assert_noop!(
Presale::contribute(RuntimeOrigin::signed(13), 0, 1_000_000_000),
Error::<Test>::HardCapReached
);
});
}
#[test]
fn contribute_after_presale_ended_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Move past presale end (block 1 + 100 = 101)
System::set_block_number(102);
assert_noop!(
Presale::contribute(RuntimeOrigin::signed(2), 0, 100_000_000),
Error::<Test>::PresaleEnded
);
});
}
#[test]
fn finalize_presale_works() {
new_test_ext().execute_with(|| {
create_assets();
// Setup: Alice creates presale with PEZ rewards
mint_assets(1, 1, 100_000_000_000_000_000_000); // 100,000 PEZ
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Mint PEZ to presale treasury for distribution
let treasury = presale_treasury(0);
mint_assets(1, treasury, 100_000_000_000_000_000_000);
// Bob and Charlie contribute (need to exceed soft cap of 5,000 USDT, max is 1000 each)
// Need 6 contributors @ 1000 USDT each = 6000 USDT
for i in 2..8 {
mint_assets(2, i, 1_010_000_000); // 1000 USDT + 10 ED
assert_ok!(Presale::contribute(RuntimeOrigin::signed(i), 0, 1_000_000_000));
}
// total_raised tracks gross amounts: 6 * 1000 = 6,000 USDT > soft cap (5,000 USDT)
let total_gross = 6_000_000_000;
// Move to end of presale
System::set_block_number(101);
// Finalize presale (requires root)
assert_ok!(Presale::finalize_presale(RuntimeOrigin::root(), 0));
// Check presale status changed to Finalized
let presale = Presale::presales(0).unwrap();
assert!(matches!(presale.status, PresaleStatus::Finalized));
// Token distribution is based on gross contribution amounts
// Each contributor: (1,000 / 6,000) * 10,000 PEZ = 1,666.666... PEZ
// tokens_for_sale = 10_000_000_000_000_000_000 (10000 PEZ with 12 decimals)
// Each share: 1_000_000_000 / 6_000_000_000 * 10_000_000_000_000_000_000
// = 1/6 * 10_000_000_000_000_000_000
// = 1_666_666_666_666_666_666 (approx, with rounding)
let expected_pez = 1_666_666_666_666_666_666u128;
for i in 2..8 {
let contributor_pez = Assets::balance(1, i);
// Allow for small rounding differences (within 0.1%)
assert!(
contributor_pez >= expected_pez - 10_000_000_000_000_000
&& contributor_pez <= expected_pez + 10_000_000_000_000_000,
"Contributor {i} PEZ: {contributor_pez} (expected ~{expected_pez})"
);
}
// Check event
System::assert_last_event(
Event::PresaleFinalized { presale_id: 0, total_raised: total_gross }.into(),
);
});
}
#[test]
fn finalize_presale_before_end_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Try to finalize immediately (use root to test the actual business logic error)
assert_noop!(
Presale::finalize_presale(RuntimeOrigin::root(), 0),
Error::<Test>::PresaleNotEnded
);
});
}
#[test]
fn finalize_presale_non_root_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
System::set_block_number(101);
// Non-root tries to finalize (finalize_presale is root-only)
assert_noop!(
Presale::finalize_presale(RuntimeOrigin::signed(2), 0),
pezsp_runtime::DispatchError::BadOrigin
);
});
}
#[test]
fn refund_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Bob contributes
let contribution = 100_000_000; // 100 USDT
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, contribution));
// Note: Treasury received 98 USDT (after 2% platform fee)
// But refund is calculated on gross amount (100 USDT)
// Refund = 95 USDT (100 - 5% fee) + 5 USDT fee distribution = 100 USDT needed
// Treasury only has 98 USDT, so we need to add more to cover:
// 1. Platform fee shortfall: 2 USDT
// 2. Min balance to prevent NotExpendable error: 1 USDT
let treasury = presale_treasury(0);
mint_assets(2, treasury, 3_000_000); // Add 3 USDT total
// Bob requests refund (not in grace period)
System::set_block_number(30);
let initial_balance = Assets::balance(2, 2);
assert_ok!(Presale::refund(RuntimeOrigin::signed(2), 0));
// Check refund with 5% fee (calculated on NET amount in treasury after 2% platform fee)
let platform_fee = contribution * 2 / 100; // 2 USDT platform fee at contribution
let net_in_treasury = contribution - platform_fee; // 98 USDT actually in treasury
let fee = net_in_treasury * 5 / 100; // 4.9 USDT refund fee
let refund_amount = net_in_treasury - fee; // 93.1 USDT refunded to user
// Check Bob's balance increased
assert_eq!(Assets::balance(2, 2), initial_balance + refund_amount);
// Check contribution marked as refunded
let contribution_info = Presale::contributions(0, 2).unwrap();
assert!(contribution_info.refunded);
// Check total raised decreased (gross amount)
assert_eq!(Presale::total_raised(0), 0);
// Check event
System::assert_last_event(
Event::Refunded { presale_id: 0, who: 2, amount: refund_amount, fee }.into(),
);
});
}
#[test]
fn refund_in_grace_period_lower_fee() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24, // 24 blocks grace period (block 1 + 24 = 25)
5, // 5% regular refund fee
2, // 2% grace refund fee
)
));
let contribution = 100_000_000; // 100 USDT
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, contribution));
// Treasury received 98 USDT (after 2% platform fee)
// Refund = 98 USDT (100 - 2% grace fee) + 2 USDT fee distribution = 100 USDT needed
// Treasury only has 98 USDT, so we need to add more to cover:
// 1. Platform fee shortfall: 2 USDT
// 2. Min balance to prevent NotExpendable error: 1 USDT
let treasury = presale_treasury(0);
mint_assets(2, treasury, 3_000_000); // Add 3 USDT total
// Refund within grace period (block < 25)
System::set_block_number(20);
let initial_balance = Assets::balance(2, 2);
assert_ok!(Presale::refund(RuntimeOrigin::signed(2), 0));
// Should use grace period fee (2% of NET amount in treasury)
let platform_fee = contribution * 2 / 100; // 2 USDT platform fee at contribution
let net_in_treasury = contribution - platform_fee; // 98 USDT in treasury
let grace_fee = net_in_treasury * 2 / 100; // 1.96 USDT grace period fee
let refund_amount = net_in_treasury - grace_fee; // 96.04 USDT refunded to user
assert_eq!(Assets::balance(2, 2), initial_balance + refund_amount);
});
}
#[test]
fn refund_with_no_contribution_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Bob tries to refund without contributing
assert_noop!(Presale::refund(RuntimeOrigin::signed(2), 0), Error::<Test>::NoContribution);
});
}
#[test]
fn cancel_presale_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Bob contributes
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 100_000_000));
// Root cancels presale (EmergencyOrigin is EnsureRoot in mock)
assert_ok!(Presale::cancel_presale(RuntimeOrigin::root(), 0));
// Check status changed
let presale = Presale::presales(0).unwrap();
assert!(matches!(presale.status, PresaleStatus::Cancelled));
// Check event
System::assert_last_event(Event::PresaleCancelled { presale_id: 0 }.into());
});
}
#[test]
fn cancel_presale_non_authorized_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Non-authorized user tries to cancel (needs EmergencyOrigin or Root)
assert_noop!(
Presale::cancel_presale(RuntimeOrigin::signed(2), 0),
pezsp_runtime::DispatchError::BadOrigin
);
});
}
#[test]
fn emergency_cancel_by_root_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Root can cancel any presale (emergency)
assert_ok!(Presale::cancel_presale(RuntimeOrigin::root(), 0));
let presale = Presale::presales(0).unwrap();
assert!(matches!(presale.status, PresaleStatus::Cancelled));
});
}
#[test]
fn whitelist_presale_works() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
mint_assets(2, 2, 1_000_000_000);
// Create whitelist presale
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
true, // whitelist enabled
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Bob tries to contribute (not whitelisted)
assert_noop!(
Presale::contribute(RuntimeOrigin::signed(2), 0, 100_000_000),
Error::<Test>::NotWhitelisted
);
// Owner adds Bob to whitelist
assert_ok!(Presale::add_to_whitelist(RuntimeOrigin::signed(1), 0, 2));
// Now Bob can contribute
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 100_000_000));
});
}
#[test]
fn add_to_whitelist_non_owner_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
true,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Charlie tries to add Bob to Alice's presale whitelist
assert_noop!(
Presale::add_to_whitelist(RuntimeOrigin::signed(3), 0, 2),
Error::<Test>::NotPresaleOwner
);
});
}
// ========== SOFT CAP TESTS ==========
#[test]
fn finalize_presale_soft_cap_reached_success() {
new_test_ext().execute_with(|| {
create_assets();
// Setup: Alice creates presale
// Soft cap: 5,000 USDT, Hard cap: 10,000 USDT
mint_assets(1, 1, 100_000_000_000_000_000_000); // 100,000 PEZ
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Mint PEZ to presale treasury
let treasury = presale_treasury(0);
mint_assets(1, treasury, 100_000_000_000_000_000_000);
// Contributors exceed soft cap (max is 1000 USDT each)
// Need 6 contributors to reach 6000 USDT (above soft cap of 5000)
for i in 2..8 {
mint_assets(2, i, 1_010_000_000); // 1000 USDT + 10 ED
assert_ok!(Presale::contribute(RuntimeOrigin::signed(i), 0, 1_000_000_000));
}
// total_raised tracks gross amounts: 6 * 1000 = 6,000 USDT > soft cap (5,000 USDT) ✅
let total_gross = 6_000_000_000;
assert_eq!(Presale::total_raised(0), total_gross);
// Move past presale end
System::set_block_number(102);
// Root finalizes presale
assert_ok!(Presale::finalize_presale(RuntimeOrigin::root(), 0));
// Check presale status is Finalized (went through Successful)
let presale = Presale::presales(0).unwrap();
assert!(matches!(presale.status, PresaleStatus::Finalized));
// Check contributors received tokens
// Total raised: 6,000 USDT
// Tokens for sale: 10,000 PEZ (10^12 decimals)
// Each contributor's share: (1,000 / 6,000) * 10,000 = 1,666.67 PEZ
for i in 2..8 {
assert!(Assets::balance(1, i) > 0, "Contributor {i} should receive PEZ");
}
});
}
#[test]
fn finalize_presale_soft_cap_not_reached_fails() {
new_test_ext().execute_with(|| {
create_assets();
// Setup: Alice creates presale
// Soft cap: 5,000 USDT, Hard cap: 10,000 USDT
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Contributors below soft cap (max is 1000 USDT each)
// Need to contribute less than soft cap of 5000 USDT
mint_assets(2, 2, 1_010_000_000); // Bob: 1000 USDT + 10 ED
mint_assets(2, 3, 1_010_000_000); // Charlie: 1000 USDT + 10 ED
mint_assets(2, 4, 1_010_000_000); // Dave: 1000 USDT + 10 ED
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 1_000_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(3), 0, 1_000_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(4), 0, 1_000_000_000));
// total_raised tracks gross amounts: 1000 + 1000 + 1000 = 3,000 USDT < soft cap (5,000
// USDT) ❌
let total_gross = 3_000_000_000;
assert_eq!(Presale::total_raised(0), total_gross);
// Move past presale end
System::set_block_number(102);
// Root finalizes presale
assert_ok!(Presale::finalize_presale(RuntimeOrigin::root(), 0));
// Check presale status is Failed (soft cap not reached)
let presale = Presale::presales(0).unwrap();
assert!(matches!(presale.status, PresaleStatus::Failed));
// Check contributors did NOT receive tokens (presale failed)
assert_eq!(Assets::balance(1, 2), 0); // Bob received nothing
assert_eq!(Assets::balance(1, 3), 0); // Charlie received nothing
});
}
#[test]
fn batch_refund_failed_presale_works() {
new_test_ext().execute_with(|| {
create_assets();
// Setup: Alice creates presale
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Fund presale treasury with wUSDT for refunds
let treasury = presale_treasury(0);
mint_assets(2, treasury, 10_000_000_000); // Mint enough for refunds
// Contributors below soft cap (max is 1000 USDT each)
// Need ED (10 USDT) + contribution amount
mint_assets(2, 2, 510_000_000); // Bob gets 510 USDT (500 + 10 ED)
mint_assets(2, 3, 510_000_000); // Charlie gets 510 USDT (500 + 10 ED)
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 500_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(3), 0, 500_000_000));
// Record initial balances
let bob_initial = Assets::balance(2, 2);
let charlie_initial = Assets::balance(2, 3);
// Move past presale end and finalize (will set status to Failed)
System::set_block_number(102);
assert_ok!(Presale::finalize_presale(RuntimeOrigin::root(), 0));
// Check status is Failed
let presale = Presale::presales(0).unwrap();
assert!(matches!(presale.status, PresaleStatus::Failed));
// Anyone can call batch_refund_failed_presale
assert_ok!(Presale::batch_refund_failed_presale(
RuntimeOrigin::signed(4), // Random account (not owner)
0, // presale_id
0, // start_index
10, // batch_size (refund up to 10 contributors)
));
// Check contributors got full refunds (NO FEE for failed presale)
assert_eq!(Assets::balance(2, 2), bob_initial + 500_000_000); // Full refund
assert_eq!(Assets::balance(2, 3), charlie_initial + 500_000_000); // Full refund
// Check contributions marked as refunded
let bob_contribution = Presale::contributions(0, 2).unwrap();
assert!(bob_contribution.refunded);
assert_eq!(bob_contribution.refund_fee_paid, 0); // No fee!
});
}
#[test]
fn batch_refund_successful_presale_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
let treasury = presale_treasury(0);
mint_assets(1, treasury, 100_000_000_000_000_000_000);
mint_assets(2, treasury, 10_000_000_000);
// Exceed soft cap (soft cap is 5000 USDT, so contribute 1000 USDT to reach it)
// Actually need multiple contributors since max is 1000 USDT each
mint_assets(2, 2, 1_010_000_000); // 1000 USDT + 10 ED
mint_assets(2, 3, 1_010_000_000);
mint_assets(2, 4, 1_010_000_000);
mint_assets(2, 5, 1_010_000_000);
mint_assets(2, 6, 1_010_000_000);
assert_ok!(Presale::contribute(RuntimeOrigin::signed(2), 0, 1_000_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(3), 0, 1_000_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(4), 0, 1_000_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(5), 0, 1_000_000_000));
assert_ok!(Presale::contribute(RuntimeOrigin::signed(6), 0, 1_000_000_000));
// Finalize (will succeed because soft cap reached)
System::set_block_number(102);
assert_ok!(Presale::finalize_presale(RuntimeOrigin::root(), 0));
// Try to batch refund a successful presale (should fail)
assert_noop!(
Presale::batch_refund_failed_presale(RuntimeOrigin::signed(4), 0, 0, 10,),
Error::<Test>::PresaleNotFailed
);
});
}
#[test]
fn create_presale_with_soft_cap_greater_than_hard_cap_fails() {
new_test_ext().execute_with(|| {
create_assets();
mint_assets(1, 1, 100_000_000_000_000_000_000);
// Try to create presale with soft_cap > hard_cap (invalid)
assert_noop!(
Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
15_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
),
Error::<Test>::InvalidTokensForSale
);
});
}
#[test]
fn debug_finalize_presale() {
use crate::mock::*;
use pezframe_support::assert_ok;
new_test_ext().execute_with(|| {
create_assets();
// Mint reward tokens to owner
mint_assets(1, 1, 100_000_000_000_000_000_000);
// Create presale
assert_ok!(Presale::create_presale(
RuntimeOrigin::signed(1),
2,
1,
make_presale_params(
10_000_000_000,
100,
false,
10_000_000,
1_000_000_000,
5_000_000_000,
10_000_000_000,
false,
0,
0,
0,
24,
5,
2
)
));
// Fund presale treasury with reward tokens
let treasury = presale_treasury(0);
mint_assets(1, treasury, 1_000_000_000_000_000_000_000);
// Fund platform accounts
mint_assets(2, 999, 100_000_000_000);
mint_assets(2, 998, 100_000_000_000);
// Create 25 contributors
for i in 2..27 {
mint_assets(2, i, 220_000_000); // payment asset
mint_assets(1, i, 1_000_000_000); // reward asset
assert_ok!(Presale::contribute(RuntimeOrigin::signed(i), 0, 200_000_000));
}
// Move to end
System::set_block_number(150);
// Try to finalize
let result = Presale::finalize_presale(RuntimeOrigin::root(), 0);
println!("Finalize result: {result:?}");
assert_ok!(result);
});
}